Tag: Animations

Easing Animations in Canvas

The <canvas> element in HTML and Canvas API in JavaScript combine to form one of the main raster graphics and animation possibilities on the web. A common canvas use-case is programmatically generating images for websites, particularly games. That’s exactly what I’ve done in a website I built for playing Solitaire. The cards, including all their movement, is all done in canvas.

In this article, let’s look specifically at animation in canvas and techniques to make them look smoother. We’ll look specifically at easing transitions — like “ease-in” and “ease-out” — that do not come for free in canvas like they do in CSS.

Let’s start with a static canvas. I’ve drawn to the canvas a single playing card that I grabbed out of the DOM:

Let’s start with a basic animation: moving that playing card on the canvas. Even for fairly basic things like requires working from scratch in canvas, so we’ll have to start building out functions we can use.

First, we’ll create functions to help calculate X and Y coordinates:

function getX(params) {   let distance = params.xTo - params.xFrom;   let steps = params.frames;   let progress = params.frame;   return distance / steps * progress; } 
 function getY(params) {   let distance = params.yTo - params.yFrom;   let steps = params.frames;   let progress = params.frame;   return distance / steps * progress; }

This will help us update the position values as the image gets animated. Then we’ll keep re-rendering the canvas until the animation is complete. We do this by adding the following code to our addImage() method.

if (params.frame < params.frames) {   params.frame = params.frame + 1;   window.requestAnimationFrame(drawCanvas);   window.requestAnimationFrame(addImage.bind(null, params)) }

Now we have animation! We’re steadily incrementing by 1 unit each time, which we call a linear animation.

You can see how the shape moves from point A to point B in a linear fashion, maintaining the same consistent speed between points. It’s functional, but lacks realism. The start and stop is jarring.

What we want is for the object to accelerate (ease-in) and decelerate (ease-out), so it mimics what a real-world object would do when things like friction and gravity come into play.

A JavaScript easing function

We’ll achieve this with a “cubic” ease-in and ease-out transition. We’ve modified one of the equations found in Robert Penner’s Flash easing functions, to be suitable for what we want to do here.

function getEase(currentProgress, start, distance, steps) {   currentProgress /= steps/2;   if (currentProgress < 1) {     return (distance/2)*(Math.pow(currentProgress, 3)) + start;   }   currentProgress -= 2;   return distance/2*(Math.pow(currentProgress, 3)+ 2) + start; }

Inserting this into our code, which is a cubic ease, we get a much smoother result. Notice how the card speeds towards the center of the space, then slows down as it reaches the end.

Advanced easing with JavaScript

We can get a slower acceleration with either a quadratic or sinusoidal ease.

function getQuadraticEase(currentProgress, start, distance, steps) {   currentProgress /= steps/2;   if (currentProgress <= 1) {     return (distance/2)*currentProgress*currentProgress + start;   }   currentProgress--;   return -1*(distance/2) * (currentProgress*(currentProgress-2) - 1) + start; }

function sineEaseInOut(currentProgress, start, distance, steps) {   return -distance/2 * (Math.cos(Math.PI*currentProgress/steps) - 1) + start; };

For a faster acceleration, go with a quintic or exponential ease:

function getQuinticEase(currentProgress, start, distance, steps) {   currentProgress /= steps/2;   if (currentProgress < 1) {     return (distance/2)*(Math.pow(currentProgress, 5)) + start;   }   currentProgress -= 2;   return distance/2*(Math.pow(currentProgress, 5) + 2) + start; }

function expEaseInOut(currentProgress, start, distance, steps) {   currentProgress /= steps/2;   if (currentProgress < 1) return distance/2 * Math.pow( 2, 10 * (currentProgress - 1) ) + start;  currentProgress--;   return distance/2 * ( -Math.pow( 2, -10 * currentProgress) + 2 ) + start; };

More sophisticated animations with GSAP

Rolling your own easing functions can be fun, but what if you want more power and flexibility? You could continue writing custom code, or you could consider a more powerful library. Let’s turn to the GreenSock Animation Platform (GSAP) for that.

Animation becomes a lot easier to implement with GSAP. Take this example, where the card bounces at the end. Note that the GSAP library is included in the demo.

The key function is moveCard:

function moveCard() {   gsap.to(position, {     duration: 2,     ease: "bounce.out",     x: position.xMax,      y: position.yMax,      onUpdate: function() {       draw();     },     onComplete: function() {       position.x = position.origX;       position.y = position.origY;     }   }); }

The gsap.to method is where all the magic happens. During the two-second duration, the position object is updated and, with every update, onUpdate is called triggering the canvas to be redrawn.

And we’re not just talking about bounces. There are tons of different easing options to choose from.

Putting it all together

Still unsure about which animation style and method you should be using in canvas when it comes to easing? Here’s a Pen showing different easing animations, including what’s offered in GSAP.

Check out my Solitaire card game  to see a live demo of the non-GSAP animations. In this case, I’ve added animations so that the cards in the game ease-out and ease-in when they move between piles.

In addition to creating motions, easing functions can be applied to any other attribute that has a from and to state, like changes in opacity, rotations, and scaling. I hope you’ll find many ways to use easing functions to make your application or game look smoother.

The post Easing Animations in Canvas appeared first on CSS-Tricks.

CSS-Tricks

, ,

Everything You Need to Know About FLIP Animations in React

With a very recent Safari update, Web Animations API (WAAPI) is now supported without a flag in all modern browsers (except IE).  Here’s a handy Pen where you can check which features your browser supports. The WAAPI is a nice way to do animation (that needs to be done in JavaScript) because it’s native — meaning it requires no additional libraries to work. If you’re completely new to WAAPI, here’s a very good introduction by Dan Wilson.

One of the most efficient approaches to animation is FLIP. FLIP requires a bit of JavaScript to do its thing. 

Let’s take a look at the intersection of using the WAAPI, FLIP, and integrating all that into React. But we’ll start without React first, then get to that.

FLIP and WAAPI

FLIP animations are made much easier by the WAAPI!

Quick refresher on FLIP: The big idea is that you position the element where you want it to end up first. Next, apply transforms to move it to the starting position. Then unapply those transforms. 

