Chase McCoy wrote a nifty post about the “gap problem” when making a grid of items. His argument might be summarized like this: how should we space elements with margins in CSS? He notes that the gap property isn’t quite ready for prime time when it comes to using it with flexbox, like this:
.grid {
display: flex;
gap: 10px;
}
Right now, using gap with flexbox is only supported in Firefox and I’ve already caught myself forgetting about that in a few projects. So watch out for that.
Make a grid with a 10px gap between each column and row.
Each column should have a minimum width (150px).
Each column should also be equal width (1fr).
The grid should auto-fill as many columns that can fit.
The nifty thing about all this is that our grid is now effectively responsive because of minmax — if you resize the browser, then the grid will snap down into fewer columns, just like this:
No media queries at all! Although sure, there’s a few other ways that you could get this to work but I think this is neat not just because we’re avoiding media queries — instead, it’s because it teaches us to think in a new way when designing and building components.
Chase continues:
With this technique, instead of using breakpoints to specify the screen size where your items should stack, you specify the minimum size an element should be before it stacks. I like this because it encourages developers to think about responsive design in terms of behaviors instead of screen sizes.
“Behaviors instead of screen sizes” is such a great way to think about component design! A lot of the problems I’ve encountered when making components for a design system is when I’ve been thinking about screen sizes — mobile, tablet, desktop, etc. — and trying to make those components fit within those constraints.
Thinking in behaviors is always more effective because there are so many things that can impact a component beyond what screen or device width we’re working with. Perhaps we want that component to fit inside another component. Or we want to align some helper text to the side of it for comparison.
Either way, thinking about behaviors instead of screen sizes isn’t really going to be fully impossible until we have container queries, as Chris writes:
Container queries are always on the top of the list of requested improvements to CSS. The general sentiment is that if we had container queries, we wouldn’t write as many global media queries based on page size. That’s because we’re actually trying to control a more scoped container, and the only reason we use media queries for that now is because it’s the best tool we have in CSS. I absolutely believe that.
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.
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:
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 instead
gsap.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:
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:
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 above
return 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 timeline
return tl;
}
const master = gsap.timeline();
master.add( doAnimation() );
master.add( doAnotherAnimation() );
// Add even more timelines!
Here’s a more complex demo showing the same technique using a Star Wars theme by Craig Roblewsky:
CodePen Embed Fallback
Wrapping your animation-building routines inside functions also makes recreating animations (say, on resize) a breeze!
var tl; // keep an accessible reference to our timeline
function buildAnimation() {
var time = tl ? tl.time() : 0; // save the old time if it exists
// kill off the old timeline if it exists
if (tl) {
tl.kill();
}
// create a new timeline
tl = gsap.timeline();
tl.to(...)
.to(...); // do your animation
tl.time(time); // set the playhead to match where it was
}
buildAnimation(); //kick things off
window.addEventListener("resize", buildAnimation); // handle resize
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 below
effect: (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!
CodePen Embed Fallback
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.
CodePen Embed Fallback
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:
Hot tip: GreenSock is working on a plugin to make scroll-based animations even easier! You’re in for quite a treat. Keep your eyes peeled for news.
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
MorphSVG morphs between any two SVG shapes, no matter the number of points, and gives you fine control over how the shapes are morphed.
CodePen Embed Fallback
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!
CodePen Embed Fallback
GSDevTools gives you a visual UI for interacting with and debugging GSAP animations, complete with advanced playback controls, keyboard shortcuts, global synchronization and more.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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!
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.
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:
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 instead
gsap.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:
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:
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 above
return 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 timeline
return tl;
}
const master = gsap.timeline();
master.add( doAnimation() );
master.add( doAnotherAnimation() );
// Add even more timelines!
Here’s a more complex demo showing the same technique using a Star Wars theme by Craig Roblewsky:
CodePen Embed Fallback
Wrapping your animation-building routines inside functions also makes recreating animations (say, on resize) a breeze!
var tl; // keep an accessible reference to our timeline
function buildAnimation() {
var time = tl ? tl.time() : 0; // save the old time if it exists
// kill off the old timeline if it exists
if (tl) {
tl.kill();
}
// create a new timeline
tl = gsap.timeline();
tl.to(...)
.to(...); // do your animation
tl.time(time); // set the playhead to match where it was
}
buildAnimation(); //kick things off
window.addEventListener("resize", buildAnimation); // handle resize
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 below
effect: (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!
CodePen Embed Fallback
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.
CodePen Embed Fallback
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:
Hot tip: GreenSock is working on a plugin to make scroll-based animations even easier! You’re in for quite a treat. Keep your eyes peeled for news.
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
MorphSVG morphs between any two SVG shapes, no matter the number of points, and gives you fine control over how the shapes are morphed.
CodePen Embed Fallback
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!
CodePen Embed Fallback
GSDevTools gives you a visual UI for interacting with and debugging GSAP animations, complete with advanced playback controls, keyboard shortcuts, global synchronization and more.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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.
CodePen Embed Fallback
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!
I just can’t stop bookmarking great links related to typography. I’m afraid I’m going to have to subject you, yet again, to a bunch of them all grouped up. So those of you that care about web type stuff, enjoy.
I know there are lots of good reasons to be excited about variable fonts. The design possibilities of endless variations in one file is chief among them. But I remain the most excited about the performance benefits. Having a single file that elegantly handles the thicker weights (for bolding) and italics is so cool. I can’t wait to wave my fist saying back in my day we had to load multiple files for our font variations!
Even if you consider the slightly larger file sizes, when combined with improved font compression formats like WOFF2, font subsetting and font loading techniques like font-display: swap; we end up in a situation where we can still get smaller overall font file sizes as well as a significant increase in stylistic opportunity.
Anna Monus did some variable font performance testing as well, evaluating the extreme case of loading 12 variations of a font against a variable font. Even though the copy of the variable font she had for Roboto was massive (over 1MB), there was a perf gain compared against loading 12 variations.
Roboto is on Google Fonts, of course, and it’s got the #1 position by far. But Google Fonts has Inter now, and I’d expect that leap up in the charts as it’s got a style that everyone seems to like and can work with just about anything.
Klint Finley on the proliferation of high-quality open-source fonts for WIRED. Sometimes they are backed by companies with thick wallets, which makes sense. But the motivation for doing it varies. Sometimes quality is the goal. Like open-source anything, lots of contributions, can, if handled well, lead to a better product. But open-source doesn’t always mean there isn’t a business possibility, and if not, not everyone cares about turning a profit on everything.
Speaking of open-source fonts, Collletttivo is an open-source font foundry with a good dozen typefaces, including a variable font one. It’s a super fun site to explore with the little fake windows you open up and move around.
There’s a new mac app called FontGoggles for poking into a font file and taking a look at what it offers. Seems like that would be easy but it really isn’t. I like that it supports WOFF/2 as that’s pretty much all we deal with on the web.
Also, remember there is a website that looks around in font files with the best name ever.
Font Match allows you to put fonts on top of each other for comparisons. Seems like it’s more for large type and comparing their features, while Font style matcher is more about comparing paragraph text.
In fact, using Font style matcher to make a perfect font fallback is one of my all-time favorite CSS-Tricks. Sssshh… don’t tell anybody but I’m compiling those all-time favorites into a book, like this chapter. You’d have to subscribe to read it, because that’s kind of the point: I’d like to sell the book. So if you happen to subscribe now, there is stuff to read there, but you’d be a very early supporter for the rest of it.
JavaScript and CSS have lived beside one another for upwards of 20 years. And yet it’s been remarkably tough to share data between them. There have been large attempts, sure. But, I have something simple and intuitive in mind — something not involving a structural change, but rather putting CSS custom properties and even Sass variables to use.
CSS custom properties and JavaScript
Custom properties shouldn’t be all that surprising here. One thing they’ve always been able to do since browsers started supporting them is work alongside JavaScript to set and manipulate the values.
Specifically, though, we can use JavaScript with custom properties in a few ways. We can set the value of a custom property using setProperty:
We can also retrieve CSS variables using getComputedStyle in JavaScript. The logic behind this is fairly simple: custom properties are part of the style, therefore, they are part of computed style.
Note that custom properties are scoped. This means we need to get computed styles from a particular element. As we previously defined our variable in :root we get them on the HTML element.
Sass variables and JavaScript
Sass is a pre-processing language, meaning it’s turned into CSS before it ever is a part of a website. For that reason, accessing them from JavaScript in the same way as CSS custom properties — which are accessible in the DOM as computed styles — is not possible.
We need to modify our build process to change this. I doubt there isn’t a huge need for this in most cases since loaders are often already part of a build process. But if that’s not the case in your project, we need three modules that are capable of importing and translating Sass modules.
The :export block is the magic sauce webpack uses to import the variables. What is nice about this approach is that we can rename the variables using camelCase syntax and choose what we expose.
Then we import the Sass file (variables.scss) file into JavaScript, giving us access to the variables defined in the file.
There are some restrictions on the :export syntax that are worth calling out:
It must be at the top level but can be anywhere in the file.
If there is more than one in a file, the keys and values are combined and exported together.
If a particular exportedKey is duplicated, the last one (in the source order) takes precedence.
An exportedValue may contain any character that’s valid in CSS declaration values (including spaces).
An exportedValue does not need to be quoted because it is already treated as a literal string.
There are lots of ways having access to Sass variables in JavaScript can come in handy. I tend to reach for this approach for sharing breakpoints. Here is my breakpoints.scs file, which I later import in JavaScript so I can use the matchMedia() method to have consistent breakpoints.
// Sass variables that define breakpoint values
$breakpoints: (
mobile: 375px,
tablet: 768px,
// etc.
);
// Sass variables for writing out media queries
$media: (
mobile: '(max-width: #{map-get($breakpoints, mobile)})',
tablet: '(max-width: #{map-get($breakpoints, tablet)})',
// etc.
);
// The export module that makes Sass variables accessible in JavaScript
:export {
breakpointMobile: unquote(map-get($media, mobile));
breakpointTablet: unquote(map-get($media, tablet));
// etc.
}
Animations are another use case. The duration of an animation is usually stored in CSS, but more complex animations need to be done with JavaScript’s help.
It makes me happy that I can exchange data between CSS, Sass and JavaScript so easily. Sharing variables like this makes code simple and DRY.
There are multiple ways to achieve the same sort of thing, of course. Les James shared an interesting approach in 2017 that allows Sass and JavaScript to interact via JSON. I may be biased, but I find the approach we covered here to be the simplest and most intuitive. It doesn’t require crazy changes to the way you already use and write CSS and JavaScript.
Are there other approaches that you might be using somewhere? Share them here in the comments — I’d love to see how you’re solving it.
I’ve written up my advice (sprinkled with great advice from others), but this is way more straightforward nuts-and-bolts training on technical writing. It’s structured like an actual course, with exercises along the way.
I’m far from an expert here. But between Geoff and I, we end up doing a lot of technical article editing for the sake of clarity.
Comedy writers seek the funniest results, horror writers strive for the scariest, and technical writers aim for the clearest. In technical writing, clarity takes precedence over all other rules.
It can be tricky to get right. Read the section on Active voice. That’s easy for anyone to get wrong.
Essentially every single rule is just an extension of “make it more clear.”
Austin Gil has kicked off the first in a five-part series about “HTML Forms Right” and to starts with semantics. It’s talking to the “we build our front-ends with JavaScript” crowd. The first block of code is an example of an Ajax form submission where the data submitted is gathered through the JavaScript API FormData.
Why is that so vital? Well, no <form> tag, no FormData. Why else use a form (aside from the Enter-key submission):
“But Austin, I’m building an SPA. Therefore if the user even sees the form, it means JavaScript MUST be enabled.” And you’d be right. Although, if it is an important form, you may want to consider supporting a no-JS world. The day may come that you want to implement SSR.
Server-Side Rendering (SSR) is going to get easier and easier to do as the benefits of it become more and more obvious. Google tells us a page that is client-side rendered has week-long-ish queue to get indexed and re-indexed on changes. Not to mention SSR is almost definitely going to be far faster to load.
Oscar Braunert’s Inclusive Inputs is a nice follow-up read as it begins with form HTML that is so close to being right, but is painfully not right. (Hint: it’s missing the label/input connection). Then he gets into interesting patterns like how to accessibly mark up required fields and fields with errors. Like:
<div class="form-group">
<label for="password">
Password
<span class="required" aria-hidden="true">*</span>
<span class="sr-only">required</span>
</label>
<input
type="password"
id="password"
name="password"
aria-describedby="desc_pw"
>
<p class="aside" id="desc_pw">Your password needs to have at least eight characters.</p>
</div>
Amber Wilson gets into Accessible HTML elements with the twist of avoiding any ARIA usage at all:
You may be aware that ARIA roles are often used with HTML elements. I haven’t written about them here, as it’s good to see how HTML written without ARIA can still be accessible.
Shout out to <dl>.
Sarah Higley does get into ARIA in Roles and relationships, but she warns us to be very careful upfront:
[…] a budding accessibility practitioner might find themselves experimenting with more serious roles like menu, listbox, or even treegrid. These are tantalizing, powerful patterns that allow you to create experiences that are not supported by only vanilla HTML. Unfortunately, they are also brittle; even small mistakes in using these roles can take a user on a very bad trip.
Talk to your kids about ARIA before it’s too late.
Ideally, don’t use ARIA at all. But if the accessibility is screwed up to the point it can’t be fixed at the DOM level, Sarah gets into some tricks. For example, one uses role="presentation" to essentially remove an element’s default role (when it is in the way).
Speaking of ARIA and not using it unless you have to, one of the things you can do with ARIA is label controls. Adrian Roselli has thoughts on how best to do that:
Here is the priority I follow when assigning an accessible name to a control:
1. Native HTML techniques 2. aria-labelledby pointing at existing visible text 3. Visibly-hidden content that is still in the page 4. aria-label
Nada Rifki demonstrates the scroll-snap-type and scroll-snap-alignCSS properties. I like that the demo shows that the items in the scrolling container can be different sizes. It is the edges of those children that matter, not some fixed snapping distance.
Here’s an outstanding idea from Max Böck. He’s created a boilerplate project for building websites that fit within a single HTTP request. This is extremely important for websites that contain critical information for public safety. As Max writes:
In cases of emergency, many organizations need a quick way to publish critical information. But exisiting (CMS) websites are often unable to handle sudden spikes in traffic.
What’s so special about this boilerplate? Well, it does smart stuff like:
provides one-click deployment via Netlify to get off the ground quickly
The example website that Max built with this boilerplate is shockingly fast and I would go one step further to argue that all websites should feel as fast as this, not just websites that are useful in an emergency.
Remember Tabletop.js? We just covered it a little bit ago in this same exact context: building editable websites. It’s a tool that turns a Google Sheet into an API, that you as a developer can hit for data when building a website. In that last article, we used that API on the client side, meaning JavaScript needed to run on every single page view, hit that URL for the data, and build the page. That might be OK in some circumstances, but let’s do it one better. Let’s hit the API during the build step so that the content is built into the HTML directly. This will be far faster and more resilient.
The situation
As a developer, you might have had to work with clients who keep bugging you with unending revisions on content, sometimes, even after months of building the site. That can be frustrating as it keeps pulling you back, preventing you from doing more productive work.
We’re going to give them the keys to updating content themselves using a tool they are probably already familiar with: Google Sheets.
A new tool
In the last article, we introduced the concept of using Google Sheets with Tabletop.js. Now let’s introduce a new tool to this party: Eleventy.
We’ll be using Eleventy (a static site generator) because we want the site to be rendered as a pure static site without having to ship all of the under workings of the site in the client side JavaScript. We’ll be pulling the content from the API at build time and having Eleventy create a minified index.html that we’ll push to the server for the production website. By being static, this allows the page to load faster and is better for security reasons.
The spreadsheet
We’ll be using a demo I built, with its repo and Google Sheet to demonstrate how to replicate something similar in your own projects. First, we’ll need a Google Sheet which will be our data store.
Open a new spreadsheet and enter your own values in the columns just like mine. The first cell of each column is the reference that’ll be used later in our JavaScript, and the second cell is the actual content that gets displayed.
In the first column, “header” is the reference name and “Please edit me!” is the actual content in the first column.
Next up, we’ll publish the data to the web by clicking on File → Publish to the web in the menu bar.
A link will be provided, but it’s technically useless to us, so we can ignore it. The important thing is that the spreadsheet(and its data) is now publicly accessible so we can fetch it for our app.
Take note that we’ll need the unique ID of the sheet from its URL as we go on.
Node is required to continue, so be sure that’s installed. If you want to cut through the process of installing all of thedependencies for this work, you can fork or download my repo and run:
npm install
Run this command next — I’ll explain why it’s important in a bit:
npm run seed
Then to run it locally:
npm run dev
Alright, let’s go into src/site/_data/prod/sheet.js. This is where we’re going to pull in data from the GoogleSheet, then turn it into an object we can easily use, and finally convert the JavaScript object back to JSON format. The JSON is stored locally for development so we don’t need to hit the API every time.
Here’s the code we want in there. Again, be sure to change the variable sheetID to the unique ID of your own sheet.
module.exports = () => {
return new Promise((resolve, reject) => {
console.log(`Requesting content from ${googleSheetUrl}`);
axios.get(googleSheetUrl)
.then(response => {
// massage the data from the Google Sheets API into
// a shape that will more convenient for us in our SSG.
var data = {
"content": []
};
response.data.feed.entry.forEach(item => {
data.content.push({
"header": item.gsx$header.$t,
"header2": item.gsx$header2.$t,
"body": item.gsx$body.$t,
"body2": item.gsx$body2.$t,
"body3": item.gsx$body3.$t,
"body4": item.gsx$body4.$t,
"body5": item.gsx$body5.$t,
"body6": item.gsx$body6.$t,
"body7": item.gsx$body7.$t,
"body8": item.gsx$body8.$t,
"body9": item.gsx$body9.$t,
"body10": item.gsx$body10.$t,
"body11": item.gsx$body11.$t,
"body12": item.gsx$body12.$t,
"body13": item.gsx$body13.$t,
"body14": item.gsx$body14.$t,
"body15": item.gsx$body15.$t,
"body16": item.gsx$body16.$t,
"body17": item.gsx$body17.$t,
})
});
// stash the data locally for developing without
// needing to hit the API each time.
seed(JSON.stringify(data), `${__dirname}/../dev/sheet.json`);
// resolve the promise and return the data
resolve(data);
})
// uh-oh. Handle any errrors we might encounter
.catch(error => {
console.log('Error :', error);
reject(error);
});
})
}
In module.exports, there’s a promise that’ll resolve our data or throw errors when necessary. You’ll notice that I’m using a axios to fetch the data from the spreadsheet. I like the it handles status error codes by rejecting the promise automatically, unlike something like Fetch that requires monitoring error codes manually.
I created a data object in there with a content array in it. Feel free to change the structure of the object, depending on what the spreadsheet looks like.
We’re using the forEach() method to loop through each spreadsheet column while equating it with the corresponding name we want to allocate to it, while pushing all of these into the data object as content.
Remember that seed command from earlier? We’re using seed to transform what’s in the data object to JSON by way of JSON.stringify, which is then sent to src/site/_data/dev/sheet.json.
Yes! Now have data in a format we can use with any templating engine, like Nunjucks, to manipulate it. But, we’re focusing on content in this project, so we’ll be using the index.md template format to communicate the data stored in the project.
For example, here’s how it looks to pull item.header through a for loop statement:
<div class="listing">
{%- for item in sheet.content -%}
<h1>{{ item.header }} </h1>
{%- endfor -%}
</div>
If you’re using Nunjucks, or any other templating engine, you’ll have to pull the data accordingly.
Finally, let’s build this out:
npm run build
Note that you’ll want a dist folder in the project where the build process can send the compiled assets.
But that’s not all! If we were to edit the Google Sheet, we won’t see anything update on our site. That’s where Zapier comes in. We can “zap” Google sheet and Netlify so that an update to the Google Sheet triggers a deployment from Netlify.
Assuming you have a Zapier account up and running, we can create the zap by granting permissions for Google and Netlify to talk to one another, then adding triggers.
The recipe we’re looking for? We’re connecting Google Sheets to Netlify so that when a “new or updated sheet row” takes place, Netlify starts a deploy. It’s truly a set-it-and-forget-it sort of deal.
Yay, there we go! We have a performant static site that takes its data from Google Sheets and deploys automatically when updates are made to the sheet.