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:

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!



Source link