Animating transforms is super efficient, thus FLIP is super efficient. Before WAAPI, we had to directly manipulate element’s styles to set transforms and wait for the next frame to unset/invert it:

// FLIP Before the WAAPI el.style.transform = `translateY(200px)`; 
 requestAnimationFrame(() => {   el.style.transform = ''; });

A lot of libraries are built upon this approach.  However, there are several problems with this:

  • Everything feels like a huge hack.
  • It is extremely difficult to reverse the FLIP animation. While CSS transforms are reversed “for free” once a class is removed, this is not the case here. Starting a new FLIP while a previous one is running can cause glitches. Reversing requires parsing a transform matrix with getComputedStyles and using it to calculate the current dimensions before setting a new animation.
  • Advanced animations are close to impossible. For example, to prevent distorting a scaled parent’s children, we need to have access to current scale value each frame. This can only be done by parsing the transform matrix.
  • There’s lots of browser gotchas. For example, sometimes getting a FLIP animation to work flawlessly in Firefox requires calling requestAnimationFrame twice:
requestAnimationFrame(() => {   requestAnimationFrame(() => {     el.style.transform = '';   }); });

We get none of these problems when WAAPI is used. Reversing can be painlessly done with the reverse function.The counter-scaling of children is also possible. And when there is a bug, it is easy to pinpoint the exact culprit since we’re only working with simple functions, like animate and reverse, rather than combing through things like the requestAnimationFrame approach. 

Here’s the outline of the WAAPI version:

el.classList.toggle('someclass'); const keyframes = /* Calculate the size/position diff */; el.animate(keyframes, 2000);

FLIP and React

To understand how FLIP animations work in React, it is important to know how and, most importantly, why they work in plain JavaScript. Recall the anatomy of a FLIP animation:

Diagram. Cache current site and position, make a style change, get new size and position, calculate the difference, set transforms, and cancel transforms. Each item has a purple background, except the last one, indicating they happen before paint.

Everything that has a purple background needs to happen before the “paint” step of rendering. Otherwise, we would see a flash of new styles for a moment which is not good. Things get a little bit more complicated in React since all DOM updates are done for us.

The magic of FLIP animations is that an element is transformed before the browser has a chance to paint. So how do we know the “before paint” moment in React?

Meet the useLayoutEffect hook. If you even wondered what is for… this is it! Anything we pass in this callback happens synchronously after DOM updates but before paint. In other words, this is a great place to set up a FLIP!

Let us do something the FLIP technique is very good for: animating the DOM position. There’s nothing CSS can do if we want to animate how an element moves from one DOM position to another. (Imagine completing a task in a to-do list and moving it to the list of “completed” tasks like when you click on items in the Pen below.)

Let’s look at the simplest example. Clicking on any of the two squares in the following Pen makes them swap positions. Without FLIP, it would happen instantly.

There’s a lot going on there. Notice how all work happens inside lifecycle hook callbacks: useEffect and useLayoutEffect. What makes it a little bit confusing is that the timeline of our FLIP animation is not obvious from code alone since it happens across two React renders. Here’s the anatomy of a React FLIP animation to show the different order of operations:

Diagram. Cache the size and position, retrieve previous size and position from cache, get new size and position, calculate the difference, and play the animation.

Although useEffect always runs after useLayoutEffect and after browser paint, it is important that we cache the element’s position and size after the first render. We won’t get a chance to do it on second render because useLayoutEffect is run after all DOM updates. But the procedure is essentially the same as with vanilla FLIP animations.

Caveats

Like most things, there are some caveats to consider when working with FLIP in React.

Keep it under 100ms

A FLIP animation is calculation. Calculation takes time and before you can show that smooth 60fps transform you need to do quite some work. People won’t notice a delay if it is under 100ms, so make sure everything is below that. The Performance tab in DevTools is a good place to check that.

Unnecessary renders

We can’t use useState for caching size, positions and animation objects because every setState will cause an unnecessary render and slow down the app. It can even cause bugs in the worst of cases. Try using useRef instead and think of it as an object that can be mutated without rendering anything.

Layout thrashing

Avoid repeatedly triggering browser layout. In the context of FLIP animations, that means avoid looping through elements and reading their position with getBoundingClientRect, then immediately animating them with animate. Batch “reads” and “writes” whenever possible. This will allow for extremely smooth animations.

Animation canceling

Try randomly clicking on the squares in the earlier demo while they move, then again after they stop. You will see glitches. In real life, users will interact with elements while they move, so it’s worth making sure they are canceled, paused, and updated smoothly. 

However, not all animations can be reversed with reverse. Sometimes, we want them to stop and then move to a new position (like when randomly shuffling a list of elements). In this case, we need to:

  • obtain a size/position of a moving element
  • finish the current animation
  • calculate the new size and position differences
  • start a new animation

In React, this can be harder than it seems. I wasted a lot of time struggling with it. The current animation object must be cached. A good way to do it is to create a Map so to get the animation by an ID. Then, we need to obtain the size and position of the moving element. There are two ways to do it:

  1. Use a function component: Simply loop through every animated element right in the body of the function and cache the current positions.
  2. Use a class component: Use the getSnapshotBeforeUpdate lifecycle method.

In fact, official React docs recommend using getSnapshotBeforeUpdate “because there may be delays between the “render” phase lifecycles (like render) and “commit” phase lifecycles (like getSnapshotBeforeUpdate and componentDidUpdate).” However, there is no hook counterpart of this method yet. I found that using the body of the function component is fine enough.

Don’t fight the browser

I’ve said it before, but avoid fighting the browser and try to make things happen the way the browser would do it. If we need to animate a simple size change, then consider whether CSS would suffice (e.g.  transform: scale()) . I’ve found that FLIP animations are used best where browsers really can’t help:

  • Animating DOM position change (as we did above)
  • Sharing layout animations

The second is a more complicated version of the first. There are two DOM elements that act and look as one changing its position (while another is unmounted/hidden). This tricks enables some cool animations. For example, this animation is made with a library I built called react-easy-flip that uses this approach:

Libraries

