https://www.joshwcomeau.com/animation/sprites/
JoshWComeau
* categories
* courses
* goodies
* About
Sprites on the Web
Filed under
Animation
on
in
February 23rd, 2026.
Feb 2026.
Last updated
on
in
February 24th, 2026.
Feb 2026.
Table of Contents
IntroductionBasic implementationStep positionsUse cases
Introduction
In 2015, back when Twitter was still Twitter, their dev team had a
problem.
In those early days, tweets could be "favourited" by clicking a
little "" icon. The product team wanted to migrate to "liking"
tweets, Facebook-style, with a "[?]". As part of this update, their
designers created this lovely animation:
A heart goes from gray to red, swelling and spraying particles in all
directions
Pause
This looks super nice, but there's kind of a lot going on in there;
by my count, there are 16 separate elements all animating at the same
time (14 particles, the popping circle, the heart). Twitter's web app
needed to run on very low-end mobile devices, so it wasn't feasible
to create this procedurally using DOM nodes. Instead, they decided to
borrow a technique from video games: sprites.
The basic idea with a sprite is that we create a single image that
contains each individual frame of an animation in a long strip. Then,
we display each frame for a fraction of a second, like a roll of film
sliding through an oldschool film projector:
Show each frame for:500ms
[500 ]
[twitter-li]
Current frame of sprite animation
In this blog post, I'll show you the best way I've found to work with
sprites in CSS, and share some of the use cases I've discovered.
We'll also talk about some of the trade-offs, to see when we
shouldn't use sprites.
Motion Warning
Your operating system has the "Reduce motion" option enabled. I
generally do my best to respect this preference on this blog, but for
this post in particular, there are several motion-heavy animations
that have not been disabled. These animations are necessary in order
to understand the animation technique.
If you have a motion sensitivity, please exercise caution with this
blog post!
Link to this headingBasic implementation
First thing's first, we need an asset! Let's use a gold trophy sprite
I created a few years ago:
[gold-troph]A shiny gold trophy with a diamond and blue flames
To produce the illusion that the fire is flickering, I drew five
different versions of the blue flames. These frames are stacked
side-by-side in a single image known as a "spritesheet":
A hand-drawn gold trophy with blue flames. There are 5 copies
side-by-side, with each trophy having a slightly different flame
pattern
Here's the fundamental strategy: we'll create an tag and
calculate its size based on one of these frames. We can then use
object-fit and object-position to control which part of the sprite is
currently visible, flipping through each frame using a CSS keyframe
animation.
This full image has a native resolution of 2000px x 800px, and
contains 5 frames. This means that each frame is 400px x 800px. In
order for this image to look sharp on high-resolution displays, we'll
want to cut this size in half, so our final image will be 200px x
400px.
By default,
tags will try to squeeze the entire image content
into the DOM node's area, meaning we'll wind up seeing all 5
trophies, crammed together:
Code Playground
Format code using Prettier
Reset Code
HTMLCSS
Focus the editor. This will trap focus until you press Escape.Code
editor:
[
[
[
[
(Click the "Refresh" icon by RESULT above to re-run the animation.)
[