Responsive Animations for Every Screen Size and Device
Cassie Evans on
Oct 20, 2022
Let’s put those CSS skills to work! Claim $50 in free hosting credit on Cloudways with code CSSTRICKS.
Before I career jumped into development, I did a bunch of motion graphics work in After Effects. But even with that background, I still found animating on the web pretty baffling.
Video graphics are designed within a specific ratio and then exported out. Done! But there aren’t any “export settings” on the web. We just push the code out into the world and our animations have to adapt to whatever device they land on.
So let’s talk responsive animation! How do we best approach animating on the wild wild web? We’re going to cover some general approaches, some GSAP-specific tips and some motion principles. Let’s start off with some framing…
How will this animation be used?
Zach Saucier’s article on responsive animation recommends taking a step back to think about the final result before jumping into code.
Will the animation be a module that is repeated across multiple parts of your application? Does it need to scale at all? Keeping this in mind can help determine the method in which an animation should be scaled and keep you from wasting effort.
This is great advice. A huge part of designing responsive animation is knowing if and how that animation needs to scale, and then choosing the right approach from the start.
Most animations fall into the following categories:
Fixed: Animations for things like icons or loaders that retain the same size and aspect ratio across all devices. Nothing to worry about here! Hard-code some pixel values in there and get on with your day.
Fluid: Animations that need to adapt fluidly across different devices. Most layout animations fall into this category.
Targeted: Animations that are specific to a certain device or screen size, or change substantially at a certain breakpoint, such as desktop-only animations or interactions that rely on device-specific interaction, like touch or hover.
Fluid and targeted animations require different ways of thinking and solutions. Let’s take a look…
Fluid animation
As Andy Bell says:
Be the browser’s mentor, not its micromanager — give the browser some solid rules and hints, then let it make the right decisions for the people that visit it.
(Here are the slides from that presentation.)
Fluid animation is all about letting the browser do the hard work. A lot of animations can easily adjust to different contexts just by using the right units from the start. If you resize this pen you can see that the animation using viewport units scales fluidly as the browser adjusts:
CodePen Embed Fallback
The purple box even changes width at different breakpoints, but as we’re using percentages to move it, the animation scales along with it too.
Animating layout properties like left and top can cause layout reflows and jittery ‘janky’ animation, so where possible stick to transforms and opacity.
We’re not just limited to these units though — let’s take a look at some other possibilities.
SVG units
One of the things I love about working with SVG is that we can use SVG user units for animation which are responsive out of the box. The clue’s in the name really — Scalable Vector Graphic. In SVG-land, all elements are plotted at specific coordinates. SVG space is like an infinite bit of graph paper where we can arrange elements. The viewBox defines the dimensions of the graph paper we can see.
viewBox="0 0 100 50”
In this next demo, our SVG viewBox is 100 units wide and 50 units tall. This means if we animate the element by 100 units along the x-axis, it will always move by the entire width of its parent SVG, no matter how big or small that SVG is! Give the demo a resize to see.
CodePen Embed Fallback
Animating a child element based on a parent container’s width is a little tricker in HTML-land. Up until now, we’ve had to grab the parent’s width with JavaScript, which is easy enough when you’re animating from a transformed position, but a little fiddlier when you’re animating to somewhere as you can see in the following demo. If your end-point is a transformed position and you resize the screen, you’ll have to manually adjust that position. Messy… 樂
CodePen Embed Fallback
If you do adjust values on resize, remember to debounce , or even fire the function after the browser is finished resizing. Resize listeners fire a ton of events every second, so updating properties on each event is a lot of work for the browser.
But, this animation speed-bump is soon going to be a thing of the past! Drum roll please… 論
After nearly 15 years as a highly-requested (impossible!?) feature, size-based Container Queries & units have shipped in both Chrome/Edge 105 & Safari 16! Firefox is not far behind.
(also: there's a prototype of style queries!) https://t.co/A2zgd9l4FC
— Mia (@TerribleMia) September 15, 2022
Container Units! Lovely stuff. At the time I’m writing this, they only work in Chrome and Safari — but maybe by the time you read this, we’ll have Firefox too. Check them out in action in this next demo. Look at those little lads go! Isn’t that exciting, animation that’s relative to the parent elements!
CodePen Embed Fallback
This browser support data is from Caniuse , which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
16.0
Fluid layout transitions with FLIP
As we mentioned earlier, in SVG-land every element is neatly placed on one grid and really easy to move around responsively. Over in HTML-land it’s much more complex. In order to build responsive layouts, we make use of a bunch of different positioning methods and layout systems. One of the main difficulties of animating on the web is that a lot of changes to layout are impossible to animate. Maybe an element needs to move from position relative to fixed, or some children of a flex container need to be smoothly shuffled around the viewport. Maybe an element even needs to be re-parented and moved to an entirely new position in the DOM.
Tricky, huh?
Well. The FLIP technique is here to save the day; it allows us to easily animate these impossible things. The basic premise is:
First: Grab the initial position of the elements involved in the transition.
Last: Move the elements and grab the final position.
Invert: Work out the changes between the first and last state and apply transforms to invert the elements back to their original position. This makes it look like the elements are still in the first position but they’re actually not.
Play: Remove the inverted transforms and animate to their faked first state to the last state.
Here’s a demo using GSAP’s FLIP plugin which does all the heavy lifting for you!
CodePen Embed Fallback
If you want to understand a little more about the vanilla implementation, head over to Paul Lewis’s blog post — he’s the brain behind the FLIP technique.
Fluidly scaling SVG
You got me… this isn’t really an animation tip. But setting the stage correctly is imperative for good animation! SVG scales super nicely by default, but we can control how it scales even further with preserveAspectRatio , which is mega handy when the SVG element’s aspect ratio and the viewBox aspect ratio are different. It works much in the same way as the background-position and background-size properties in CSS. The declaration is made up of an alignment value (background-position) and a Meet or Slice reference (background-size).
As for those Meet and Slice references — slice is like background size: cover, and meet is like background-size: contain.
preserveAspectRatio="MidYMax slice" — Align to the middle of the x-axis, the bottom of the y-axis, and scale up to cover the entire viewport.
preserveAspectRatio="MinYMin meet" — Align to the left of the x-axis, the top of the y-axis, and scale up while keeping the entire viewBox visible.
CodePen Embed Fallback
Tom Miller takes this a step further by using overflow: visible in CSS and a containing element to reveal “stage left” and “stage right” while keeping the height restricted:
For responsive SVG animations, it can be handy to make use of the SVG viewbox to create a view that crops and scales beneath a certain browser width, while also revealing more of the SVG animation to the right and left when the browser is wider than that threshold. We can achieve this by adding overflow visible on the SVG and teaming it up with a max-height wrapper to prevent the SVG from scaling too much vertically.
CodePen Embed Fallback
Fluidly scaling canvas
Canvas is much more performant for complex animations with lots of moving parts than animating SVG or HTML DOM, but it’s inherently more complex too. You have to work for those performance gains! Unlike SVG that has lovely responsive units and scaling out of the box,
has to be bossed around and micromanaged a bit.
I like setting up my
so that it works much in the same way as SVG (I may be biased) with a lovely unit system to work within and a fixed aspect ratio.
also needs to be redrawn every time something changes, so remember to delay the redraw until the browser is finished resizing, or debounce!
CodePen Embed Fallback
George Francis also put together this lovely little library which allows you to define a Canvas viewBox attribute and preserveAspectRatio — exactly like SVG!
Targeted animation
You may sometimes need to take a less fluid and more directed approach to your animation. Mobile devices have a lot less real estate, and less animation-juice performance-wise than a desktop machine. So it makes sense to serve reduced animation to mobile users, potentially even no animation:
Sometimes the best responsive animation for mobile is no animation at all! For mobile UX, prioritize letting the user quickly consume content versus waiting for animations to finish. Mobile animations should enhance content, navigation, and interactions rather than delay it. Eric van Holtz
In order to do this, we can make use of media queries to target specific viewport sizes just like we do when we’re styling with CSS! Here’s a simple demo showing a CSS animation being handled using media queries and a GSAP animation being handled with gsap.matchMedia():
CodePen Embed Fallback
The simplicity of this demo is hiding a bunch of magic! JavaScript animations require a bit more setup and clean-up in order to correctly work at only one specific screen size. I’ve seen horrors in the past where people have just hidden the animation from view in CSS with opacity: 0, but the animation’s still chugging away in the background using up resources.
If the screen size doesn’t match anymore, the animation needs to be killed and released for garbage collection, and the elements affected by the animation need to be cleared of any motion-introduced inline styles in order to prevent conflicts with other styling. Up until gsap.matchMedia(), this was a fiddly process. We had to keep track of each animation and manage all this manually.
gsap.matchMedia() instead lets you easily tuck your animation code into a function that only executes when a particular media query matches. Then, when it no longer matches, all the GSAP animations and ScrollTriggers in that function get reverted automatically. The media query that the animations are popped into does all the hard work for you. It’s in GSAP 3.11.0 and it’s a game changer!
We aren’t just constrained to screen sizes either. There are a ton of media features out there to hook into!
(prefers-reduced-motion) /* find out if the user would prefer less animation */ (orientation: portrait) /* check the user's device orientation */ (max-resolution: 300dpi) /* check the pixel density of the device */
In the following demo we’ve added a check for prefers-reduced-motion so that any users who find animation disorienting won’t be bothered by things whizzing around.
CodePen Embed Fallback
And check out Tom Miller’s other fun demo where he’s using the device’s aspect ratio to adjust the animation:
CodePen Embed Fallback
Thinking outside of the box, beyond screen sizes
There’s more to thinking about responsive animation than just screen sizes. Different devices allow for different interactions, and it’s easy to get in a bit of a tangle when you don’t consider that. If you’re creating hover states in CSS, you can use the hover media feature to test whether the user’s primary input mechanism can hover over elements.
@media (hover: hover) { /* CSS hover state here */ }