Tag: Animations

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

, , , , , , , ,

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]

Creating Animations Using React Spring

Have you ever needed animation in your React application? Traditionally, implementing animation has not an easy feat to accomplish. But now, thanks to Paul Henschel, we there’s a new React tool just for that. react-spring inherits from animated and react-motion for interpolations, optimized performance, and a clean API.

In this tutorial, we will be looking at two of the five hooks included in react-spring, specifically useSpring and useTrail. The examples we’ll implement make use of both APIs.

If you want to follow along, install react-spring to kick things off:

## yarn yarn add react-spring  ## npm npm install react-spring --save

Spring

The Spring prop can be used for moving data from one state to another. We are provided with a from and to prop to help us define the animation’s starting and ending states. The from prop determines the initial state of the data during render, while we use to in stating where it should to be after the animation completes.

In the first example, we will make use of the render prop version of creating spring animation.

See the Pen
react spring 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

On initial render, we want to hide a box, and slide it down to the center of the page when a button is clicked. It’s possible to do this without making use of react-spring, of course, but we want to animate the entrance of the box in to view and only when the button is clicked.

class App extends React.Component {   state = {     content: false   }    displayContent = (e) => {     e.preventDefault()     this.setState({ content: !this.state.content })   }    render() {     return (       <div className="container">           // The button that toggles the animation           <div className="button-container">             <button               onClick={this.displayContent}               className="button">                 Toggle Content             </button>           </div>           {             !this.state.content ?               (                 // Content in the main container                 <div>                   No Content                 </div>               )             : (               // We call Spring and define the from and to props               <Spring                 from={{                   // Start invisible and offscreen                   opacity: 0, marginTop: -1000,                 }}                 to={{                   // End fully visible and in the middle of the screen                   opacity: 1, marginTop: 0,                 }}               >                 { props => (                   // The actual box that slides down                   <div  className="box" style={ props }>                     <h1>                       This content slid down. Thanks to React Spring                     </h1>                   </div>               )}             </Spring>             )         }       </div>     )   } }

Most of the code is basic React that you might already be used to seeing. We make use of react-spring in the section where we want to conditionally render the content after the value of content has been changed to true. In this example, we want the content to slide in from the top to the center of the page, so we make use of marginTop and set it to a value of -1000 to position it offscreen, then define an opacity of 0 as our values for the from prop. This means the box will initially come from the top of the page and be invisible.

Clicking the button after the component renders updates the state of the component and causes the content to slide down from the top of the page.

We can also implement the above example using the hooks API. For this, we’ll be making use of the useSpring and animated hooks, alongside React’s built-in hooks.

const App = () => {   const [contentStatus, displayContent] = React.useState(false);   // Here's our useSpring Hook to define start and end states   const contentProps = useSpring({     opacity: contentStatus ? 1 : 0,     marginTop: contentStatus ? 0 : -1000   })   return (     <div className="container">       <div className="button-container">         <button           onClick={() => displayContent(a => !a)}           className="button">Toggle Content</button>       </div>         {           !contentStatus ?             (               <div>                 No Content               </div>             )           : (             // Here's where the animated hook comes into play             <animated.div className="box" style={ contentProps }>               <h1>                 This content slid down. Thanks to React Spring               </h1>             </animated.div>           )         }     </div>   ) }

First, we set up the state for the component. Then we make use of useSpring to set up the animations we need. When contentStatus is true, we want the values of marginTop and opacity to be 0 and 1, respectively. Else, they should be -1000 and 0. These values are assigned to contentProps which we then pass as props to animated.div.

When the value of contentStatus changes, as a result of clicking the button, the values of opacity and marginTop changes alongside. This cause the content to slide down.

See the Pen
react spring 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Trail

The Trail prop animates a list of items. The animation is applied to the first item, then the siblings follow suit. To see how that works out, we’ll build a component that makes a GET request to fetch a list of users, then we will animate how they render. Like we did with Spring, we’ll see how to do this using both the render props and hooks API separately.

First, the render props.

class App extends React.Component {   state = {     isLoading: true,     users: [],     error: null   };      // We're using the Fetch <abbr>API</abbr> to grab user data   // https://css-tricks.com/using-data-in-react-with-the-fetch-api-and-axios/   fetchUsers() {     fetch(`https://jsonplaceholder.typicode.com/users`)       .then(response => response.json())       .then(data =>         // More on setState: https://css-tricks.com/understanding-react-setstate/         this.setState({           users: data,           isLoading: false,         })       )       .catch(error => this.setState({ error, isLoading: false }));   }    componentDidMount() {     this.fetchUsers();   }    render() {     const { isLoading, users, error } = this.state;     return (       <div>         <h1>Random User</h1>         {error ? <p>{error.message}</p> : null}         {!isLoading ? (           // Let's define the items, keys and states using Trail           <Trail             items={users}             keys={user => user.id}             from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}             to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }}           >           {user => props => (           <div style={props} className="box">             {user.username}           </div>         )}       </Trail>         ) : (           <h3>Loading...</h3>         )}       </div>     );   } }

When the component mounts, we make a request to fetch some random users from a third-party API service. Then, we update this.state.users using the data the API returns. We could list the users without animation, and that will look like this:

users.map(user => {   const { username, name, email } = user;   return (     <div key={username}>       <p>{username}</p>     </div>   ); })

But since we want to animate the list, we have to pass the items as props to the Trail component:

<Trail   items={users}   keys={user => user.id}   from={{ marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' }}   to={{ marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }} >   {user => props => (     <div style={props} className="box">       {user.username}     </div>   )} </Trail>

We set the keys to the ID of each user. You can see we are also making use of the from and to props to determine where the animation should start and end.

Now our list of users slides in with a subtle animation:

See the Pen
React Spring – Trail 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

The hooks API gives us access to useTrail hook. Since we are not making use of a class component, we can make use of the useEffect hook (which is similar to componentDidMount and componentDidUpdate lifecycle methods) to fetch the users when the component mounts.

const App = () => {   const [users, setUsers] = useState([]);      useEffect(() => {     fetch(`https://jsonplaceholder.typicode.com/users`)       .then(response => response.json())       .then(data =>         setUsers(data)       )   }, [])      const trail = useTrail(users.length, {     from: { marginLeft: -20, opacity: 0, transform: 'translate3d(0,-40px,0)' },     to: { marginLeft: 20, opacity: 1, transform: 'translate3d(0,0px,0)' }   })    return (     <React.Fragment>       <h1>Random User</h1>       {trail.map((props, index) => {         return (           <animated.div             key={users[index]}             style={props}             className="box"           >             {users[index].username}           </animated.div>         )       })}     </React.Fragment>   ); }

We have the initial state of users set to an empty array. Using useEffect, we fetch the users from the API and set a new state using the setUsers method we created with help from the useState hook.

Using the useTrail hook, we create the animated style passing it values for from and to, and we also pass in the length of the items we want to animate. In the part where we want to render the list of users, we return the array containing the animated props.

See the Pen
React Spring -Trail 2
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Go, spring into action!

Now you have a new and relatively easy way to work with animations in React. Try animating different aspects of your application where you see the need. Of course, be mindful of user preferences when it comes to animations because they can be detrimental to accessibility.

While you’re at it, ensure you check out the official website of react-spring because there are tons of demo to get your creative juices flowing with animation ideas.

The post Creating Animations Using React Spring appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Scroll-Linked Animations

You scroll down to a certain point, now you want to style things in a certain way. A header becomes fixed. An animation triggers. A table of contents appears. To do anything based on scroll position, JavaScript is required right now. You watch the scroll position via a DOM event and alter an element’s styling based on that position. Or, probably better if you can, use IntersectionObserver. We just blogged about all this.

Now there is a new (unofficial) spec trying to bring these possibilities to CSS. I love it when web standards get involved because it sees authors like us trying to pull off certain design effects and wants to (presumably) help make it easier and more performant. I also like how this spec lists editors from Mozilla and Google and Apple.

I wonder how they’ll handle the infinite-loop stuff here. Like you scroll to a point, it triggers some animation, which moves some element such that it changes the scroll position, which stops the animation, which moves the scroll position again… etc. I also wonder why it’s all specific to animation. “Scroll-position styling” seems like it would have the widest appeal and use level of usefulness.

Direct Link to ArticlePermalink

The post Scroll-Linked Animations appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Writing Animations That Bring Your Site to Life

Web animation is one of the factors that can strongly enhance your website’s look and feel. Sadly, unlike mobile apps, there aren’t as many websites using animation to their benefit as you would think. We don’t want to count yours among those, so this article is for you and anyone else looking for ways to use animation for a better user experience! Specifically, we’re going to learn how to make web interactions delightful using CSS animations.

Here’s what we’re going to build together:

Live Demo GitHub Repo

Before we move ahead, it’s worth mentioning that I’m going to assume you have at least some familiarity with modern front-end frameworks and a basic understanding of CSS animations. If you don’t, then no fear! CSS-Tricks has a great guides on React and Vue, as well as a thorough almanac post on the CSS animation property.

Good? OK, let’s talk about why we’d want to use animation in the first place and cover some baseline information about CSS animations.

Why would we to animate anything?

We could probably do an entire post on this topic alone. Oh, wait! Sarah Drasner already did that and her points are both poignant and compelling.

But, to sum things up based on my own experiences:

  • Animations enhance the way users interact with an interface. For example, smart animations can reduce cognitive load by giving users better context between page transitions.
  • They can provide clear cues to users, like where we want them to focus attention.
  • Animations serve as another design pattern in and of themselves, helping users to get emotionally attached to and engage with the interface.
  • Another benefit of using animations is that they can create a perception that a site or app loads faster than it actually does.

A couple of house rules with animations

Have you ever bumped into a site that animates all the things? Wow, those can be jarring. So, here’s a couple of things to avoid when working with animations so our app doesn’t fall into the same boat:

  • Avoid animating CSS properties other than transform and opacity. If other properties have to be animated, like width or height, then make sure there aren’t a lot of layout changes happening at the same time. There’s actually a cost to animations and you can see exactly how much by referring to CSS Triggers.
  • Also, just because animations can create perceived performance gains, there’s actually a point of diminishing return when it comes to using them. Animating too many elements at the same time may result in decreased performance.

Now we can get our hands dirty with some code!

Let’s build a music app

We’re going to build the music app we looked at earlier, which is inspired by Aurélien Salomon’s Dribbble shot. I chose this example so that we can focus on animations, not only within a component, but also between different routes. We’ll build this app using Vue and create animations using vanilla (i.e. no framework) CSS.

Animations should go hand-in-hand with UI development. Creating UI before defining their movement is likely to cost much more time. In this case, the Dribbble shot provides that scope for us.

Let’s start with the development.

Step 1: Spin up the app locally

First things first. We need to set up a new Vue project. Again, we’re assuming some base-level understanding of Vue here, so please check out the Learning Vue guide for more info on setting up.

We need a couple of dependencies for our work, notably vue-router for transitioning between views and sass-loader so we can write in Sass and compile to CSS. Here’s a detailed tutorial on using routes and Sass can be installed by pointing the command line at the project directory and using npm install -D sass-loader node-sass.

We have what we need!

Step 2: Setting up routes

For creating routes, we’re first going to create two bare minimum components — Artists.vue and Tracks.vue. We’ll drop a new file in the src folder called router.js and add routes for these components as:

import Vue from 'vue' import Router from 'vue-router' import Artists from './components/Artists.vue' import Tracks from './components/Tracks.vue'  Vue.use(Router) export default new Router({ 	mode: 'history', 	routes: [ 		{ 			path: '/', 			name: 'artists', 			component: Artists 		}, 		{ 			path: '/:id', 			name: 'tracks', 			component: Tracks 		} 	] })

Import router.js into the main.js and inject it to the Vue instance. Lastly, replace the content of your App.vue by <router-view/>.

Step 3: Create the components and content for the music app

We need two components that we’ll transition between with animation. Those are going to be:

  1. Artists.vue: a grid of artists
  2. Tracks.vue: An artist image with a back button

If you wanna jump ahead a bit, here are some assets to work with:

  1. Images and sample data in JSON format.
  2. Content for the components

When all is said and done, the two views will come out to something like this:

Artists.vue (left) and Tracks.vue (right)

Step 4: Animate!

Here we are, the part we’ve really wanted to get to all this time. The most important animation in the app is transitioning from Artists to Tracks when clicking on an artist. It should feel seamless where clicking on an artist image puts that image in focus while transitioning from one view into the next. This is exactly the type of animation that we rarely see in apps but can drastically reduce cognitive load for users.

To make sure we’re all on the same page, we’re going to refer to the first image in the sequence as the “previous” image and the second one as the “current” image. Getting the effect down is relatively easy as long as we know the dimensions and position of the previous image in the transition. We can animate the current image by transforming it as per previous image.

The formula that I’m using is transform: translate(x, y) scale(n), where n is equal to the size of previous image divided by the size of current image. Note that we can use a static value of n since the dimensions are fixed for all the images. For example, the image size in the Artists view is 190x190 and 240x240 in the Tracks view. Thus, we can replace n by 190/240 = 0.791. That means the transform value becomes translate(x, y) scale(0.791) in our equation.

Animating from Artists to Tracks

Next thing is to find x and y. We can get these values though click event in the Artists view as:

const {x, y} = event.target.getBoundingClientRect()

…and then send these values to the Tracks view, all while switching the route. Since we aren’t using any state management library, the two components will communicate via their parent component, which is the top level component, App.vue. In App.vue, let’s create a method that switches the route and sends the image info as params.

gotoTracks(position, artistId) { 	this.$ router.push({ 		name: 'tracks', 		params: { 			id: artistId, 			position: position 		} 	}) }

Here’s the relevant code from the repo to reference, in case you’re interested.

Since we have received the position and ID of the image in Tracks, we have all the required data to show and animate it. We’ll first fetch artist information (specifically the name and image URL) using artist ID.

To animate the image, we need to calculate the transform value from the image’s starting position. To set the transform value, I’m using CSS custom properties, which can be done with CSS-in-JS techniques as well. Note that the image’s position that we received through props will be relative to window. Therefore we’ll have to subtract some fixed offset caused by the padding of the container <div> to even out our math.

const { x, y } = this.$ route.params.position // padding-left const offsetLeft = 100 // padding-top const offsetTop = 30  // Set CSS custom property value document.documentElement.style.setProperty( 	'--translate',  	`translate($ {x - offsetLeft}px, $ {y - offsetTop}px) scale(0.792)` )

We’ll use this value to create a keyframe animation to move the image:

@keyframes move-image { 	from { 		transform: var(--translate); 	} }

This gets assigned to the CSS animation:

.image { 	animation: move-image 0.6s; }

…and it will animate the image from this transform value to its original position on component load.

Transitioning from Artists to Tracks

We can use the same technique when going the opposite direction, Tracks to Artists. As we already have the clicked image’s position stored in the parent component, we can pass it to props for Artists as well.

Transitioning from Tracks to Artists

Step 5: Show the tracks!

It’s great that we can now move between our two views seamlessly, but the Tracks view is pretty sparse at the moment. So let’s add the track list for the selected artist.

We’ll create an empty white box and a new keyframe to slide it upwards on page load. Then we’ll add three subsections to it: Recent Tracks, Popular Tracks, and Playlist. Again, if you want to jump ahead, feel free to either reference or copy the final code from the repo.

The Tracks view with content

Recent Tracks is the row of thumbnails just below the artist image where each thumbnail includes the track name and track length below it. Since we’re covering animations here, we’ll create a scale-up animation, where the image starts invisible (opacity: 0) and a little smaller than it’s natural size (scale(0.7)), then is revealed (opacity: 1 )and scales up to its natural size (transform: none).

.track { 	opacity: 0; 	transform: scale(0.7); 	animation: scale-up 1s ease forwards; }  @keyframes scale-up { 	to { 		opacity: 1; 		transform: none; 	} }

The Popular Tracks list and Playlist sit side-by-side below the Recent Tracks, where Popular tracks takes up most of the space. We can slide them up a bit on initial view with another set of keyframes:

.track { 	... 	animation: slide-up 1.5s; }  @keyframes slide-up { 	from { 		transform: translateY(140px); 	} }

To make the animation feel more natural, we’ll create a stagger effect so the Recent Tracks lead a little ahead of the Popular Tracks and Playlist using an incremental animation delay to each item.

@for $ i from 1 to 5 { 	&:nth-child(#{$ i + 1}) { 		animation-delay: #{$ i * 0.05}s; 	} }

The code above is basically looking for each child element, then adding a 0.05 second delay to each element it finds. So, for example, the first child gets a 0.05 second delay, the second child gets a 0.10 second delay and so on.

Check out how nice and natural this all looks:

Bonus: micro-interactions!

One of the fun things about working with animations is thinking through the small details because they’re what tie things together and add delight to the user experience. We call these micro-interactions and they serve a good purpose by providing visual feedback when an action is performed.

Depending on the complexity of the animations, we might need a library like anime.js or GSAP. This example is pretty straightforward, so we can accomplish everything we need by writing some CSS.

First micro-interaction: The volume icon

Let’s first get a volume icon in SVG format (Noun Project and Material Design are good sources). On click, we’ll animate-in and out its path element to show the level of volume. For this, we’ll create a method which switches its CSS class according to the volume level.

<svg @click="changeVolume"> 	<g :class="`level-$ {volumeLevel}`"> 		<path d="..."/> <!-- volume level 1 --> 		<path d="..."/> <!-- volume level 2 --> 		<path d="..."/> <!-- volume level 3 --> 		<polygon points="..."/> 	</g> </svg>

Based on this class, we can show and hide certain path elements as:

path { 	opacity: 0; 	transform-origin: left; 	transform: translateX(-5px) scale(0.6); 	transition: transform 0.25s, opacity 0.2s; }  .level-1 path:first-child, .level-2 path:first-child, .level-2 path:nth-child(2), .level-3 path { 	opacity: 1; 	transform: none; }
The animated volume control

Second micro-interaction: The favorite icon

Do you like it when you click on Twitter’s heart button? That’s because it feels unique and special by the way it animates on click. We’ll make something similar but real quick. For this, we first get an SVG heart icon and add it to the the markup. Then we’ll add a bouncy animation to it that’s triggered on click.

@keyframes bounce { 	0%, 100% { 		transform: none; 	} 	30% { 		transform: scale(1.3); 	} 	60% { 		transform: scale(0.9); 	} }

Another fun thing we can do is add other small heart icons around it with random sizes and positions. Ideally, we’d add a few absolute-positioned HTML elements that a heart as the background. Let’s Arrange each of them as below by setting their left and bottom values.

We’ll also include a fade away effect so the icons appear to dissolve as they move upward by adding a keyframe animation on the same click event.

@keyframes float-upwards { 	0%, 100% { 		opacity: 0; 	} 	50% { 		opacity: 0.7; 	} 	50%, 100% { 		transform: translate(-1px, -5px); 	} }
The animated favorite button

Summing up

That’s all! I hope you find all this motivating to try animations on your own websites and projects.

While writing this, I also wanted to expand on the fundamental animation principles we glossed over earlier because I believe that they help choose animation durations, and avoid non-meaningful animations. That’s important to discuss because doing animations correctly is better than doing them at all. But this sounds like a whole another topic to be covered in a future article.

The post Writing Animations That Bring Your Site to Life appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]