Tag: Animate

How to Animate the Details Element

Here’s a nice simple demo from Moritz Gießmann on animating the triangle of a <details> element, which is the affordance that tells people this thing can be opened. Animating it, then is another kind of affordance that tells people this thing is opening now.

The tricks?

  1. Turn off the default triangle: details summary::-webkit-details-marker { display:none; }. You can’t animate that one.
  2. Make a replacement triangle with the CSS border trick and a pseudo element.
  3. Animate the new triangle when the state is open: details[open] > summary::before { transform: rotate(90deg); }.

This only animates the triangle. The content inside still “snaps” open. Wanna smooth things out? Louis Hoebregts’ “How to Animate the Details Element Using WAAPI” covers that.

Here’s a fork where I’ll combine them just because:

I see Moritz put the cursor: pointer; on the summary as well like Greg Gibson suggests.

The post How to Animate the Details Element appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, ,

How to Animate a SVG with border-image

Let’s take a look at how to combine the border-image property in CSS with animated SVGs that move around a border. In the process, we’ll cover how to hand-craft resizable, nine-slice animated SVGs that you can use not only re-create the effect, but to make it your own.

Here’s what we’re making:

Animated gif of red skulls moving around a list of high scores in a retro arcade font that go from first place to tenth place.
Spooky skulls? Retro arcade? What’s not to like?!

This is actually part of The Skull, a capture-the-flag riddle I’m working on that’s designed to explore the internals of Arduino and its microcontroller. I searched how to animate a border like this, but couldn’t find any useful examples. Most of the stuff I found was about marching ants, but unfortunately, the stroke-dasharray trick doesn’t work with skulls, let alone more complex shapes.

So, in the spirit of learning and sharing, I’m blogging about it here with you!

Should we use background or border-image?

At first, I didn’t even know border-image was a thing. I tried using a ::before pseudo-element in my first attempt, and animated its background-position property. That got my this far:

As you can see, it worked, but completing the border would require at least eight different elements (or pseudo-elements). Not ideal to clutter the HTML like that.

I posted a question on the Israeli CSS developers Facebook group, and everyone pointed me at the border-image property. It does exactly what it says on the tin: use an image (or CSS gradient) for an element’s border.

To work with border-image, you have to provide it an image which is used in a nine-slice way (think of a tic-tac-toe board over the image). Each of those nine regions represents a different part of the border: the top, right, left, and bottom, each of the four corners, and then the middle (which is ignored).

For instance, if we just wanted just static skulls, we could take advantage of SVG patterns to repeat the skull nine times. First, we define an 24×24 pattern using the skull’s path, and then use this pattern as the fill for a 72×72 rect:

<svg version="1.1" height="72" width="72" xmlns="http://www.w3.org/2000/svg">  <defs>   <pattern id="skull-fill" width="24" height="24"  patternUnits="userSpaceOnUse">     <path d="..." fill="red"/>   </pattern>  </defs>  <rect fill="url(#skull-fill)" width="72" height="72" /> </svg>

Next, we define a border and set the border-image on the target element:

.skulls {   border: 24px solid transparent;   border-image: url("https://skullctf.com/images/skull-9.svg") 24 round; }

And we get a border made out of skulls:

Adding SVG animations

Now we can animate those skulls! It works, uh, for the most part.

The idea is to create a different animation for each region in the border image. For instance, in the top-left corner, we have one skull going from the right-to-left, while a second skull goes from top- to-bottom at the same time.

We’ll animate the transform property for the movement. We’ll also take advantage of SVG’s <use> to avoid repeating the lengthy <path> definition for each skull:

<svg version="1.1" height="96" width="96" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">  <style>   @keyframes left {to {transform: translate(-32px, 0)}}   @keyframes down {to {transform: translate(0, 32px)}}  </style>  <defs>   <path id="skull" d="..." fill="red"/>  </defs>   <!-- Top-left corner: one skull goes left, another goes down -->  <use href="#skull" x="0" y="0"  style="animation: down .4s infinite linear"/>  <use href="#skull" x="32" y="0" style="animation: left .4s infinite linear"/> </svg>

The SVG animation syntax might look familiar there, because rather than some SVG-specific syntax, like SMIL, it’s just using CSS animations. Cool, right?

This is what we get:

And if we add a grid, we can see how this animation also covers some of the top and left edges as well:

It starts looking more impressive after we add the remaining three edges, thus completely covering all the eight regions of the border image:

<svg version="1.1" height="96" width="96" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">  <style>   @keyframes left {to {transform: translate(-32px, 0)}}   @keyframes down {to {transform: translate(0, 32px)}}   @keyframes right {to {transform: translate(32px, 0)}}   @keyframes up {to {transform: translate(0, -32px)}}  </style>  <defs>   <path id="skull" d="..." fill="red"/>  </defs>   <!-- Top-left corner: one skull goes left, another goes down -->  <use href="#skull" x="0" y="0"  style="animation: down .4s infinite linear"/>  <use href="#skull" x="32" y="0" style="animation: left .4s infinite linear"/>   <!-- Top-right corner: one skull goes up, another goes left -->  <use href="#skull" x="64" y="0" style="animation: left .4s infinite linear"/>  <use href="#skull" x="64" y="32" style="animation: up .4s infinite linear"/>   <!-- Bottom-left corner: one skull goes down, another goes right -->  <use href="#skull" x="0" y="32" style="animation: down .4s infinite linear"/>  <use href="#skull" x="0" y="64" style="animation: right .4s infinite linear"/>   <!-- Bottom-right corner: one skull goes right, another goes up -->  <use href="#skull" x="32" y="64" style="animation: right .4s infinite linear"/>  <use href="#skull" x="64" y="64" style="animation: up .4s infinite linear"/> </svg>

And this gives us a complete circuit:

Dancing skulls, ready to go into your border!

Putting everything together, we use the animated SVG we’ve just created as the border-image, and get the desired result:

I could play with this all day…

Once I got this to work, I started tinkering with the animation properties. This is one of the advantages of using SVGs instead of GIFs: changing the nature of the animation is as easy as changing one CSS property in the SVG source file, and you get to see the result instantly, not to mention the smaller file sizes (especially if you are dealing with gradients), full color support and crisp scaling.

