Creating scroll-initiated motion can be immensely gratifying. We’ll be diving into a particular effect seen on the Casper Mattress website (created by Jonnie Hallman) which captivated many developers. In this tutorial we’ll breakdown the steps needed to make a similar sprite animation with ScrollMagic.
End Result
Let’s take a look at what we’ll be creating:
The Sprite
Before we begin coding we’ll need a sprite to use as the basis for this demo. Here’s the one we’ll use: me, enjoying a lovely cup of coffee. You can grab a copy in order to follow the tutorial.

The entire sprite is arranged in linear fashion along the x-axis. Each image within the sprite is 200px x 509px resulting in a total size of 2000px x 509px (each single image is 200px wide multiplied by ten keyframes).
Extra Points Already
For the extremely curious I specifically utilized the Gulp plugin gulp.spritesmith
instead of piecing together the sprite manually. While it’s not required to create this demo, here’s the setup I used in case you’d like to try it yourself:
var gulp = require('gulp'); var spritesmith = require('gulp.spritesmith'); gulp.task('sprite', function () { var spriteData = gulp.src('images/*.png').pipe(spritesmith({ imgName: 'sprite.png', cssName: 'sprite.css', algorithmOpts: { sort: false }, algorithm: 'left-right', })); return spriteData.pipe(gulp.dest('images/dist')); });
It's important to note the algorithm property can accept an assortment of values in order to arrange the sprite as linear, vertical, diagonal and more. I recommend you take the time to read more about this Gulp plugin for any of your future sprite needs.
Creating the Markup
The markup is the soil from which our demo will grow so we’ll start by creating the container where the sprite animation will take place. We’ll also construct some dummy containers for the sake of having regions to scroll through.
<section></section><section><div></div></section><section></section>
The middle section with the containing div
is where our sprite motion fires, but we’ll need to add some classes and ids for styling and event hook purposes.
<section class="panel"></section><section class="panel js-scrollpin"><div class="frame"></div></section><section class="panel"></section>
The class js-scrollpin
is the point from which the viewport will become “pinned” as the user scrolls. This sticking point will last for a defined duration set from within our JavaScript ( we’ll address this in an upcoming section). The inner div
with the class frame
will be the point where the scrolling magic takes place.
Creating the Styles
Our demo wouldn’t be complete without the styling, so let’s get started! To write the code with updates and refinements in mind we’ll use Sass, allowing us to create the loop and variables for succinct authoring.
$frame-count: 9; $offset-val: 100;
These variables are defining:
- the frame count, equal to the number of images in the sprite
- and the offset value that positions the background-position of each singular image inside the sprite.
You may notice that I’ve only set the frame count to 9 due to the fact the first frame is already in view leaving 9 frames remaining in the sequence (10 - 1 = 9).
I’ve Been Framed
As mentioned, each image within our sprite is 200px wide so I’ll define the width of the frame as being 200px.

.frame { width: 200px; background-image: url(sprite.png); background-repeat: no-repeat; background-position: 0 50%; }
The image is loaded as a background and positioned accordingly.
Now for the loop:
@for $i from 1 through $frame-count { .frame#{$i} { background-position: -(($i * $offset-val) * 2) + px 50%; } }
This Sass loop is the most important part. As we scroll, each correlating class will be toggled via JavaScript; these toggled class names correlate to the position of each image within the sprite.

.frame1 { background-position: -200px 50%; } .frame2 { background-position: -400px 50%; } .frame3 { background-position: -600px 50%; } .frame4 { background-position: -800px 50%; } .frame5 { background-position: -1000px 50%; } .frame6 { background-position: -1200px 50%; } .frame7 { background-position: -1400px 50%; } .frame8 { background-position: -1600px 50%; } .frame9 { background-position: -1800px 50%; }
The compiled output from our Sass helps to explain how this math works, but let’s dive deeper to make sure we fully understand how the arithmetic is calculated.
$i * $offset-val
On each iteration of the loop the $i
variable cycles from 1 through 9 and multiplied by the offset value (100). Finally we take the entire result and multiply by a factor of 2. The factor of 2 will help to move the background position in relation to where each image resides in the sprite. It also matches the offset value defined in the JavaScript which we’ll discuss in the next part of our journey.
Casting the JavaScript
The key to this entire effect is the JavaScript. For this particular demo we’ll be using ScrollMagic; a JavaScript library for magical scroll interactions. Before writing any ScrollMagic code let’s make sure we have the ScrollMagic library loaded.
With that done, we’ll establish some variables.
var frame = document.querySelector('.frame'), frame_count = 9, offset = 100;
The variables defined should look pretty familiar as they directly relate to the values we specified in our Sass, with the exception of selecting the frame class using document.querySelector
.
var controller = new ScrollMagic.Controller({ globalSceneOptions: { triggerHook: 0 } });
The controller is the main class required once per scroll container. Our globalSceneOptions
object passed contains a triggerHook
. This property’s values can be a number between 0 and 1 defining the position of the trigger hook in relation to the viewport.
new ScrollMagic.Scene({ triggerHook: 0, triggerElement: '.js-scrollpin', duration: (frame_count * offset) + 'px', reverse: true }) .setPin('.js-scrollpin') .addTo(controller);
We define the pin once in the target scene and attach our animation within the same scene. We’ll create a scene for every class and toggle them on or off depending on the offset value of the scroll.
Break it Down
There are a few things going still left to explain so let’s break down the important parts:
triggerElement: '.js-scrollpin'
The Trigger element is the referencing id of the panel that contains our sprite.
.setPin('.js-scrollpin')
The setPin
method will tell ScrollMagic to, once the triggerElement
is reached, pin that panel to the top of the viewport.

duration: (frame_count * offset) + 'px',
The duration will help control how long the motion occurs, based on the frame count and offset. As you’ll remember, we had a frame count of 9 and an offset of 100. These values when multiplied together equate to 900px; the total value of the trigger offset points.
for (var i = 1, l = frame_count; i <= l; i++) { new ScrollMagic.Scene({ triggerElement: '.js-scrollpin', offset: i * offset }) .setClassToggle(frame, 'frame' + i) .addTo(controller); }
Since there are 9 frames, we loop through that number of iterations whilst defining the same panel for a triggerElement. On each iteration of the loop we multiply that loop’s value by the offset amount (1 * 100, 2 * 100, 3 * 100…). We also define a class to toggle through; .frame1
, .frame2
, .frame3
and so on. Each of the classes represents the background position of each image within the sprite.
And We’re Done!
Conclusion
You might be wondering what the total file size of the sprite is, so let me lay down some numbers. Uncompressed the image weighed in at a hefty 1.5 MB, but when compressed resulted in a more acceptable 319 KB. This does not include the 6KB gzipped to load the ScrollMagic library.
I hope you're inspired by this technique to use in your next project and please feel free, as always, to ask questions in the comments below. Happy coding!