There are quite a few libraries that make FLIP animations in React easier and abstract the boilerplate. Ones that are currently maintained actively include: react-flip-toolkit and mine, react-easy-flip.

If you do not mind something heavier but capable of more general animations, check out framer-motion. It also does cool shared layout animations! There is a video digging into that library.


Resources and references

The post Everything You Need to Know About FLIP Animations in React appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

A New Way to Delay Keyframes Animations

If you’ve ever wanted to add a pause between each iteration of your CSS @keyframes animation, you’ve probably been frustrated to find there’s no built-in way to do it in CSS. Sure, we can delay the start of a set of @keyframes with animation-delay, but there’s no way to add time between the first iteration through the keyframes and each subsequent run. 

This came up when I wanted to adapt this shooting stars animation for use as the background of the homepage banner in a space-themed employee portal. I wanted to use fewer stars to reduce distraction from the main content, keep CPUs from melting, and still have the shooting stars seem random.

No pausing

For comparisons sake.

The “original” delay method

Here’s an example of where I applied the traditional keyframes delay technique to my fork of the shooting stars.

This approach involves figuring out how long we want the delay between iterations to be, and then compressing the keyframes to a fraction of 100%. Then, we maintain the final state of the animation until it reaches 100% to achieve the pause.

@keyframes my-animation {   /* Animation happens between 0% and 50% */   0% {     width: 0;   }   15% {     width: 100px;   }   /* Animation is paused/delayed between 50% and 100% */   50%, 100% {     width: 0;   } }

I experienced the main drawback of this approach: each keyframe has to be manually tweaked, which is mildly painful and certainly prone to error. It’s also harder to understand what the animation is doing if it requires mentally transposing all the keyframes back up to 100%.

New technique: hide during the delay

Another technique is to create a new set of @keyframes that is responsible for hiding the animation during the delay. Then, apply that with the original animation, at the same time.

.target-of-animation {   animation: my-awesome-beboop 1s, pause-between-iterations 4s; }  @keyframes my-awesome-beboop {   ... }  @keyframes pause-between-iterations {   /* Other animation is visible for 25% of the time */   0% {     opacity: 1;   }   25% {     opacity: 1;   }   /* Other animation is hidden for 75% of the time */   25.1% {     opacity: 0;	   }   100% {     opacity: 0;   } } 

A limitation of this technique is that the pause between animations must be an integer multiple of the “paused” keyframes. That’s because keyframes that repeat infinitely will immediately execute again, even if there are longer running keyframes being applied to the same element.

Interesting aside: When I started this article, I mistakenly thought that an easing function is applied at 0% and ends at 100%.. Turns out that the easing function is applied to each CSS property, starting at the first keyframe where a value is defined and ending at the next keyframe where a value is defined (e.g., an easing curve would be applied from 25% to 75%, if the keyframes were 25% { left: 0 } 75% { left: 50px}). In retrospect, this totally makes sense because it would be hard to adjust your animation if it was a subset of the total easing curve, but my mind is slightly blown.

In the my-awesome-beboop keyframes example above, my-awesome-beboop will run three times behind the scenes during the pause-between-animations keyframes before being revealed for what appears to be its second loop to the user (which is really the fifth time it’s been executed).  

Here’s an example that uses this to add a delay between the shooting stars:

 

Can’t hide your animation during the delay?

If you need to keep your animation on screen during the delay, there is another option besides hiding. You can still use a second set of @keyframes, but animate a CSS property in a way that counteracts or nullifies the motion of the primary animation. For example, if your main animation uses translateX, you can animate left or margin-left in your set of delay @keyframes.

Here’s a couple of examples:

Pause by changing transform-origin:

Pause by counter-acting transform: translateX by animating the left property:

In the case of the pausing the translateX animation, you’ll need to get fancier with the @keyframes if you need to pause the animation for more than just a single iteration:

/* pausing the animation for three iterations */ @keyframes slide-left-pause {   25%, 50%, 75% {     left: 0;   }   37.5%, 62.5%, 87.5% {     left: -100px;   }   100% {     left: 0;   } }

You may get some slight jitter during the pause. In the translateX example above, there’s some minor vibration on the ball during the slide-left-pause as the animations fight each other for dominance.

Wrap up

The best option performance-wise is to hide the element during the delay or animate transform. Animating properties like left, margin, width are much more intense on a processor than animating opacity (although the contain property appears to be changing that).

If you have any insights or comments on this idea, let me know!


Thanks to Yusuke Nakaya for the original shooting stars CSS animation that I forked on CodePen.

The post A New Way to Delay Keyframes Animations appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Let’s Make One of Those Fancy Scrolling Animations Used on Apple Product Pages

Apple is well-known for the sleek animations on their product pages. For example, as you scroll down the page products may slide into view, MacBooks fold open and iPhones spin, all while showing off the hardware, demonstrating the software and telling interactive stories of how the products are used.

Just check out this video of the mobile web experience for the iPad Pro:

Source: Twitter

A lot of the effects that you see there aren’t created in just HTML and CSS. What then, you ask? Well, it can be a little hard to figure out. Even using the browser’s DevTools won’t always reveal the answer, as it often can’t see past a <canvas> element.

Let’s take an in-depth look at one of these effects to see how it’s made so you can recreate some of these magical effects in our own projects. Specifically, let’s replicate the AirPods Pro product page and the shifting light effect in the hero image.

The basic concept

The idea is to create an animation just like a sequence of images in rapid succession. You know, like a flip book! No complex WebGL scenes or advanced JavaScript libraries are needed.

By synchronizing each frame to the user’s scroll position, we can play the animation as the user scrolls down (or back up) the page.

Start with the markup and styles

The HTML and CSS for this effect is very easy as the magic happens inside the <canvas> element which we control with JavaScript by giving it an ID.

In CSS, we’ll give our document a height of 100vh and make our <body> 5⨉ taller than that to give ourselves the necessary scroll length to make this work. We’ll also match the background color of the document with the background color of our images.

The last thing we’ll do is position the <canvas>, center it, and limit the max-width and height so it does not exceed the dimensions of the viewport.