For starters, I tried to see what it would like like if I changed the animation timing function to ease:

We can also make the skulls fade between red and green:

We can even make the skulls change orientation as they go around the high score table:

Head to the JavaScript tab where you can tinker with the SVG source and try it for yourself.

The giant 🐘 in the room (cough, Firefox)

I was very happy when I first got this to work. However, there are some caveats that you should be aware of. First and foremost, for some reason, Firefox doesn’t render the animation at the edges of the border, only at the corners:

Funny enough, if I changed the SVG to a GIF with the same animation, it worked perfectly fine. But then the edges stop animating on Chrome! 🤦‍♂️

In any case, it seems to be a browser bug, because if we change the border-image-repeat property to stretch , Firefox does animate the edges, but the result is a bit quirky (though it can probably fit the theme of the page):

Changing the border-image-repeat value to space also seems to work, but only if the width of the element is not a whole multiple of the skull size, which means we get some gaps in the animation.

I also spotted a few visual issues in cases when the container size is not a multiple of the patch size (32px in this case), such as tiny black lines on the skulls. I suspect this has to do with some floating point rounding issue. It also tends to break when zooming in.

Not perfect, but definitely done! If you want to see the final version in action, you are invited to check out The Skull’s High Scores page. Hopefully, it’ll have some of your names on it very soon!

The post How to Animate a SVG with border-image appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.



How to Animate the Details Element Using WAAPI

Animating accordions in JavaScript has been one of the most asked animations on websites. Fun fact: jQuery’s slideDown() function was already available in the first version in 2011.

In this article, we will see how you can animate the native <details> element using the Web Animations API.

HTML setup

First, let’s see how we are gonna structure the markup needed for this animation.

The <details> element needs a <summary> element. The summary is the content visible when the accordion is closed.
All the other elements within the <details> are part of the inner content of the accordion. To make it easier for us to animate that content, we are wrapping it inside a <div>.

<details>   <summary>Summary of the accordion</summary>   <div class="content">     <p>       Lorem, ipsum dolor sit amet consectetur adipisicing elit.       Modi unde, ex rem voluptates autem aliquid veniam quis temporibus repudiandae illo, nostrum, pariatur quae!       At animi modi dignissimos corrupti placeat voluptatum!     </p>   </div> </details>

Accordion class

To make our code more reusable, we should make an Accordion class. By doing this we can call new Accordion() on every <details> element on the page.

