Tips for Writing Animation Code Efficiently | CSS-Tricks
Tips for Writing Animation Code Efficiently
Zach Saucier on Apr 10, 2020 (Updated on Jul 29, 2020)
Learn Development at Frontend Masters
I’ve been coding web animations and helping others do the same for years now. However, I have yet to see a concise list of tips focused on how to efficiently build animations, so here you go!
I will be using the GreenSock Animation Platform (GSAP). It provides a simple, readable API and solves cross-browser inconsistencies so that you can focus on animating. The code and concepts should be understandable even if you’ve never used GSAP. If you’d like to familiarize yourself with the basics of GSAP first so that you can get the most out of this article, the best place to begin is GSAP’s getting started page (includes a video).
Tip #1: Use an animation library
Some developers think that using an animation library is wasteful because they can just use native browser technologies like CSS transitions, CSS animations or the Web Animations API (WAAPI) to accomplish the same thing without loading a library. In some cases, that’s true. However, here are a few other factors to consider:
- Browser bugs, inconsistencies, and compatibility: An animation library, like GSAP, solves these for you and is universally compatible. You can even use motion paths in IE9! There are many problematic areas when it comes to cross-browser issues, including handling transform-origin on SVG elements, path stroke measurements, 3D origins in Safari, and many more that we don’t have the space to list.
- Animation workflow: Building even moderately complex animations is much faster and more fun with a tool like GSAP. You can modularize animations, nest them as deeply as you want, and have their timing adjusted automatically. This makes it so much easier to experiment. Trust me: once you try building an animation sequence in CSS and then in GSAP, you’ll see what I mean. Night and day! Future edits are faster too.
- Animate beyond the DOM: Canvas, WebGL, generic objects, and complex strings can’t be animated with native technologies. Using one consistent tool for all your animations is much cleaner.
- Runtime control: Using a good animation library can enable you to pause, resume, reverse, seek through, or even gradually change the speed of an entire animation sequence. You can control each transform component independently (rotation, scale, x, y, skew, etc.). You can also retrieve those values at any time as well. JavaScript animations give you ultimate flexibility.
- Easing options (bounce, elastic, etc.): CSS only gives you two control points for eases. GSAP’s CustomEase lets you literally create any ease you can imagine.
- Lag smoothing: GSAP can prioritize absolute timing or adjust things on the fly to avoid jumps if the CPU gets bogged down.
- Advanced capabilities: Using GSAP, it’s easy to morph SVGs, add physics/inertia, edit motion paths directly in the browser, use position-aware staggers, and more.
Most of the top animators in the industry use a tool like GSAP because they’ve learned these same things over the years. Once you get beyond very basic animations, a JavaScript library will make your life much, much easier and open up entirely new possibilities.
Tip #2: Use timelines
A good animation library will provide some way of creating individual animations (called tweens) and a way to sequence animations in a timeline. Think of a timeline like a container for your tweens where you position them in relation to one another.
Most anytime you need your animations to run in a sequence, you should be using a timeline.
const tl = gsap.timeline();tl.to(".box", { duration: 1, x: 100 }).to(".box", { duration: 1, backgroundColor: "#f38630" }, "+=0.5") .to(".box", { duration: 1, x: 0, rotation: -360 }, "+=0.5")
By default in GSAP, tweens added to a timeline will wait for the previous tweens to complete before running. The +=0.5 adds an additional offset or delay of a half-second as well, so the second tween will start 0.5 seconds after the first tween finishes no matter how long the first tween’s duration is.
To increase the amount of time between the tween to 1 second, all you need to do is change the +=0.5 to +=1! Super easy. With this approach, you can iterate on your animations quickly without worrying about doing the math to combine previous durations.
Tip #3: Use relative values
By “relative values” I mean three things:
- Animate values relative to their current value. GSAP recognizes += and -= prefixes for this. So x: "+=200" will add 200 units (usually pixels) to the current x. And x: "-=200" will subtract 200 from the current value. This is also useful in GSAP’s position parameter when positioning tweens relative to one another.
- Use relative units (like vw, vh and, in some cases, %) when values need to be responsive to viewport size changes.
- Use methods like .to() and .from() (instead of .fromTo()) whenever possible so that the start or end values are dynamically populated from their current values. That way, you don’t need to declare start and end values in every tween. Yay, less typing! For example, if you had a bunch of differently-colored elements, you could animate them all to black like gsap.to(".class", {backgroundColor: "black" }).
Tip #4: Use keyframes
If you find yourself animating the same target over and over in a row, that’s a perfect time to use keyframes! You do so like this:
gsap.to(".box", { keyframes: [{ duration: 1, x: 100 },{ duration: 1, backgroundColor: "#f38630", delay: 0.5 }, { duration: 1, x: 0, rotation: -360, delay: 0.5 }]});
No timeline necessary! To space out the tweens we just use the delay property in each keyframe. (It can be negative to create overlaps.)
Tip #5: Use smart defaults
GSAP has default values for properties like ease ("power1.out") and duration (0.5 seconds). So, the following is a valid tween that will animate for half a second.
gsap.to(".box", { color: "black" })
To change GSAP’s global defaults, use gsap.defaults():
// Use a linear ease and a duration of 1 insteadgsap.defaults({ ease: "none", duration: 1 });
This can be handy, but it’s more common to set defaults for a particular timeline so that it affects only its children. For example, we can avoid typing duration: 1 for each of the sub-tweens by setting a default on the parent timeline:
const tl = gsap.timeline({ defaults: { duration: 1 } });tl.to(".box", { x: 100 }).to(".box", { backgroundColor: "#f38630" }, "+=0.5") .to(".box", { x: 0, rotation: -360 }, "+=0.5")
Tip #6: Animate multiple elements at once
We mentioned this briefly in the third tip, but it deserves its own tip.
If you have multiple elements that share the same class of .box, the code above will animate all of the elements at the same time!
You can also select multiple elements with different selectors by using a more complex selector string:
gsap.to(".box, .circle", { ... });
Or you can pass an array of variable references as long as the elements are of the same type (selector string, variable reference, generic object, etc.):
var box = document.querySelector(".box");var circle = document.querySelector(".circle");// some time later…gsap.to([box, circle], { ... });
Tip #7: Use function-based values, staggers, and/or loops
Function-based values
Use a function instead of a number/string for almost any property, and GSAP will call that function once for each target when it first renders the tween. Plus, it’ll use whatever gets returned by the function as the property value! This can be really handy for creating a bunch of different animations using a single tween and for adding variance.
GSAP will pass the following parameters into the function:
- The index
- The specific element being affected
- An array of all of the elements affected by the tween
For example, you could set the movement direction based on the index:
Or you could choose items from an array:
Staggers
Make your animations look more dynamic and interesting by offsetting the start times with a stagger. For simple staggered offsets in a single tween, just use stagger: 0.2 to add 0.2 seconds between the start time of each animation.
You can also pass in an object to get more complex stagger effects, including ones that emanate outward from the center of a grid or randomize the timings:
For more information about GSAP’s staggers, check out the stagger documentation.
Loops
It can be helpful to loop through a list of elements to create or apply animations, particularly when they are based on some event, like a user’s interaction (which I’ll discuss later on).
To loop through a list of items, it’s easiest to use .forEach(). But since this isn’t supported on elements selected with .querySelectorAll() in IE, you can use GSAP’s utils.toArray() function instead.
In the example below, we are looping through each container to add animations to its children that are scoped to that container.
Tip #8: Modularize your animations
Modularization is one of the key principles of programming. It allows you to build small, easy-to-understand chunks that you can combine into larger creations while still keeping things clean, reusable, and easy to modify. It also lets you to use parameters and function scope, increasing the re-usability of your code.
Functions
Use functions to return tweens or timelines and then insert those into a master timeline:
function doAnimation() {// do something, like calculations, maybe using arguments passed into the function// return a tween, maybe using the calculations abovereturn gsap.to(".myElem", { duration: 1, color: "red"});}tl.add( doAnimation() );
Nesting timelines can truly revolutionize the way you animate. It lets you sequence really complex animations with ease while keeping your code modular and readable.
function doAnimation() {const tl = gsap.timeline();tl.to(...);tl.to(...);// ...as many animations as you’d like!// When you’re all done, return the timelinereturn tl;}const master = gsap.timeline();master.add( doAnimation() );master.add( doAnotherAnimation() );// Add even more timelines!
Here’s a real-world use case modified from Carl Schooff’s “Writing Smarter Animation Code” post.
Here’s a more complex demo showing the same technique using a Star Wars theme by Craig Roblewsky:
Wrapping your animation-building routines inside functions also makes recreating animations (say, on resize) a breeze!
var tl; // keep an accessible reference to our timelinefunction buildAnimation() {var time = tl ? tl.time() : 0; // save the old time if it exists// kill off the old timeline if it existsif (tl) {tl.kill();}// create a new timelinetl = gsap.timeline();tl.to(...).to(...); // do your animationtl.time(time); // set the playhead to match where it was}buildAnimation(); //kick things offwindow.addEventListener("resize", buildAnimation); // handle resize
If you find yourself repeating the same code and switching out one variable for another that’s usually a sign you should make a general function or use a loop instead in order to keep your code DRY (Don’t Repeat Yourself).
Effects
With effects, you can turn a custom animation into a named effect that can be called anytime with new targets and configurations. This is especially helpful when you have standards for your animations or if you are going to be calling the same animation from different contexts.
Here’s a super-simple “fade” effect to show the concept:
// register the effect with GSAP:gsap.registerEffect({name: "fade",defaults: {duration: 2}, //defaults get applied to the "config" object passed to the effect beloweffect: (targets, config) => {return gsap.to(targets, {duration: config.duration, opacity:0});}});// now we can use it like this:gsap.effects.fade(".box");// Or override the defaults:gsap.effects.fade(".box", {duration: 1});
Tip #9: Use control methods
GSAP provides many methods to control the state of a tween or timeline. They include .play(), .pause(), .reverse(), .progress(), .seek(), .restart(), .timeScale(), and several others.
Using control methods can make transitions between animations more fluid (such as being able to reverse part way through) and more performant (by reusing the same tween/timeline instead of creating new instances each time). And by giving you finer control over the state of the animation, it can help with debugging as well.
Here’s a simple example:
One amazing use case is tweening the timeScale of a timeline!
Use case: interaction events that trigger animations
Inside of event listeners for user interaction events, we can use control methods to have fine control over our animation’s play state.
In the example below, we are creating a timeline for each element (so that it doesn’t fire the same animation on all instances), attaching a reference for that timeline to the element itself, and then playing the relevant timeline when the element is hovered, reversing it when the mouse leaves.
Use case: Animating between multiple states of a timeline
You may want a set of animations to affect the same properties of the same elements, but only in certain sequences (e.g. active/inactive states, each with mouseover/mouseout states). It may get tricky to manage. We can simplify it by using states of a timeline and control events.
Use case: Animating based on the scroll position
We can easily fire animations based on the scroll position by using control methods. For example, this demo plays a full animation once a scroll position has been reached:
You can also attach the progress of an animation to the scroll position for more fancy scroll effects!
But if you’re going to do this, it’s best to throttle the scroll listener for performance reasons:
However, setting this up every time you need to do an animation on scroll is a hassle. You can use GreenSock’s official scroll plugin called ScrollTrigger to do this (and much more) for you!
Bonus tip: Use GSAP’s plugins, utility methods, and helper functions
GSAP plugins add extra capabilities to GSAP’s core. Some plugins make it easier to work with rendering libraries, like PixiJS or EaselJS, while other plugins add superpowers like morphing SVG, drag and drop functionality, etc. This keeps the GSAP core relatively small and lets you add features when you need them.
Plugins
ScrollTrigger creates jaw-dropping scroll-based animations with minimal code. Or trigger anything scroll-related, even if it has nothing to do with animation.
MorphSVG morphs between any two SVG shapes, no matter the number of points, and gives you fine control over how the shapes are morphed.
DrawSVG progressively reveals (or hides) the stroke of an SVG element, making it look like it’s being drawn. It works around various browser bugs that affect typical stroke-dashoffset animations.
MotionPath animates anything (SVG, DOM, canvas, generic objects, whatever) along a motion path in any browser. You can even edit the path in-browser using MotionPathHelper!
GSDevTools gives you a visual UI for interacting with and debugging GSAP animations, complete with advanced playback controls, keyboard shortcuts, global synchronization and more.
Draggable provides a surprisingly simple way to make virtually any DOM element draggable, spinnable, tossable, or even flick-scrollable using mouse or touch events. Draggable integrates beautifully (and optionally) with InertiaPlugin so the user can flick and have the motion decelerate smoothly based on momentum.
CustomEase (along with CustomBounce and CustomWiggle) add to GSAP’s already extensive easing capabilities by enabling you to register any ease that you’d like.
SplitText is an easy to use JavaScript utility that allows you to split HTML text into characters, words and lines. It’s easy to use, extremely flexible, works all the way back to IE9, and handles special characters for you.
ScrambleText scrambles the text in a DOM element with randomized characters, refreshing new randomized characters at regular intervals, while gradually revealing your new text (or the original) over the course of the tween. Visually, it looks like a computer decoding a string of text.
Physics2D lets you tween the position of elements based on velocity and acceleration as opposed to going to specific values. PhysicsProps is similar but works with any property, not just 2D coordinates.
Utility methods
GSAP has built-in utility methods that can make some common tasks easier. Most are focused on manipulating values in a particular way, which can be especially helpful when generating or modifying animation values. The ones that I use most often are .wrap(), .random, .interpolate(), .distribute(), .pipe(), and .unitize(), but there are many others you might find helpful.
Helper functions
In a similar light, but not built into GSAP’s core, are some helper functions GreenSock has created over the years to deal with specific use cases. These functions make it easy to FLIP your animations, return a random number based on an ease curve, blend two ease curves, and much more. I highly recommend checking them out!
Conclusion
You’ve made it to the end! Hopefully, you’ve learned a thing or two along the way and this article will continue to be a resource for you in the years to come.
As always, if you have any questions about GSAP, feel free to drop by the GreenSock forums. They’re incredibly helpful and welcoming! As an employee of GreenSock, that’s where I hang out often; I love helping people with animation-related challenges!