Earth Rotating Sprite Animation

I wanted to animate a sprite by targeting a changing data attribute + unique css background-positioning. I win.

The setup is pretty basic. I have an event listener on document scroll. Using that event I can see how far you have scrolled (window.scrollY). I add that number to an existing counter. Sprinkle on some math (below) and you have the current frame. Easy peasy.

The sprite that I'm using has 16 rows and 16 columns. So I had to add in the math for wrapping around from position 0, 15 to 1, 0. That’s why I use $sprite-height and $frame-height below.

Demo

SASS

  • Width of sprite image: $sprite-width
  • Height of sprite image: $sprite-height
  • Number of horizontal frames: $x-max
  • Number of vertical frames: $y-max
  • Frame width: $frame-width = $sprite-width / $x-max
  • Frame height: $frame-height = $sprite-height / $y-max
#frame-container {
  width: round($frame-width);
  height: round($frame-height);
  background-image: url(/path/to/sprite.jpg);

  @for $frame from 0 to ($y-max * $x-max) {
    &[data-number="#{$frame}"] {
      $x-position: 1 - round(($frame % $x-max) * $frame-width);
      $y-position: 1 - round(floor($frame / $x-max) * $frame-height);
      background-position: #{$x-position} #{$y-position};
    }
  }
}

JavaScript

There’s almost nothing going on here. Set an event listener on the document for scroll. In the caught event, find the scroll movement using scrollY. Add that to the existing frame number and add a large multiple of numberOfFrames to account for negative numbers (you don’t want a negative frame number). Then, find the modulus of that number to get a final frame number between zero and the maximum number of frames. Finally, set that number to the container’s data attribute, and let the CSS do the rendering work.

const frameContainer = document.getElementById('frame-container');
const numberOfFrames = 16 * 16;
let number = 0;
let lastKnownScrollPosition = 0;

function updateEarth(value) {
  number = ((number + value) + (numberOfFrames * 100)) % numberOfFrames;
  frameContainer.dataset.number = number;
}

document.addEventListener('scroll', (event) => {
  const scrollPosition = Math.floor(window.scrollY);
  const movementY = scrollPosition - lastKnownScrollPosition;
  lastKnownScrollPosition = scrollPosition;

  window.requestAnimationFrame(() => {
    updateEarth(movementY);
  });
});