D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
tophtucker
Full window
Github gist
Typeset gyro
<!DOCTYPE html> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="apple-mobile-web-app-capable" content="yes"> <style> * { box-sizing: border-box; } html, body { margin: 0; width: 100%; height: 100%; position: relative; font-size: 30px; font-family: sans-serif; } /* TEXT DISPLAY */ .container { position: absolute; top: 3em; left: 0; width: 100%; height: calc(100% - 5em); overflow-y: scroll; -webkit-overflow-scrolling: touch; padding: .5em; } p { font-size: 1em; margin: 0; } p span { position: relative; display: inline-block; white-space: pre; } p span.active { background: rgba(255,0,255,.2); } /* TIMELINE */ .timeline-row { position: fixed; top: 0; left: 0; width: 100%; height: 3em; border-bottom: 1px solid black; } .record { display: block; float: left; width: 20%; border-bottom: none; top: 0; height: 100%; } .timeline { display: block; float: right; width: 80%; height: 100%; overflow: hidden; position: relative; } .timeline .timeline-inner { position: absolute; top: 0; left: 0; height: 100%; } .timeline .timeline-inner span { position: absolute; height: 100%; line-height: 3rem; top: 0; width: 2em; text-align: center; font-size: 2em; } .timeline div.frame { position: absolute; top: 0; left: 50%; transform: translate(-50%, 0); width: 2em; height: 100%; border: 2px solid rgba(255,0,255,1); } /* CONTROLS */ .controls { position: fixed; bottom: 0; left: 0; width: 100%; height: 2rem; border-top: 1px solid black; background: white; } .controls .row { height: 2em; } button { height: 100%; padding: .5em; background: white; border: none; border-right: 1px solid black; border-bottom: 1px solid black; font-family: inherit; font-size: inherit; cursor: pointer; } .controls .row.one-col button { width: 100%; } .controls .row.four-col button { width: 25%; } .controls button.record { color: red; } .toggles button.active { background: black; color: white; } .toggles button span { display: inline-block; } /* .toggles button.alpha span { animation: 3s linear 0s infinite normal alpha; } .toggles button.beta span { animation: 3s linear 0s infinite normal beta; } .toggles button.gamma span { animation: 3s linear 0s infinite normal gamma; } .toggles button.scale span { animation: 3s ease-in-out 0s infinite alternate scale; } @keyframes alpha { from { transform: rotateZ(0deg); } to { transform: rotateZ(360deg); } } @keyframes beta { from { transform: rotateX(0deg); } to { transform: rotateX(360deg); } } @keyframes gamma { from { transform: rotateY(0deg); } to { transform: rotateY(360deg); } } @keyframes scale { from { transform: scale(2); } to { transform: scale(.5); } } */ </style> <body> <div class="row timeline-row"> <button class="record">▶️</button><div class="timeline"><div class="frame"></div></div> </div> <!-- No man is an island, entire of itself; every man is a piece of the continent, a part of the main. If a clod be washed away by the sea, Europe is the less, as well as if a promontory were, as well as if a manor of thy friend’s or of thine own were: any man’s death diminishes me, because I am involved in mankind, and therefore never send to know for whom the bell tolls; it tolls for thee. --> <div class="container"> <p>No man is an island, entire of itself; every man is a piece of the continent, a part of the main. Any man’s death diminishes me, because I am involved in mankind, and therefore never send to know for whom the bell tolls; it tolls for thee.</p> </div> <div class="controls"> <div class="row four-col toggles"> <button class="alpha"><span>A</span> </button><button class="beta"><span>A</span> </button><button class="gamma"><span>A</span> </button><button class="scale"><span>A</span></button> </div> </div> </body> <script src="https://d3js.org/d3.v4.min.js"></script> <script> var toggles = { alpha: true, beta: true, gamma: false, scale: true } setupToggles(toggles, d3.select('.toggles')); // store current rotation in euler angles var rotation = { alpha: 0, beta: 0, gamma: 0 }; // store whole history of acceleration and implied velocity and position, // starting from these initial conditions var z = [ { position: 0, velocity: 0, acceleration: 0, time: undefined } ]; var duration = 30 * 1000; var currentIndex = 0, indexOffset = 0; window.addEventListener('devicemotion', handleMotion); window.addEventListener('deviceorientation', handleOrientation); window.addEventListener('mousemove', handleMousemove); var sel = d3.select('.container p'); var text = sel.text().split(''); var letter = sel .text('') .selectAll('span') .data(text) .enter() .append('span') .text(function(d) { return d; }); var letterTl = d3.select('.timeline') .append('div') .classed('timeline-inner', true) .selectAll('span') .data(text) .enter() .append('span') .text(function(d) { return d; }) .style('left', function(d,i) { return i * 40 - this.offsetWidth/2 + 'px'; }); var timeScale = d3.scaleLinear() .domain([0, duration]) .range([0, letter.size()]) .clamp(true); var scaleScale = d3.scaleLinear() .domain([-5,5]) .range([-1,1]) .clamp(true); var timeline = d3.select('.timeline'); var timelineScale = d3.scaleLinear() .domain([timeline.node().offsetWidth / 2, timeline.node().offsetWidth / 2 - letter.size() * 40]) .range([0, letter.size()]) .clamp(true); var timelineDrag = d3.drag() .container(function() { return this; }) .on('start', function() { pauseTimer(); }) .on('drag', function() { d3.select('.timeline .timeline-inner') .style('left', function() { return d3.event.dx + parseInt(d3.select(this).style('left')) +'px'; }) }) .on('end', function() { d3.select('.timeline .timeline-inner') .style('left', function() { currentIndex = Math.round(timelineScale(parseInt(d3.select(this).style('left')))); return timelineScale.invert(currentIndex) + 'px'; }); }); timeline.call(timelineDrag); var renderTimer = d3.timer(render); var indexTimer; pauseTimer(); setIndex(0); function startTimer() { indexOffset = currentIndex; d3.select('button.record') .text('⏸') .on('click', pauseTimer); if(indexTimer) indexTimer.stop(); indexTimer = d3.timer(setIndex); } function pauseTimer() { d3.select('button.record') .text('▶') .on('click', startTimer); if(indexTimer) indexTimer.stop(); } function endTimer() { letter .classed('active', false); d3.select('button.record') .text('↩️️️') .on('click', restartTimer); if(indexTimer) indexTimer.stop(); } function restartTimer() { currentIndex = 0; indexOffset = 0; setIndex(0); pauseTimer(); } function setIndex(t) { currentIndex = Math.max(0,Math.floor(timeScale(t))) + indexOffset; if(currentIndex >= letter.size()) endTimer(); d3.select('.timeline .timeline-inner') .style('left', function() { return timelineScale.invert(currentIndex) + 'px'; }); // letterTl // .filter(function(d,i) { // return Math.abs(currentIndex - i) >= 4; // }).style('display', 'none'); // letterTl // .select(function(d,i) { // return (Math.abs(currentIndex - i) < 4) ? this : null; // }).style('display', 'block'); } function render() { d3.select('.controls .alpha span').style('transform', 'rotateZ('+ -rotation.alpha +'deg)'); d3.select('.controls .beta span').style('transform', 'rotateX('+ rotation.beta +'deg)'); d3.select('.controls .gamma span').style('transform', 'rotateY('+ rotation.gamma +'deg)'); d3.select('.controls .scale span').style('transform', 'scale('+ (Math.pow(2,scaleScale(z[0].acceleration))) +')'); var transformString = ''; if(toggles.alpha) transformString += 'rotateZ('+ -rotation.alpha +'deg) '; if(toggles.beta) transformString += 'rotateX('+ rotation.beta +'deg) '; if(toggles.gamma) transformString += 'rotateY('+ rotation.gamma +'deg) '; if(toggles.scale) transformString += 'scale('+ (Math.pow(2,scaleScale(z[0].acceleration))) +')'; letter .classed('active', false) .filter(function(d,i) { return i === currentIndex; }) .classed('active', true) .style('transform', transformString) .each(function() { var isTooHighToSee = this.offsetTop < this.offsetParent.scrollTop; var isTooLowToSee = this.offsetTop + this.offsetHeight > this.offsetParent.offsetHeight + this.offsetParent.scrollTop; if(isTooHighToSee) { this.offsetParent.scrollTop = this.offsetTop; } if(isTooLowToSee) { this.offsetParent.scrollTop = this.offsetTop + this.offsetHeight - this.offsetParent.offsetHeight; } }); letterTl .filter(function(d,i) { return i === currentIndex; }) .style('transform', transformString); } function handleOrientation(e) { if(e.gamma === null || e.beta === null || e.alpha === null) return; rotation = { gamma: e.gamma || 0, beta: e.beta || 0, alpha: e.alpha || 0 } } // accelerate according to z-axis device motion function handleMotion(e) { if(e.acceleration.x === null || e.acceleration.y === null || e.acceleration.z === null) return; accelerate(e.acceleration.z, e.timeStamp); } // for testing on desktop, basically: map horizontal mouse position to acceleration function handleMousemove(e) { var mouseAccelerator = d3.scaleLinear() .domain([0,innerWidth]) .range([-1,1]); accelerate(mouseAccelerator(e.pageX), e.timeStamp); } // step forward with new acceleration, applying some very crude filtering & friction function accelerate(a, t) { var newZ = Object.assign({}, z[0]); newZ.acceleration = Math.abs(a) > .1 ? a : 0; // noise filter newZ.time = t; newZ = eulerStep(z[0], newZ); newZ.velocity *= .9; // friction newZ.velocity = Math.abs(newZ.velocity) < .01 ? 0 : newZ.velocity; // noise filter newZ.position *= .999; // tend back to zero z.unshift(newZ); } // euler double integration function eulerStep(state0, state1) { var interval = (state1.time - state0.time) / 1000; // convert ms to s if(interval) { state1.position = state0.position + state0.velocity * interval; state1.velocity = state0.velocity + state0.acceleration * interval; } return Object.assign({}, state1); } function setupToggles(toggles, sel) { Object.keys(toggles).forEach(function(key) { sel.select('.'+key) .on('click', function() { if(d3.select(this).classed('active')) { // disable d3.select(this).classed('active', false); toggles[key] = false; } else { // enable d3.select(this).classed('active', true); toggles[key] = true; } }); if(toggles[key]) sel.select('.'+key).node().click(); }); } </script>
https://d3js.org/d3.v4.min.js