class Accordion {   // The default constructor for each accordion   constructor() {}    // Function called when user clicks on the summary   onClick() {}    // Function called to close the content with an animation   shrink() {}    // Function called to open the element after click   open() {}    // Function called to expand the content with an animation   expand() {}    // Callback when the shrink or expand animations are done   onAnimationFinish() {} }


The constructor is the place we save all the data needed per accordion.

constructor(el) {   // Store the <details> element   this.el = el;   // Store the <summary> element   this.summary = el.querySelector('summary');   // Store the <div class="content"> element   this.content = el.querySelector('.content');    // Store the animation object (so we can cancel it, if needed)   this.animation = null;   // Store if the element is closing   this.isClosing = false;   // Store if the element is expanding   this.isExpanding = false;   // Detect user clicks on the summary element   this.summary.addEventListener('click', (e) => this.onClick(e)); }


In the onClick() function, you’ll notice we are checking if the element is being animated (closing or expanding). We need to do that in case users click on the accordion while it’s being animated. In case of fast clicks, we don’t want the accordion to jump from being fully open to fully closed.

The <details> element has an attribute, [open], applied to it by the browser when we open the element. We can get the value of that attribute by checking the open property of our element using this.el.open.

onClick(e) {   // Stop default behaviour from the browser   e.preventDefault();   // Add an overflow on the <details> to avoid content overflowing   this.el.style.overflow = 'hidden';   // Check if the element is being closed or is already closed   if (this.isClosing || !this.el.open) {     this.open();   // Check if the element is being openned or is already open   } else if (this.isExpanding || this.el.open) {     this.shrink();   } }


This shrink function is using the WAAPI .animate() function. You can read more about it in the MDN docs. WAAPI is very similar to CSS @keyframes. We need to define the start and end keyframes of the animation. In this case, we only need two keyframes, the first one being the current height the element, and the second one is the height of the <details> element once it is closed. The current height is stored in the startHeight variable. The closed height is stored in the endHeight variable and is equal to the height of the <summary>.

shrink() {   // Set the element as "being closed"   this.isClosing = true;    // Store the current height of the element   const startHeight = `$  {this.el.offsetHeight}px`;   // Calculate the height of the summary   const endHeight = `$  {this.summary.offsetHeight}px`;    // If there is already an animation running   if (this.animation) {     // Cancel the current animation     this.animation.cancel();   }    // Start a WAAPI animation   this.animation = this.el.animate({     // Set the keyframes from the startHeight to endHeight     height: [startHeight, endHeight]   }, {     // If the duration is too slow or fast, you can change it here     duration: 400,     // You can also change the ease of the animation     easing: 'ease-out'   });    // When the animation is complete, call onAnimationFinish()   this.animation.onfinish = () => this.onAnimationFinish(false);   // If the animation is cancelled, isClosing variable is set to false   this.animation.oncancel = () => this.isClosing = false; }


The open function is called when we want to expand the accordion. This function does not control the animation of the accordion yet. First, we calculate the height of the <details> element and we apply this height with inline styles on it. Once it’s done, we can set the open attribute on it to make the content visible but hiding as we have an overflow: hidden and a fixed height on the element. We then wait for the next frame to call the expand function and animate the element.

open() {   // Apply a fixed height on the element   this.el.style.height = `$  {this.el.offsetHeight}px`;   // Force the [open] attribute on the details element   this.el.open = true;   // Wait for the next frame to call the expand function   window.requestAnimationFrame(() => this.expand()); }


The expand function is similar to the shrink function, but instead of animating from the current height to the close height, we animate from the element’s height to the end height. That end height is equal to the height of the summary plus the height of the inner content.

expand() {   // Set the element as "being expanding"   this.isExpanding = true;   // Get the current fixed height of the element   const startHeight = `$  {this.el.offsetHeight}px`;   // Calculate the open height of the element (summary height + content height)   const endHeight = `$  {this.summary.offsetHeight + this.content.offsetHeight}px`;    // If there is already an animation running   if (this.animation) {     // Cancel the current animation     this.animation.cancel();   }    // Start a WAAPI animation   this.animation = this.el.animate({     // Set the keyframes from the startHeight to endHeight     height: [startHeight, endHeight]   }, {     // If the duration is too slow of fast, you can change it here     duration: 400,     // You can also change the ease of the animation     easing: 'ease-out'   });   // When the animation is complete, call onAnimationFinish()   this.animation.onfinish = () => this.onAnimationFinish(true);   // If the animation is cancelled, isExpanding variable is set to false   this.animation.oncancel = () => this.isExpanding = false; }


This function is called at the end of both the shrinking or expanding animation. As you can see, there is a parameter, [open], that is set to true when the accordion is open, allowing us to set the [open] HTML attribute on the element, as it is no longer handled by the browser.

onAnimationFinish(open) {   // Set the open attribute based on the parameter   this.el.open = open;   // Clear the stored animation   this.animation = null;   // Reset isClosing & isExpanding   this.isClosing = false;   this.isExpanding = false;   // Remove the overflow hidden and the fixed height   this.el.style.height = this.el.style.overflow = ''; }

Setup the accordions

Phew, we are done with the biggest part of the code!

All that’s left is to use our Accordion class for every <details> element in the HTML. To do so, we are using a querySelectorAll on the <details> tag, and we create a new Accordion instance for each one.

document.querySelectorAll('details').forEach((el) => {   new Accordion(el); });


To make the calculations of the closed height and open height, we need to make sure that the <summary> and the content always have the same height.

For example, do not try to add a padding on the summary when it’s open because it could lead to jumps during the animation. Same goes for the inner content — it should have a fixed height and we should avoid having content that could change height during the opening animation.

Also, do not add a margin between the summary and the content as it will not be calculated for the heights keyframes. Instead, use a padding directly on the content to add some spacing.

The end

And voilà, we have a nice animated accordion in JavaScript without any library! 🌈

The post How to Animate the Details Element Using WAAPI appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , ,

How to Animate Text with SVG and CSS

The other day I was helping my pal Jez work Dept. of Enthusiasm, the site for his newsletter, and I had a thought. What if we made the word “enthusiasm” in the title animate a little bit? Like, what if each of the letters in the word bopped up and down enthusiastically?

Like this:

Neat, huh? To build this thing I knew we could use SVG for the text and then animate things with CSS. Each letter is a path with its own class, which makes it possible to select each one. That said, there’s nothing really stopping us from doing this with HTML and CSS. Using SVG is just one approach that felt right to me at the time.

To get started we headed over to Figma and typed out the text in separate text boxes. We did this so that when we click on the “Outline stroke” menu item here…

…we have individual vectors of each letter. This will help us when we export the SVG so that we can add the correct CSS classes to each element. Once we’ve outlined the strokes of each letter we can then edit the points in the vector (but we don’t need to for what we’re about to do):

If we added all the text in one box and clicked “Outline Stroke” then it would’ve created a single vector with all these letters combined. That would then make a single path with the coordinates and that’s pretty difficult for me to style or even understand what the heck is going on in there.

Next up, I put all these letters in a Frame (Sketch calls this an Artboard) and placed each word into a Group. This way, when they’re exported as an SVG, each word will be in it’s own g tag which also helps us style the letters:

From there, I exported the SVG — but! — I had to make sure to include the id option when doing it.

If we don’t do this we’ll get a bunch of path elements for each letter but they won’t have an id attributes.

This is what we get after the export:

I’m not sure how much of this weirdness is me and how much is Figma’s SVG export, but I deleted that <rect> element because it’s unecessary. Then I gave the body element a background so I could see the text and remove those inline height and width attributes on the SVG itself:

Neato! Now we can get to the fun part: animating each letter in the word.

If you look at the HTML of that example above you’ll notice there’s a g element which with an id with the same name of the Frame in Figma.There are also g elements for each word and every path that makes up the word will have an individual id. (This is why naming our Frames and Groups properly, as well as keeping things organized in any design application, is important.)

One thing that surprised me was the order in which each path is exported though: it’s in the opposite order than the one I’d expect, with M being the first letter in the “ENTHUSIASM” group. So I cleaned that up a bit and made sure each letter is in the correct order.

To get the animation working we first bump down each letter by 2px:

g path {   transform: translateY(2px); }

That’s because I want each letter to make a 2px hop which we’ll get to in a bit. I also noticed with this change I’d need to update the SVG viewbox too. Otherwise, the bottom of each letter will be cut off:

<svg class="header" viewBox="0 0 146 13" fill="none" xmlns="http://www.w3.org/2000/svg">

I probably should’ve have just repositioned the text within the frame in Figma and exported it again, but this is fine for what I needed.

Now I can target the third group in the SVG (the word “enthusiasm”) and set the animation-count to infinite:

/* targets the word "enthusiasm" */ g:nth-child(3) path {   animation-name: wiggleWiggle;   animation-duration: 2.5s;   animation-iteration-count: infinite; }

The code above then calls the wiggleWiggle animation below:

@keyframes wiggleWiggle {   20%,   100% {     transform: translate(0, 2px); /* stay on the baseline for most of the animation duration */   }    0% {     transform: translate(0, 0px); /* hop up */   }   10% {     transform: translate(0, 2px); /* return to baseline */   } }

See the beginning of that keyframe — the 20%, 100% bit? What that’s saying is “keep all the text on the baseline for the majority of the animation.” That’s what gives us a nice delay between each bounce:

I learnt this trick from this really good post about animation timing by Geoff and I would highly recommend you check it out if you’re about to start learning about animation in CSS.

Now for the fun bit: with the animation-delay property, we can make each letter hop just after the one before it. There’s definitely a smarter way I could be doing this, but I just used the id of each letter like so:

#E {   animation-delay: 0s; }  #N {   animation-delay: 0.1s; }  #T {   animation-delay: 0.15s; }  #H {   animation-delay: 0.2s; }  #U {   animation-delay: 0.25s; }  #S_2 {   animation-delay: 0.3s; }  #I {   animation-delay: 0.35s; }  #A {   animation-delay: 0.4s; }  #S {   animation-delay: 0.45s; }  #M {   animation-delay: 0.5s; }

It sure is messy, but writing the loop wouldn’t save me that much time and I won’t need to update it in the future, so I think it’s fine enough. And with that we’re pretty much done!

We now have a bouncy, enthusiastic title to say hello. Yay for wiggly text!

The post How to Animate Text with SVG and CSS appeared first on CSS-Tricks.



4 Ways to Animate the Color of a Text Link on Hover

Let’s create a pure CSS effect that changes the color of a text link on hover… but slide that new color in instead of simply swapping colors.

There are four different techniques we can use to do this. Let’s look at those while being mindful of important things, like accessibility, performance, and browser support in mind.

Let’s get started!

Technique 1: Using background-clip: text

At the time of writing, the background-clip: text property is an experimental feature and is not supported in Internet Explorer 11 and below.

This technique involves creating knockout text with a hard stop gradient. The markup consists of a single HTML link (<a>) element to create a hyperlink:

<a href="#">Link Hover</a>

We can start adding styles to the hyperlink. Using overflow: hidden will clip any content outside of the hyperlink during the hover transition:

a {   position: relative;   display: inline-block;   font-size: 2em;   font-weight: 800;   color: royalblue;   overflow: hidden; }

We will need to use a linear gradient with a hard stop at 50% to the starting color we want the link to be as well as the color that it will change to:

a {   /* Same as before */   background: linear-gradient(to right, midnightblue, midnightblue 50%, royalblue 50%); }

Let’s use background-clip to clip the gradient and the text value to display the text. We will also use the background-size and background-position properties to have the starting color appear:

a {   /* Same as before */   background-clip: text;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent;   background-size: 200% 100%;   background-position: 100%; }

Finally, let’s add the transition CSS property and :hover CSS pseudo-class to the hyperlink. To have the link fill from left to right on hover, use the background-position property:

a {   /* Same as before */   transition: background-position 275ms ease; } a:hover {   background-position: 0 100%; }

While this technique does achieve the hover effect, Safari and Chrome will clip text decorations and shadows, meaning they won’t be displayed. Applying text styles, such as an underline, with the text-decoration CSS property will not work. Perhaps consider using other approaches when creating underlines.

Technique 2: Using width/height

This works by using a data attribute containing the same text as the one in the <a> tag and setting the width (filling the text from left-to-right or right-to-left) or height (filling the text from top-to-bottom or bottom-to-top), from 0% to 100% on hover.

Here is the markup:

<a href="#" data-content="Link Hover">Link Hover</a>

The CSS is similar to the previous technique minus the background CSS properties. The text-decoration property will work here:

a {   position: relative;   display: inline-block;   font-size: 2em;   color: royalblue;   font-weight: 800;   text-decoration: underline;   overflow: hidden; }

This is when we need to use the content from the data-content attribute. It will be positioned above the content in the <a> tag. We get to use the nice little trick of copying the text in the data attribute and displaying it via the attr() function on the content property of the element’s ::before pseudo-element.

a::before {   position: absolute;   content: attr(data-content); /* Prints the value of the attribute */   top: 0;   left: 0;   color: midnightblue;   text-decoration: underline;   overflow: hidden;   transition: width 275ms ease; }

To keep the text from wrapping to the next line, white-space: nowrap will be applied. To change the link fill color, set the value for the color CSS property using the ::before pseudo-element and having the width start at 0:

a::before {   /* Same as before */   width: 0;   white-space: nowrap; }

Increase the width to 100% to the ::before pseudo element to complete the text effect on hover:

a:hover::before {   width: 100%; }

While this technique does the trick, using the width or height properties will not produce a performant CSS transition. It is best to use either the transform or opacity properties to achieve a smooth, 60fps transition.

Using the text-decoration CSS property can allow for different underline styles to appear in the CSS transition. I created a demo showcasing this using the next technique: the clip-path CSS property.

Technique 3: Using clip-path

For this technique, we will be using the clip-path CSS property with a polygon shape. The polygon will have four vertices, with two of them expanding to the right on hover:

The markup is the same as the previous technique. We will use a ::before pseudo-element again, but the CSS is different:

a::before {   position: absolute;   content: attr(data-content);   color: midnightblue;   text-decoration: underline;   clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);   transition: clip-path 275ms ease; }

Unlike the previous techniques, text-decoration: underline must be declared to the ::before pseudo-element for the color to fill the underline on hover.

Now let’s look into the CSS for the clip-path technique:

clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);

The polygon’s vertices of the clip-path property are set in percentages to define coordinates by the order written:

  • 0 0 = top left
  • 0 0 = top right
  • 100% 0 = bottom right
  • 0 100% = bottom left

The direction of the fill effect can be changed by modifying the coordinates. Now that we have an idea for the coordinates, we can make the polygon expand to the right on hover:

a:hover::before {   clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }

This technique works pretty well, but note that support for the clip-path property varies between browsers. Creating a CSS transition with clip-path is a better alternative than using the width/height technique; however, it does affect the browser paint.

Technique 4: Using transform

The markup for this technique uses a masking method with a <span> element. Since we will be using duplicated content in a separate element, we will use aria-hidden="true" to improve accessibility — that will hide it from screen readers so the content isn’t read twice:

<a href="#"><span data-content="Link Hover" aria-hidden="true"></span>Link Hover</a>

The CSS for the <span> element contains a transition that will be starting from the left:

span {   position: absolute;   top: 0;   left: 0;   overflow: hidden;   transform: translateX(-100%);   transition: transform 275ms ease; }

Next, we need to get the <span> to slide the right like this:

To do this, we will use the translateX() CSS function and set it to 0:

a:hover span {   transform: translateX(0); }

Then, we will use the ::before pseudo-element for the <span>, again using the data-content attribute we did before. We’ll set the position by translating it 100% along the x-axis.

span::before {    display: inline-block;   content: attr(data-content);   color: midnightblue;   transform: translateX(100%);   transition: transform 275ms ease;   text-decoration: underline; }

Much like the <span> element, the position of the ::before pseudo-element will also be set to  translateX(0):

a:hover span::before {   transform: translateX(0); }

While this technique is the the most cross-browser compatible of the bunch, it requires more markup and CSS to get there. That said, using the transform CSS property is great for performance as it does not trigger repaints and thus produces smooth, 60fps CSS transitions.

There we have it!

We just looked at four different techniques to achieve the same effect. Although each has its pros and cons, you can see that it’s totally possible to slide in a color change on text. It’s a neat little effect that makes links feel a little more interactive.

The post 4 Ways to Animate the Color of a Text Link on Hover appeared first on CSS-Tricks.


, , , , ,

Animate SVG Path Changes in CSS

Every once in a while I’m motivated to attempt to draw some shapes with <path>, the all-powerful drawing syntax of SVG. I only understand a fragment of what it all can do, but I know enough to be dangerous. All the straight-line syntax commands (like L) are pretty straightforward and I find the curved Q command fairly intuitive. Box yourself into a viewBox="0 0 100 100" and drawing simple stuff doesn’t seem so bad.

Here’s a classic example of mine that draws things with all the basic commands, but also animates them with CSS (Chromium browsers only):

Weird but true:

<svg viewBox="0 0 10 10">   <path d="M2,2 L8,8" /> </svg>
svg:hover path {   transition: 0.2s;   d: path("M8,2 L2,8"); }

The other day I had a situation where I needed a UI element that has a different icon depending on what state it’s in. It was kind of a “log” shape so the default was straight lines, kinda like a hamburger menu (only four lines so it read more like lines of text), then other various states.

  4. ERROR

First I wrote the most complicated state machine in the world:

const indicator = document.querySelector(".element");  let currentState = indicator.dataset.state;  indicator.addEventListener("click", () => {   let nextState = "";    if (currentState == "DEFAULT") {     nextState = "ACTIVE";   } else if (currentState == "ACTIVE") {     nextState = "SUCCESS";   } else if (currentState == "SUCCESS") {     nextState = "ERROR";   } else {     nextState = "DEFAULT";   }      indicator.dataset.state = nextState;   currentState = nextState; });

That opened the door for styling states with data-attributes:

.element {      &[data-state="DEFAULT"] {   }   &[data-state="ACTIVE"] {   }   &[data-state="SUCCESS"] {   }   &[data-state="ERROR"] {   }  }

So now if my element starts with the default state of four lines:

<div class="element" data-state="DEFAULT">   <svg viewBox="0 0 100 100" class="icon">     <path d="M0, 20 Q50, 20 100, 20"></path>     <path d="M0, 40 Q50, 40 100, 40"></path>     <path d="M0, 60 Q50, 60 100, 60"></path>     <path d="M0, 80 Q50, 80 100, 80"></path>   </svg> </div> 

…I can alter those paths in CSS for the rest of the states. For example, I can take those four straight lines and alter them in CSS.

Note the four “straight” lines conveniently have an unused curve point in them. Only paths that have the same number and type of points in them can be animated in CSS. Putting the curve point in there opens doors.

These four new paths actually draw something close to a circle!

.editor-indicator {      &[data-state="ACTIVE"] {     .icon {       :nth-child(1) {         d: path("M50, 0 Q95, 5 100,50");       }       :nth-child(2) {         d: path("M100, 50 Q95, 95 50,100");       }       :nth-child(3) {         d: path("M50,100 Q5, 95 0, 50");       }       :nth-child(4) {         d: path("M0, 50 Q5, 5 50, 0");       }     }   }  }

For the other states, I drew a crude checkmark (for SUCCESS) and a crude exclamation point (for FAILURE).

Here’s a demo (again, Chromium), where you can click it to change the states:

I didn’t end up using the thing because neither Firefox nor Safari support the d: path(); thing in CSS. Not that it doesn’t animate them, it just doesn’t work period, so it was out for me. I just ended up swapping out the icons in the different states.

If you need cross-browser shape morphing, we have a whole article about that.

The post Animate SVG Path Changes in CSS appeared first on CSS-Tricks.


, ,

Animate Text on Scroll

We covered the idea of animating curved text not long ago when a fun New York Times article came out. All I did was peek into how they did it and extract the relevant parts to a more isolated demo.

That demo is here:

See the Pen
Selfie Crawl
by Chris Coyier (@chriscoyier)
on CodePen.

@keyframers covered it much nicer than I did and made this video. (Seriously, it’s so clear and concise — it’s a great watch.)

I particularly like how quick’n’easy creating, exporting, optimizing, and integrating the actual SVG codes in their demonstration.

Their final demo:

See the Pen
SVG textPath Animation on Scroll Tutorial | Keyssentials: Web Animation Tips by @keyframers
by @keyframers (@keyframers)
on CodePen.

The post Animate Text on Scroll appeared first on CSS-Tricks.


, ,

How to Animate on the Web With Greensock

There are truly thousands of ways to animate on the web. We’ve covered a comparison of different animation technologies here before. Today, we’re going to dive into a step-by-step guide of one of my favorite ways to get it done: using GreenSock.

(They don’t pay me or anything, I just really enjoy using them.)

Why do I prefer Greensock to other methods? Technically speaking, it’s often the best tool for the job. It’s usage is extremely straightforward, even for complex movement. Here are a few more reasons why I prefer using it:

  • You can use them on DOM elements as well as within WebGL/Canvas/Three.js contexts.
  • The easing is very sophisticated. CSS animations are limited to two bezier handles for ease, meaning if you want, say, a bounce effect, you would have to make keyframes up and down and up and down for each pass. GreenSock allows multiple bezier handles to create advanced effects. A bounce is a single line of code. You can see what I mean by checking out their ease visualizer.
  • You can sequence movement on a timeline. CSS animations can get a little spaghetti when you have to coordinate several things at once. GreenSock remains very legible and allows you to control the timeline itself. You can even animate your animations! 🤯
  • It will perform some calculations under the hood to prevent strange cross-browser behavior as well as things that the spec dictates should be true aren’t — like the way that stacking transforms work.
  • It offers a lot of advanced functionality, in the form of plugins, that you can use if you’d like to take your work a step further — things like Morphing SVG shapes, drawing SVG paths, dragging and dropping, inertia, and more.

People sometimes ask me why I’d use this particular library over all of the other choices. It’s further along than most others — they’ve been around since the Flash was still a thing. This demo reel is pretty inspiring, and also gets the point across that serious web animators do tend to reach for this tool:

What follows is a breakdown of how to create movement on the web, distilled down to the smallest units I could make them. Let’s get started!

Animating a DOM element

Consider a ball that we with a <div> that’s styled with a border-radius value of 50%. Here’s how we would scale and move it with Greensock:

<div class="ball"></div>
gsap.to('.ball', {   duration: 1,   x: 200,   scale: 2 })

See the Pen
Greensock Tutorial 1
by Sarah Drasner (@sdras)
on CodePen.

In this case, we’re telling GreenSock (gsap) to take the element with the class of .ball move it .to() a few different properties. We’ve shortened the CSS properties of transform: translateX(200px) to a streamlined x: 200 (note there’s no need for the units, but you can pass them as a string). We’re also not writing transform: scale(2). Here’s a reference of the transforms you might want to use with animations, and their corresponding CSS syntax:

x: 100 // transform: translateX(100px) y: 100 // transform: translateY(100px) z: 100 // transform: translateZ(100px) // you do not need the null transform hack or hardware acceleration, it comes baked in with // force3d:true. If you want to unset this, force3d:false scale: 2 // transform: scale(2) scaleX: 2 // transform: scaleX(2) scaleY: 2 // transform: scaleY(2) scaleZ: 2 // transform: scaleZ(2) skew: 15 // transform: skew(15deg) skewX: 15 // transform: skewX(15deg) skewY: 15 // transform: skewY(15deg) rotation: 180 // transform: rotate(180deg) rotationX: 180 // transform: rotateX(180deg) rotationY: 180 // transform: rotateY(180deg) rotationZ: 180 // transform: rotateZ(180deg) perspective: 1000 // transform: perspective(1000px) transformOrigin: '50% 50%' // transform-origin: 50% 50%

Duration is what you might think it is: it’s a one-second stretch of time.

So, OK, how would we animate, say, an SVG? Let’s consider the same code above as an SVG:

<svg viewBox="0 0 500 400">   <circle class="ball" cx="80" cy="80" r="80" /> </svg>
gsap.to('.ball', {   duration: 1,   x: 200,   scale: 2 })

See the Pen
Greensock Tutorial 2
by Sarah Drasner (@sdras)
on CodePen.

From an animation perspective, it’s actually exactly the same. It’s grabbing the element with the class .ball on it, and translating those properties. Since SVGs are literally DOM elements, we can slap a class on any one of them and animate them just the same way!

Great! We’re cooking with gas.


I mentioned before that eases are one of my favorite features, let’s take a look at how we’d use them.

Let’s take the original ball. Maybe we want to try one of those more unique bounce eases. It would go like this:

gsap.to('.ball', {   duration: 1.5,   x: 200,   scale: 2,   ease: 'bounce' })

See the Pen
Greensock Tutorial 3
by Sarah Drasner (@sdras)
on CodePen.

That’s it! This version of GreenSock assumes that you want to use ease-out timing (which is better for entrances), so it applies that as a default. All you have to do is specify “bounce” as a string and you’re off to the races.

You might have noticed we also lengthened the duration a bit as well. That’s because the ball has to do more “work” in between the initial and end states. A one-second duration, though lovely for linear or sine easing, is a little too quick for a bounce or an elastic ease.

Delays and Timelines

I mentioned that the default ease-out timing function is good for entrances. What about an ease-in or ease-in-out exit? Let’s do that as well.

gsap.to('.ball', {   duration: 1.5,   x: 200,   scale: 2,   ease: 'bounce' })  gsap.to('.ball', {   duration: 1.5,   delay: 1.5,   x: 0,   scale: 1,   ease: 'back.inOut(3)' })

You might have noticed a few things happening. For example, we didn’t use bounce.in on the second-to-last line (ease: 'back.inOut(3)'). Instead, we used another ease called back for ease-in-out. We also passed in a configuration option because, as you can see with Greensock’s ease visualizer tool, we’re not limited to just the default configuration for that ease. We can adjust it to our needs. Neat!

You may have also noticed that we chained the animations using a delay. We took the length of the duration of the first animation and made sure the next one has a delay of that matches. Now, that works here, but that’s pretty brittle. What if we want to change the length of the first one? Well, now we’ve got to go back through and change the delay that follows. And what if we have another animation after that? And another one after that? Well, we’d have to go back through and calculate all the other delays down the line. That’s a lot of manual work.

We can offload that work to the computer. Some of my more complex animations are hundreds of chained animations! If I finish my work and want to adjust something in the beginning, I don’t want to have to go back through everything. Enter timelines:

gsap   .timeline()   .to(‘.ball’, {     duration: 1.5,     x: 200,     scale: 2,     ease: "bounce"   })   .to(‘.ball’, {     duration: 1.5,     x: 0,     scale: 1,     ease: "back.inOut(3)"   });

See the Pen
Greensock Tutorial 4
by Sarah Drasner (@sdras)
on CodePen.

This instantiates a timeline and then chains the two animations off of it.

But we still have a bit of repetition where we keep using the same duration in each animation. Let’s create a default for that as an option passed to the timeline.

gsap   .timeline({     defaults: {       duration: 1.5     }   })   .to('.ball', {     x: 200,     scale: 2,     ease: "bounce"   })   .to('.ball', {     x: 0,     scale: 1,     ease: "back.inOut(3)"   });

See the Pen
Greensock Tutorial 4
by Sarah Drasner (@sdras)
on CodePen.

That’s pretty cool! Alright, you are probably starting to see how things are built this way. While it might not be a big deal in an animation this simple, defaults and timelines in really complex animations can truly keep code maintainable.

Now, what if we want to mirror this motion in the other direction with the ball, and just… keep it going? In other words, what if we want a loop? That’s when we add repeat: -1, which can be applied either to a single animation or to the entire timeline.

gsap   .timeline({     repeat: -1,     defaults: {       duration: 1.5     }   })   .to('.ball', {     x: 200,     scale: 2,     ease: "bounce"   })   .to('.ball', {     x: 0,     scale: 1,     ease: "back.inOut(3)"   })   .to('.ball', {     x: -200,     scale: 2,     ease: "bounce"   })   .to('.ball', {     x: 0,     scale: 1,     ease: "back.inOut(3)"   });

See the Pen
Greensock Tutorial 5
by Sarah Drasner (@sdras)
on CodePen.

We could also not only make it repeat, but repeat and play back and forth, like a yoyo. That’s why we call this yoyo: true. To make the point clear, we’ll show this with just the first animation. You can see it plays forward, and then it plays in reverse.

gsap   .timeline({     repeat: -1,     yoyo: true,     defaults: {       duration: 1.5     }   })   .to('.ball', {     x: 200,     scale: 2,     ease: "bounce"   })

See the Pen
Greensock Tutorial 6
by Sarah Drasner (@sdras)
on CodePen.

Overlaps and Labels

It’s great that we can chain animations with ease, but real-life motion doesn’t exactly work this way. If you walk across the room to get a cup of water, you don’t walk. Then stop. Then pick up the water. Then drink it. You’re more likely to do things in one continuous movement. So let’s talk briefly about how to overlap movement and make things fire at once.

If we want to be sure things fire a little before and after each other on a timeline, we can use an incrementer or decrementer. If we take the followingexampxle that shows three balls animating one after another, it feels a little stiff.

gsap   .timeline({     defaults: {       duration: 1.5     }   })   .to('.ball', {     x: 300,     scale: 2,     ease: "bounce"   })   .to('.ball2', {     x: 300,     scale: 2,     ease: "bounce"   })   .to('.ball3', {     x: 300,     scale: 2,     ease: "bounce"   })

See the Pen
Greensock Tutorial 8
by Sarah Drasner (@sdras)
on CodePen.

Tbigns get smoother if we overlap the movement just slightly using those decrementers passed as strings:

gsap   .timeline({     defaults: {       duration: 1.5     }   })   .to('.ball', {     x: 300,     scale: 2,     ease: "bounce"   })   .to('.ball2', {     x: 300,     scale: 2,     ease: "bounce"   }, '-=1')   .to('.ball3', {     x: 300,     scale: 2,     ease: "bounce"   }, '-=1')

See the Pen
Greensock Tutorial 9
by Sarah Drasner (@sdras)
on CodePen.

Another way we can do this is to use something called a label. Labels make sure things fire off at a particular point in time in the playhead of the animation. It looks like this:.add('labelName')

gsap   .timeline({     defaults: {       duration: 1.5     }   })   .add('start')   .to('.ball', {     x: 300,     scale: 2,     ease: "bounce"   }, 'start')   .to('.ball2', {     x: 300,     scale: 2,     ease: "bounce"   }, 'start')   .to('.ball3', {     x: 300,     scale: 2,     ease: "bounce"   }, 'start')

See the Pen
Greensock Tutorial 10
by Sarah Drasner (@sdras)
on CodePen.

We can even increment and decrement from the label. I actually do this a lot in my animations. It looks like this: 'start+=0.25'

gsap   .timeline({     defaults: {       duration: 1.5     }   })   .add('start')   .to('.ball', {     x: 300,     scale: 2,     ease: "bounce"   }, 'start')   .to('.ball2', {     x: 300,     scale: 2,     ease: "bounce"   }, 'start+=0.25')   .to('.ball3', {     x: 300,     scale: 2,     ease: "bounce"   }, 'start+=0.5')

Whew! We’re able to do so much with this! Here’s an example of an animation that puts a lot of these premises together, with a bit of interaction using vanilla JavaScript. Be sure to click on the bell.

See the Pen
phone interaction upgrade
by Sarah Drasner (@sdras)
on CodePen.

If you’re looking more for framework-based animation with GreenSock, here’s an article I wrote that covers this in Vue, and a talk I gave that addresses React — it’s a couple of years old but the base premises still apply.

But there’s still so much we haven’t cover, including staggers, morphing SVG, drawing SVG, throwing things around the screen, moving things along a path, animating text… you name it! I’d suggest heading over to GreenSock’s documentation for those details. I also have a course on Frontend Masters that covers all of these in much more depth and the materials are open source on my GitHub. I also have a lot of Pens that are open source for you to fork and play with.

I hope this gets you started working with animation on the web! I can’t wait to see what you make!

The post How to Animate on the Web With Greensock appeared first on CSS-Tricks.



Link Underlines That Animate Into Block Backgrounds

It’s a cool little effect. The default link style has an underline (which is a good idea) and then on :hover you see the underline essentially thicken up turning into almost what it would have looked liked if you used a background-color on the link instead.

Here’s an example of the effect on the Superfriendly site:

A journey:

  • The Superfriendly site does it with box-shadow. Turning box-shadow: inset 0 -0.07em 0 #0078d6; into box-shadow: inset 0 -0.85em 0 #a2d5fe; with a transition. Andres Cuervo ported that idea to a Pen. (I forked it to fix the “start offset” idea that was broken-seeming to me on the original).
  • You might be tempted to draw the line with a pseudo-element that’s, say, absolutely positioned within the relatively positioned link. Then you animate its height or scaleY or something. Here’s that kind of idea. Your enemy here is going to be links that break onto new lines, which box-shadow seems to handle more elegantly.
  • Another idea would be using linear-gradient with hard color stops to kinda “fake” the drawing of a line that’s positioned to look like an underline. Then the gradient can be animated to cover the element on hover, probably by moving its background-position. Here’s that kind of idea and another example we wrote up a little while back. This handles line breaks nicer than the previous method.
  • The default text-decoration: underline; has a distinct advantage these days: text-decoration-skip-ink! It has become the default behavior for links to have the underlines deftly skip around the decenders in text, making for much nicer looking underlines than any of these techniques (also: borders) can pull off. There are properties that are somewhat new that you may not be aware of that give you more control over the underline that we have traditionally had, like text-decoration-color. But there is more, like thickness and offset, that make this effect possible! Miriam Suzanne has a demo of exactly this, which only works in Firefox Nightly at the moment, but should be making the rounds soon enough.

Summary: If you need to do this effect right now in the most browsers you can, the box-shadow technique is probably best. If it’s just an enhancement that can wait a bit, using text-decoration-thickness / text-decoration-offset / text-decoration-color with a transition is a better option for aesthetics, control, and being able to understand the code at first glance.

The post Link Underlines That Animate Into Block Backgrounds appeared first on CSS-Tricks.


, , , , ,

Animate a Blob of Text with SVG and Text Clipping

I came across this neat little animation in a designer newsletter — unfortunately, I lost track of the source, so please give a shout out if you recognize it! In it, a block of text appears to bleed into view with a swirl of colors, then goes out the same way it came in. It’s a slick effect and one I wanted to recreate in code.

The approach I took was to use SVG text as a clip path for an SVG background. We must use SVG text because CSS currently only allows us to animate the background with text clipping if the background is a GIF, which we’re not going to do.

Our first task is to create a background of different SVG shapes. Oval-y blobs work pretty well. The thing to make sure of is to match the size the artboard/canvas in whatever illustration app you’re using to the same dimension as the viewBox you want the final work to be. (In Inkscape, this option is under the Scale section of Document Properties.)

The goal is to cover most of the artboard with a variety of shapes. I’ve found that it looks best if some of the shapes overlap.

Next, we’ll create some text in a <clipPath>, group the objects that make up the background inside a <g> element, and apply a CSS clip-path on that group. Altogether it looks something like this:

<svg viewbox="0 0 700 225">      <clipPath id="textClip" class="filled-heading">     <text y="70">We are</text>     <text y="140">Creators</text>     <text y="210">+Innovators</text>   </clipPath>       <g id="background" clip-path="url(#textClip)">     <path d="m449.78..." />        </g> </svg>

At this point, all we get is some plain text because we haven’t gotten around to the background animation quite yet.

So what about that animation? We can use a relatively simple CSS animation like this:

/* Animate the background shapes */ #background path {   animation: pulse 4s cubic-bezier(0.455, 0.030, 0.515, 0.955) infinite;    /* Necessary to keep the SVG objects in place while scaling */   transform-origin: 50% 50%;   transform-box: fill-box; }  @keyframes pulse {   /* Rotating it along with the scale makes it a little bit more fancy */   0%, 100% { transform: scale(0) rotate(33deg); }   35%, 65% { transform: scale(1) rotate(0deg); } }

So far, so good.

transform-box: fill-box; is not supported in Internet Explorer or Edge at this point, so if you need to support those browsers, you’ll need to use a JavaScript workaround, like this one.

See the Pen
Animated blob SVG text clipping effect – Pt. 1
by Zach Saucier (@Zeaklous)
on CodePen.

We could start painting things in by hard-coding color values using a text or vector editor, but it’s more fun to color the shapes dynamically. Something like this:

// Define an array of colors const colors = ['#f5a147','#51cad8','#112b39']; // Select the SVG paths var blobs = document.querySelectorAll("path");  // Randomly apply colors to the SVG fill property blobs.forEach(blob => {   blob.style.fill = colors[Math.floor(Math.random() * colors.length)]; });

In order to change the text values for each iteration, we need to first add them to the SVG clip path.

<clipPath id="text" class="filled-heading">   <text y="70">We are</text>   <text y="140">Creators</text>   <text y="210">+Innovators</text>      <text y="70">We are</text>   <text y="140">Movers</text>   <text y="210">+Shakers</text>      <text y="70">We are</text>   <text y="140">Stylish</text>   <text y="210">+Techy</text> </clipPath>

Then we can either use CSS or JavaScript to reveal the lines of text in our preferred order. Unfortunately, we can’t surround each section of <text> using a <g> element because <g> elements don’t work inside of a clipPath. For this post, we’re going to split things up into three CSS animations, one for each group of three paths:

/* Selects paths 1-3 */ #textClip text:nth-of-type(n + 1):nth-of-type(-n + 3) {   animation: showFirst 12s infinite; }  /* Selects paths 4-6 */ #textClip text:nth-of-type(n + 4):nth-of-type(-n + 6) {   animation: showSecond 12s infinite; }  /* Selects paths 7-9 */ #textClip text:nth-of-type(n + 7):nth-of-type(-n + 9) {   animation: showThird 12s infinite; }  @keyframes showFirst {   0%, 33% {     opacity: 1;   }   33.0001%, 100% { opacity: 0; } }  @keyframes showSecond {   33.0001%, 66% {     opacity: 1;   }   0%, 33%, 66.0001%, 100% { opacity: 0; } }  @keyframes showThird {   66.0001%, 99.999% {     opacity: 1;   }   0%, 66%, 100% { opacity: 0; } }

That does the trick!

See the Pen
Animated blob SVG text clipping effect – Pt. 2
by Zach Saucier (@Zeaklous)
on CodePen.

At this point, we can have a little fun. For example, we can swap backgrounds for a different effect. I used Inkscape’s star tool with three to four points to generate some random shapes (using Inkscape’s random parameter) and then colored them using a palette from one of the many color scheme generators (I used Palx) to produce this version:

See the Pen
Animated blob SVG text clipping effect – Pt. 3
by Zach Saucier (@Zeaklous)
on CodePen.

The backgrounds don’t even need to fill up the entire background, depending on the effect that we want to create. For example, we could duplicate the text using a element and fill in the text using that as seen in this demo.

Or we could mix it up by rotating the background blobs like this:

See the Pen
Animated blob SVG text clipping effect – Pt. 5
by Zach Saucier (@Zeaklous)
on CodePen.

To make the colors change for every new set of words, we could use either a CSS or JavaScript for the animation. I used JavaScript (and moved the CSS animation that was hiding the text lines to the JavaScript):

See the Pen
Animated blob SVG text clipping effect – Pt. 6
by Zach Saucier (@Zeaklous)
on CodePen.

To center the text horizontally, add x="50%" text-anchor="middle" to each <text> element (Demo). Centering it vertically would take more manual calculation since we’re working with a multi-line format.

One of the nice things about this approach is that, since it uses SVG, it is responsive by default!

P.S. After I made this approach and was looking for the original GIF author, I came across another recreation by Martí Fenosa doing the same effect using a different approach. Check his demo out as well because it’s clever!

The post Animate a Blob of Text with SVG and Text Clipping appeared first on CSS-Tricks.


, , ,