This is a block that illustrates some of the tricks I've learned while designing scroll narratives like A Visual Introduction to Machine Learning and Let's Free Congress. While the animations are much more complicated, the core mechanics are similar

Let's get scrolling!

In traditional animation, motion is linked to time. In Javascript that might be using setInterval to call some render code regularly, with the elapsed time as an input.

In scroll-linked animations, instead of using elapsed time, the scrollTop is used as the driver of motion. The scrollTop value (currently: ) can be transformed in various ways for various effects.

On the right, the scrollTop value is used as the input into a d3.scale function. That value is then used as the rotation value on the <g> group.

There are two chunks of code that makes this all work. First is an event handler that records the scroll position. It looks like this:

  .on("scroll.scroller", function() {
    newScrollTop = container.node().scrollTop

The second piece of code is the render code. Approximately 60 times a second (using window.requestAnimationFrame) it checks if newScrollTop is different from scrollTop. If it is different, then update our graphics accordingly. It looks like this:

var render = function() {
  // Don't re-render if scroll didn't change
  if (scrollTop !== newScrollTop) {
    // Graphics Code Goes Here


That's the core of it. Although there's a couple other minor tricks that's worth pointing out.

1. Pacing the Panels: These panels that contains the text are spaced according to the height of the window, such that just one paragraph is visible at a time.

To achieve this in a responsive way, I use vh units to set the top and bottom padding on each panel. That way, the paragraphs are spaced correctly no matter the size of the screen.

2. Responsive Timing: The clock on the right hits 12 just as you finish scrolling, no matter what the screen size is. Getting the animation in sync with scroll requires using the dimensions of container and the screen as input in the animation scaling function.

The way this is achieved is through a callback on the window.resize handler. It reads in the relevant dimensions and feeds it back into the .domain of the d3.scale function.