html {   height: 100vh; } 
 body {   background: #000;   height: 500vh; } 
 canvas {   position: fixed;   left: 50%;   top: 50%;   max-height: 100vh;   max-width: 100vw;   transform: translate(-50%, -50%); }

Right now, we are able to scroll down the page (even though the content does not exceed the viewport height) and our <canvas> stays at the top of the viewport. That’s all the HTML and CSS we need.

Let’s move on to loading the images.

Fetching the correct images

Since we’ll be working with an image sequence (again, like a flip book), we’ll assume the file names are numbered sequentially in ascending order (i.e. 0001.jpg, 0002.jpg, 0003.jpg, etc.) in the same directory.

We’ll write a function that returns the file path with the number of the image file we want, based off of the user’s scroll position.

const currentFrame = index => (   `https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/$ {index.toString().padStart(4, '0')}.jpg` )

Since the image number is an integer, we’ll need to turn it in to a string and use padStart(4, '0') to prepend zeros in front of our index until we reach four digits to match our file names. So, for example, passing 1 into this function will return 0001.

That gives us a way to handle image paths. Here’s the first image in the sequence drawn on the <canvas> element:

As you can see, the first image is on the page. At this point, it’s just a static file. What we want is to update it based on the user’s scroll position. And we don’t merely want to load one image file and then swap it out by loading another image file. We want to draw the images on the <canvas> and update the drawing with the next image in the sequence (but we’ll get to that in just a bit).

We already made the function to generate the image filepath based on the number we pass into it so what we need to do now is track the user’s scroll position and determine the corresponding image frame for that scroll position.

Connecting images to the user’s scroll progress

To know which number we need to pass (and thus which image to load) in the sequence, we need to calculate the user’s scroll progress. We’ll make an event listener to track that and handle some math to calculate which image to load.

We need to know:

  • Where scrolling starts and ends
  • The user’s scroll progress (i.e. a percentage of how far the user is down the page)
  • The image that corresponds to the user’s scroll progress

We’ll use scrollTop to get the vertical scroll position of the element, which in our case happens to be the top of the document. That will serve as the starting point value. We’ll get the end (or maximum) value by subtracting the window height from the document scroll height. From there, we’ll divide the scrollTop value by the maximum value the user can scroll down, which gives us the user’s scroll progress.

Then we need to turn that scroll progress into an index number that corresponds with the image numbering sequence for us to return the correct image for that position. We can do this by multiplying the progress number by the number of frames (images) we have. We’ll use Math.floor() to round that number down and wrap it in Math.min() with our maximum frame count so it never exceeds the total number of frames.

window.addEventListener('scroll', () => {     const scrollTop = html.scrollTop;   const maxScrollTop = html.scrollHeight - window.innerHeight;   const scrollFraction = scrollTop / maxScrollTop;   const frameIndex = Math.min(     frameCount - 1,     Math.floor(scrollFraction * frameCount)   ); });

Updating <canvas> with the correct image

We now know which image we need to draw as the user’s scroll progress changes. This is where the magic of  <canvas> comes into play. <canvas> has many cool features for building everything from games and animations to design mockup generators and everything in between!

One of those features is a method called requestAnimationFrame that works with the browser to update <canvas> in a way we couldn’t do if we were working with straight image files instead. This is why I went with a <canvas> approach instead of, say, an <img> element or a <div> with a background image.

requestAnimationFrame will match the browser refresh rate and enable hardware acceleration by using WebGL to render it using the device’s video card or integrated graphics. In other words, we’ll get super smooth transitions between frames — no image flashes!

Let’s call this function in our scroll event listener to swap images as the user scrolls up or down the page. requestAnimationFrame takes a callback argument, so we’ll pass a function that will update the image source and draw the new image on the <canvas>:

requestAnimationFrame(() => updateImage(frameIndex + 1))

We’re bumping up the frameIndex by 1 because, while the image sequence starts at 0001.jpg, our scroll progress calculation starts actually starts at 0. This ensures that the two values are always aligned.

The callback function we pass to update the image looks like this:

const updateImage = index => {   img.src = currentFrame(index);   context.drawImage(img, 0, 0); }

We pass the frameIndex into the function. That sets the image source with the next image in the sequence, which is drawn on our <canvas> element.

Even better with image preloading

We’re technically done at this point. But, come on, we can do better! For example, scrolling quickly results in a little lag between image frames. That’s because every new image sends off a new network request, requiring a new download.

We should try preloading the images new network requests. That way, each frame is already downloaded, making the transitions that much faster, and the animation that much smoother!

All we’ve gotta do is loop through the entire sequence of images and load ‘em up:

const frameCount = 148; 
 const preloadImages = () => {   for (let i = 1; i < frameCount; i++) {     const img = new Image();     img.src = currentFrame(i);   } }; 
 preloadImages();

Demo!

A quick note on performance

While this effect is pretty slick, it’s also a lot of images. 148 to be exact.

No matter much we optimize the images, or how speedy the CDN is that serves them, loading hundreds of images will always result in a bloated page. Let’s say we have multiple instances of this on the same page. We might get performance stats like this:

1,609 requests, 55.8 megabytes transferred, 57.5 megabytes resources, load time of 30.45 seconds.

That might be fine for a high-speed internet connection without tight data caps, but we can’t say the same for users without such luxuries. It’s a tricky balance to strike, but we have to be mindful of everyone’s experience — and how our decisions affect them.

A few things we can do to help strike that balance include:

  • Loading a single fallback image instead of the entire image sequence
  • Creating sequences that use smaller image files for certain devices
  • Allowing the user to enable the sequence, perhaps with a button that starts and stops the sequence

Apple employs the first option. If you load the AirPods Pro page on a mobile device connected to a slow 3G connection and, hey, the performance stats start to look a whole lot better:

8 out of 111 requests, 347 kilobytes of 2.6 megabytes transferred, 1.4 megabytes of 4.5 megabytes resources, load time of one minute and one second.

Yeah, it’s still a heavy page. But it’s a lot lighter than what we’d get without any performance considerations at all. That’s how Apple is able to get get so many complex sequences onto a single page.


Further reading

If you are interested in how these image sequences are generated, a good place to start is the Lottie library by AirBnB. The docs take you through the basics of generating animations with After Effects while providing an easy way to include them in projects.

The post Let’s Make One of Those Fancy Scrolling Animations Used on Apple Product Pages appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

Pseudo-elements in the Web Animations API

To use the Web Animations API (e.g. el.animate()) you need a reference to a DOM element to target. So, how do you use it on pseudo-elements, which don’t really offer a direct reference? Dan Wilson covers a (newish?) part of the API itself:

const logo = document.getElementById('logo');  logo.animate({ opacity: [0, 1] }, {   duration: 100,   pseudoElement: '::after' });

I noticed in Dan’s article that ::marker is supported. I was just playing with that recently while doing our List Style Recipes page. I figured I’d give it a spin by testing the WAAPI and @keyframes on both a ::marker and and ::after element:

At first, I confused myself because it seemed like the WAAPI wasn’t working on ::after, but Dan reminded me that when using a transform, the element can’t be display: inline. Instead, I made it inline-block and it worked fine. However, I did uncover that @keyframes don’t seem to work on ::marker elements in Firefox — hopefully they’ll fix that (and we get Chrome and Safari support for ::marker ASAP).

Direct Link to ArticlePermalink

The post Pseudo-elements in the Web Animations API appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Performant Expandable Animations: Building Keyframes on the Fly

Animations have come a long way, continuously providing developers with better tools. CSS Animations, in particular, have defined the ground floor to solve the majority of uses cases. However, there are some animations that require a little bit more work.

You probably know that animations should run on the composite layer. (I won’t extend myself here, but if you want to know more, check this article.) That means animating transform or opacity properties that don’t trigger layout or paint layers. Animating properties like height and width is a big no-no, as they trigger those layers, which force the browser to recalculate styles.

On top of that, even when animating transform properties, if you want to truly hit 60 FPS animations, you probably should get a little help from JavaScript, using the FLIP technique for extra smoother animations! 

However, the problem of using transform for expandable animations is that the scale function isn’t exactly the same as animating width/height properties. It creates a skewed effect on the content, as all elements get stretched (when scaling up) or squeezed (when scaling down).

So, because of that, my go-to solution has been (and probably still is, for reasons I will detail later), technique #3 from Brandon Smith’s article. This still has a transition on height, but uses Javascript to calculate the content size, and force a transition using requestAnimationFrame. At OutSystems, we actually used this to build the animation for the OutSystems UI Accordion Pattern.

Generating keyframes with JavaScript

Recently, I stumbled on another great article from Paul Lewis, that details a new solution for expanding and collapsing animations, which motivated me to write this article and spread this technique around.

Using his words, the main idea consists of generating dynamic keyframes, stepping…

[…] from 0 to 100 and calculate what scale values would be needed for the element and its contents. These can then be boiled down to a string, which can be injected into the page as a style element. 

To achieve this, there are three main steps.

Step 1: Calculate the start and end states

We need to calculate the correct scale value for both states. That means we use getBoundingClientRect() on the element that will serve as a proxy for the start state, and divide it with the value from the end state. It should be something like this:

function calculateStartScale () {   const start= startElement.getBoundingClientRect();   const end= endElement.getBoundingClientRect();   return {     x: start.width / end.width,     y: start.height / end.height   }; }

Step 2: Generate the Keyframes

Now, we need to run a for loop, using the number of frames needed as the length. (It shouldn’t really be less than 60 to ensure a smooth animation.) Then, in each iteration, we calculate the correct easing value, using an ease function:

function ease (v, pow=4) {   return 1 - Math.pow(1 - v, pow); }  let easedStep = ease(i / frame);

With that value, we’ll get the scale of the element on the current step, using the following math:

const xScale = x + (1 - x) * easedStep; const yScale = y + (1 - y) * easedStep;

And then we add the step to the animation string:

animation += `$ {step}% {   transform: scale($ {xScale}, $ {yScale}); }`;

To avoid the content to get stretched/ skewed, we should perform a counter animation on it, using the inverted values:

const invXScale = 1 / xScale; const invYScale = 1 / yScale;  inverseAnimation += `$ {step}% {   transform: scale($ {invXScale}, $ {invYScale}); }`;

Finally, we can return the completed animations, or directly inject them in a newly created style tag.

Step 3: Enable the CSS animations 

On the CSS side of things, we need to enable the animations on the correct elements:

.element--expanded {   animation-name: animation;   animation-duration: 300ms;   animation-timing-function: step-end; }  .element-contents--expanded {   animation-name: inverseAnimation ;   animation-duration: 300ms;   animation-timing-function: step-end; }

You can check the example of a Menu from Paul Lewis article, on Codepen (courtesy of Chris Coyer):

Building an expandable section 

After grasping these baseline concepts, I wanted to check if I could apply this technique to a different use case, like a expandable section.

We only need to animate the height in this case, specifically on the function to calculate scales. We’re getting the Y value from the section title, to serve as the collapsed state, and the whole section to represent the expanded state:

    _calculateScales () {       var collapsed = this._sectionItemTitle.getBoundingClientRect();       var expanded = this._section.getBoundingClientRect();              // create css variable with collapsed height, to apply on the wrapper       this._sectionWrapper.style.setProperty('--title-height', collapsed.height + 'px');        this._collapsed = {         y: collapsed.height / expanded.height       }     }

Since we want the expanded section to have absolute positioning (in order to avoid it taking space when in a collapsed state), we are setting the CSS variable for it with the collapsed height, applied on the wrapper. That will be the only element with relative positioning.

Next comes the function to create the keyframes: _createEaseAnimations(). This doesn’t differ much from what was explained above. For this use case, we actually need to create four animations:

  1. The animation to expand the wrapper
  2. The counter-expand animation on the content
  3. The animation to collapse the wrapper
  4. The counter-collapse animation on the content

We follow the same approach as before, running a for loop with a length of 60 (to get a smooth 60 FPS animation), and create a keyframe percentage, based on the eased step. Then, we push it to the final animations strings:

outerAnimation.push(`   $ {percentage}% {     transform: scaleY($ {yScale});   }`);    innerAnimation.push(`   $ {percentage}% {     transform: scaleY($ {invScaleY});   }`);

We start by creating a style tag to hold the finished animations. As this is built as a constructor, to be able to easily add multiple patterns, we want to have all these generated animations on the same stylesheet. So, first, we validate if the element exists. If not, we create it and add a meaningful class name. Otherwise, you would end up with a stylesheet for each section expandable, which is not ideal.

 var sectionEase = document.querySelector('.section-animations');  if (!sectionEase) {   sectionEase = document.createElement('style');   sectionEase.classList.add('section-animations');  }

Speaking of that, you may already be wondering, “Hmm, if we have multiple expandable sections, wouldn’t they still be using the same-named animation, with possibly wrong values for their content?” 

You’re absolutely right! So, to prevent that, we are also generating dynamic animation names. Cool, right?

We make use of the index passed to the constructor from the for loop when making the querySelectorAll('.section') to add a unique element to the name:

var sectionExpandAnimationName = "sectionExpandAnimation" + index; var sectionExpandContentsAnimationName = "sectionExpandContentsAnimation" + index;

Then we use this name to set a CSS variable on the current expandable section. As this variable is only in this scope, we just need to set the animation to the new variable in the CSS, and each pattern will get its respective animation-name value.

.section.is--expanded {   animation-name: var(--sectionExpandAnimation); }  .is--expanded .section-item {   animation-name: var(--sectionExpandContentsAnimation); }  .section.is--collapsed {   animation-name: var(--sectionCollapseAnimation); }  .is--collapsed .section-item {   animation-name: var(--sectionCollapseContentsAnimation); }

The rest of the script is related to adding event listeners, functions to toggle the collapse/expand status and some accessibility improvements.

About the HTML and CSS: it needs a little bit of extra work to make the expandable functionality work. We need an extra wrapper to be the relative element that doesn’t animate. The expandable children have an absolute position so that they don’t occupy space when collapsed.

Remember, since we need to make counter animations, we make it scale full size in order to avoid a skew effect on the content.

.section-item-wrapper {   min-height: var(--title-height);   position: relative; }  .section {   animation-duration: 300ms;   animation-timing-function: step-end;   contain: content;   left: 0;   position: absolute;   top: 0;   transform-origin: top left;   will-change: transform; }  .section-item {   animation-duration: 300ms;   animation-timing-function: step-end;   contain: content;   transform-origin: top left;   will-change: transform;   }

I would like to highlight the importance of the animation-timing-functionproperty. It should be set to linear or step-end to avoid easing between each keyframe.

The will-change property — as you probably know — will enable GPU acceleration for the transform animation for an even smoother experience. And using the contains property, with a value of contents, will help the browser treat the element independently from the rest of the DOM tree, limiting the area before it recalculates the layout, style, paint and size properties.

We use visibility and opacity to hide the content, and stop screen readers to access it, when collapsed.

.section-item-content {   opacity: 1;   transition: opacity 500ms ease; }  .is--collapsed .section-item-content {   opacity: 0;   visibility: hidden; }

And finally, we have our section expandable! Here’s the complete code and demo for you to check:

Performance check

Anytime we work with animations, performance ought to be in the back of our mind. So, let’s use developer tools to check if all this work was worthy, performance-wise. Using the Performance tab (I’m using Chrome DevTools), we can analyze the FPS and the CPU usage, during the animations.

And the results are great!

The higher the green bar, the higher the frames. And there’s no junk either, which would be signed by red sections.

Using the FPS meter tool to check the values at greater detail, we can see that it constantly hits the 60 FPS mark, even with abusive usage.

Final considerations

So, what’s the verdict? Does this replace all other methods? Is this the “Holy Grail” solution?

In my opinion, no. 

But… that’s OK, really! It’s another solution on the list. And, as is true with any other method, it should be analyzed if it’s the best approach for the use-case.

This technique definitely has its merits. As Paul Lewis says, this does take a lot of work to prepare. But, on the flip side, we only need to do it once, when the page loads. During interactions, we are merely toggling classes (and attributes in some cases, for accessibility).

However, this brings some limitations for the UI of the elements. As you could see for the expandable section element, the counter-scale makes it much more reliable for absolute and off-canvas elements, like floating-actions or menus. It’s also difficult to styled borders because it’s using overflow: hidden.

Nevertheless, I think there’s tons of potential with this approach. Let me know what you think!

The post Performant Expandable Animations: Building Keyframes on the Fly appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Playing With Particles Using the Web Animations API

When it comes to motion and animations, there is probably nothing I love more than particles. This is why every time I explore new technologies I always end up creating demos with as many particles as I can.

In this post, we’ll make even more particle magic using the Web Animations API to create a firework effect when clicking on a button.

Browser support

At the time I’m writing this article, all major browsers — with the exception of Safari and Internet Explorer — at least partially support the Web Animations API. Safari support can be enabled in the “Experimental Features” developer menu.

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

Chrome Firefox IE Edge Safari
83 76 No 80 TP

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
80 68 80 13.3

If you’re interested in reproducing the Twitter heart animation you could also have a look at this cool article by Ana Tudor which is another great example of exploding particles on a button.

HTML setup

We won’t need much HTML for this demo. We will use a <button> element but it could be another type of tag element. We could even listen to any click on the page to make particles pop from anywhere if we really wanted to.

<button id="button">Click on me</button>

CSS setup

Since every particle has a few CSS properties in common, we can set them in the global CSS of the page. As you can create custom tag elements in HTML, I will use a <particle> tag name to avoid using semantic tags. But truth is, you could animate <p>, <i> or any tag of your choice.

particle {   border-radius: 50%;   left: 0;   pointer-events: none;   position: fixed;   top: 0; }

A couple thing to note here:

  • The particles should not interact with the layout of our page, so we’re setting a fixed position with top and left at 0px each.
  • We’re also removing pointer events to avoid any user interaction on the HTML particles while they are on the screen.

Because styling the button and the page layout is not really the purpose of this article I will leave that on the side.

JavaScript setup

Here are the six steps we will follow in our JavaScript:

  1. Listen to click event on the button
  2. Create 30 <particle> elements and append them into the <body>
  3. Set a random width, height and background for every particle
  4. Animate each particle from the mouse position to a random place as they fade out
  5. Remove the <particle> from the DOM when the animation is complete

Step 1: The click event

// We first check if the browser supports the Web Animations API if (document.body.animate) {   // If yes, we add a click listener on our button   document.querySelector('#button').addEventListener('click', pop); }

Step 2: The particles

// The pop() function is called on every click function pop(e) {    // Loop to generate 30 particles at once   for (let i = 0; i < 30; i++) {     // We pass the mouse coordinates to the createParticle() function     createParticle(e.clientX, e.clientY);   } } function createParticle(x, y) {   // Create a custom particle element   const particle = document.createElement('particle');   // Append the element into the body   document.body.appendChild(particle); }

Step 3: Particle width, height and background

function createParticle (x, y) {   // [...]   // Calculate a random size from 5px to 25px   const size = Math.floor(Math.random() * 20 + 5);   // Apply the size on each particle   particle.style.width = `$ {size}px`;   particle.style.height = `$ {size}px`;   // Generate a random color in a blue/purple palette   particle.style.background = `hsl($ {Math.random() * 90 + 180}, 70%, 60%)`; }

Step 4: Animate each particle

function createParticle (x, y) {   // [...]   // Generate a random x & y destination within a distance of 75px from the mouse   const destinationX = x + (Math.random() - 0.5) * 2 * 75;   const destinationY = y + (Math.random() - 0.5) * 2 * 75;    // Store the animation in a variable because we will need it later   const animation = particle.animate([     {       // Set the origin position of the particle       // We offset the particle with half its size to center it around the mouse       transform: `translate($ {x - (size / 2)}px, $ {y - (size / 2)}px)`,       opacity: 1     },     {       // We define the final coordinates as the second keyframe       transform: `translate($ {destinationX}px, $ {destinationY}px)`,       opacity: 0     }   ], {     // Set a random duration from 500 to 1500ms     duration: 500 + Math.random() * 1000,     easing: 'cubic-bezier(0, .9, .57, 1)',     // Delay every particle with a random value from 0ms to 200ms     delay: Math.random() * 200   }); }

Because we have a random delay, the particles waiting to start their animation are visible on the top-left of the screen. To prevent this, we can set a zero opacity on every particle in our global CSS.

particle {   /* Same as before */   opacity: 0; }

Step 5: Remove particles after the animation completes

It is important to remove the particle elements from the DOM. Since we create 30 new elements on every click, the browser memory can fill up pretty quickly and cause things to get janky. Here’s how we can do that:

function createParticle (x, y) {   // Same as before   // When the animation is finished, remove the element from the DOM   animation.onfinish = () => {     particle.remove();   }; }

Final result

Putting everything together gives us what we’re looking for: a colorful explosion of particle goodness.

Not seeing the animation in the demo? Check if your browser supports the Web Animations API. in the support table at the top of the post.

Be creative!

Because all this is using CSS, it’s pretty simple to modify the particle styles. Here are five examples using various shapes… and even characters!


Or hey, we can even explode the button itself like Zach Saucier did in this post.

A button with a gradient exploding into particles

The post Playing With Particles Using the Web Animations API appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Use and Reuse Everything in SVG… Even Animations!

If you are familiar with SVG and CSS animations and started to work with them often, here are some ideas you might want to keep in mind before jumping into the job. This article will be about learning how to build and optimize your code with <use> element, CSS Variables and CSS animations.

Live Demo

Part 1: The SVG <use> element

If you are a developer that likes to keep your code DRY or a big fan of Sass/CSS variables, there is a good chance that you will like this tag.

Let’s say you have an element that is repeated many times in your graphic. Instead of having a complex part of your code repeated many times in your SVG, you can define this part once and then clone it somewhere else in your document with the <use> element. This will not only reduce an enormous amount of code, but also will make your markup simpler and easier to manipulate.

To start implementing the <use> element, go to your SVG and follow this steps:

  1. Identify the part of the code that you want to clone
  2. Add an ID to that part
  3. Link it inside your <use> tag like this: <use xlink:href="#id"/>

That’s it! Your new clone is ready, now you can change its attributes (e.g. x and y position) to fit your needs.

Let’s dive into a very convenient example

I want to share this real case where I needed to animate a big cube made of little cube units. (Imagine the classic Rubik’s Cube.)

We’ll start by drawing the cube unit in SVG using basic shapes and transforms:

<svg viewBox="-130 -20 300 100">   <g id="cube">     <rect width="21" height="24" transform="skewY(30)"/>     <rect width="21" height="24" transform="skewY(-30) translate(21 24.3)"/>     <rect width="21" height="21"  transform="scale(1.41,.81) rotate(45) translate(0 -21)"/>   </g> </svg>

Note that the shapes are grouped in a <g> element so we can add the ID to the whole figure.

Next, let’s build a bigger cube cloning this unit. First, we need to wrap the cube from the previous example inside the <defs> tag inside the SVG. In the <defs> element we can put whatever we want to reuse, which could be a single shape, a group, a gradient.. almost any SVG element. They won’t render anywhere unless we use them outside this tag.

Then we can link the unit as many times as we want using its ID and change the x and y position on every clone like this:

<use xlink:href="#cube" x="142" y="124"/> <use xlink:href="#cube" x="100" y="124"/> <!-- ... -->

Now we have to position every cube remembering that the last element will appear at the front, after that we’ll have our first big cube ready!

xlink:href is deprecated since SVG2, but is better to use it for compatibility purposes. In modern browsers you can just use href but I tested it on Safari and at the time of writing is not working there. If you use xlink:href make sure you include this namespace in your SVG tag: xmlns:xlink="http://www.w3.org/1999/xlink" (you won’t need it if you decide to use href).

Part 2: Using CSS variables to apply different styles to your reused graphic

I chose a main color for the cube, which is a lighter and a darker shade for the sides and a stroke color. But what if we want to make a second cube a different color?

We can replace the fills and strokes with CSS variables to make these attributes more flexible. That way, we’ll be able to reuse the same cube unit with another palette (instead of defining a second unit with different colors for a second cube).

Why not add a class to the new cube and change the fill color with CSS? We’ll do that, but first, try to inspect a <use> element. You’ll notice it renders in the Shadow DOM. which means it is not vulnerable to scripts and styles, like elements in the normal DOM. Whatever values you define in the figure inside <defs> will be inherited by all its instances and you won’t be able to rewrite those with CSS. But if you replace those values with variables, then you’ll be able to control them in CSS.

In our cube unit, we’ll go through each side and replace the fill and stroke values with semantic variable names.

For example, this:

<rect fill="#00affa" stroke="#0079ad" />

…can be replaced with this:

<rect fill="var(--mainColor)" stroke="var(--strokeColor)" />

From here, we must duplicate the SVG to build a second cube. However, we don’t need to duplicate <defs> if we are keeping both in the same document. We can add a class to each SVG and control the color palette through CSS, redefining the values of the variable.

Let’s create a palette for the blue cube and another one for the pink cube:

.blue-cube {   --mainColor: #009CDE;   --strokeColor: #0079ad;   --lightColor: #00affa;   --darkColor: #008bc7; }  .pink-cube {   --mainColor: #de0063;   --strokeColor: #ad004e;   --lightColor: #fa0070;   --darkColor: #c7005a; }

This way, we can add as many cubes as we want and change all colors from one place.

Part 3: Reusing animations

The idea for this instance is to break the cubes on hover — something like an exploded view so some pieces will move away from the center when we place the cursor over the cubes.

Let’s start by defining two movements, one for each axis: move Y and move X. By dividing the animations in movements, we’ll be able to reuse them in every cube. The animations will consist of moving the cube from its initial position to 30px or 50px away in one direction. We can use a transform translate (X or Y ) to achieve that. For example:

@keyframes moveX {   to { transform: translateX(-35px);  } }

But if we want to be able to reuse this animation, it’s better to replace the numeric value with a variable, like this:

@keyframes moveX {   to { transform: translateX(var(--translate, 35px)); } }

If the variable is not defined, the default value will be 35px.

Now we need at least one class to bind to the animation. In this case, though, we need two classes to move cubes in the x-axis: .m-left and .m-right.

.m-left, .m-right {    animation: 2s moveX alternate infinite;  }

For the cube to move left, we need a negative value, but we can also declare a different number. We can define our variable like this inside the .m-left class:

.m-left { --translate: -50px; }

What’s happening here is we’re declaring that, when we add the class .m-left to one element, this will play the animation moveX (the one defined in the @keyframes) which will last two seconds to translate in the x-axis and reach a new position that is -50px left. Then, the animation alternates directions so that it moves from the last position and take two more seconds to go to its original state. And so on, because it’s an infinite loop.

We can declare another variable to the .m-right class but if we don’t, remember that it will take the 35px we declared at the beginning.

The default animation-play-state value is running but maybe we don’t want the cubes to move all the time. It would be very distracting and annoying to use on a site with some nearby content. So, let’s try to play the animation only on hover by adding this:

svg:hover .m-left {   animation: 2s moveX alternate infinite; }

You can try it by yourself and will find that the animation is jumping super fast to the initial state every time we place the cursor out of the cube. To avoid it, we can add the value paused at the end of the animation shorthand:

.m-left {   animation: 2s moveX alternate infinite paused; }

Now the animation is paused but will be running on hover by adding this line of CSS:

svg:hover * {    animation-play-state: running;  }

We can apply each class to different elements in the SVG. In the first blue cube, we are moving single cubes; in the second one, we’re applying those classes to groups of cubes.

One last thing…

It wasn’t until later that I realized I could reuse a single unit to build them all. I worked on the small cube to make it isometric enough so it could align easily with the other ones next to it. At this point, my unit was a <path>, but I decided to replace it with SVG shapes to reduce the code and get cleaner markup.

I learned that it is better to take some time to analyze what can be done with SVG before drawing every single shape and dealing with a huge amount of code. It might take more time at the beginning, but will save you a lot of time and effort in the long run.

The post Use and Reuse Everything in SVG… Even Animations! appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Stop Animations During Window Resizing

Say you have page that has a bunch of transitions and animations on all sorts of elements. Some of them get triggered when the window is resized because they have to do with size of the page or position or padding or something. It doesn’t really matter what it is, the fact that the transition or animation runs may contribute to a feeling of jankiness as you resize the window. If those transitions or animations don’t deliver any benefit in those scenarios, you can turn them off!

The trick is to apply a class that universally shuts off all the transitions and animations:

let resizeTimer; window.addEventListener("resize", () => {   document.body.classList.add("resize-animation-stopper");   clearTimeout(resizeTimer);   resizeTimer = setTimeout(() => {     document.body.classList.remove("resize-animation-stopper");   }, 400); });

Now we have a resize-animation-stopper class on the <body> that can force disable any transition or animation while the window is being resized, and goes away after the timeout clears.

.resize-animation-stopper * {   animation: none !important;   transition: none !important; }

There is probably some more performant way of doing this than setTimeout, but that’s the concept. I use this right here on this very site (v17) after noticing some significant resizing jank. It hasn’t entirely eliminated the jank but it’s noticeably better.

Here’s an example:

See the Pen
Turn off animation on resize?
by Chris Coyier (@chriscoyier)
on CodePen.

That demo is mostly just for the working code. There probably isn’t enough going on transitions-wise to notice much resize jank.

The post Stop Animations During Window Resizing appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

A Course About CSS Layout and Animations

Christina Gorton just released a new course called CSS Layout and Animations as a part of Design+Code, which is a $ 9/month. That includes a ton of video training on everything from stuff like this to React to Sketch to iOS development… and beyond!

Christina approaches the course with my favorite way to learn this stuff: by starting from a lovely design and then pulling it off with code.

That’s Figma as the design tool, which is another tool I love.

Of course, what I really love is that:

  • The course is full of CSS trickery and modern HTML & CSS features, like using flexbox and grid in practical ways.
  • She uses CodePen to prototype everything — the perfect place to get started with a project like this, in my humble opinion.

Direct Link to ArticlePermalink

The post A Course About CSS Layout and Animations appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]