Animating CSS border-image
Border images are an overlooked feature. One neat fact is that border image slices can run across entire borders on an element, and animating it creates beautiful effects. Animating CSS border image originally handwritten and published with love on CSS Tricks . You should really get the newsletter as well.
The CSS
border-image
property is one of those features we take for granted. And it’s not new at all! In fact, Andy Clarke has a fairly recent article that “revisits” the property and shows just how creative we can get with it. Go read that article — it’s worth it.
The TL;DR is that we can use an image or gradient as a border instead of standard (ahem, boring) styles, say, solid or dashed border lines. What I want to do is take them a little further to show how we can animate border images for even more interesting user interfaces!
Like this:
Why border images at all?
There’s a pretty big caveat to border images: they don’t curve to border shapes. That’s a big limitation for an animation that travels around an element like we’re doing here. But we can work around that for this example.
But that also begs the question: “Why work around that if there are other approaches?” Just look at Temani Afif’s approach that uses a CSS mask (he has a great write-up on it):
I like
border-image
because it’s super efficient, especially when it comes to animating border images and gradients. Mainly because it’s easy to replicate the designs across all the borders — it’s automatic — and also because we can “slice” the images using the
border-image-slice
property. It’s similar to
background-size
and
background-position
where a single slice of the image can be used. However the border image slices can run across the entire borders, and animating it creates beautiful effects, as you’ll see.
The HTML
We’re doing nothing crazy here. Just a
div
that contains a bit of formatted text in it:
<div class=card><strong>Bruce Wayne</strong></div>
What about the image? That’s set up as a CSS background, so let’s jump right in that.
Base styles
We’re giving the
.card
set dimensions then dropping in a background image on it:
.card {
width: 150px;
aspect-ratio: 0.69;
position: relative;
background: center/90% no-repeat;
background-image: url("batman.jpg");
}
Cool, cool. I’m also adding a little styling to the text to make sure it’s not right on top of the background:
The border image
I’ll be using a single-color CSS gradient because (1) it’s a type of background image and (2) it’s a little easier to demonstrate the animation.
I’ll also set all the values of
border-image
separately in their own longhand properties, so it’s clearer for you to know which value affects the gradient in which way.
Let’s start with the effect of a simple
linear-gradient
:
.card {
/* ... */
/* Creates a linear color */
border-image-source: linear-gradient(-45deg, red 0%, transparent 0%);
/* Controls how the gradient is carved */
border-image-slice: 1;
/* Sets the physical thickness of the border */
border-image-width: 5px;
}
You may have noticed in the demo that there is a little gap between the animated border and the image. We can push the border away from the
.card
using the
border-image-outset
property:
.card {
/* ... */
border-image-source: linear-gradient(-45deg, red 0%, transparent 0%);
border-image-slice: 1;
border-image-width: 5px;
/* Pushes the border away from the div */
border-image-outset: 5px;
}
We need
border-image-source
and
border-image-width
to render the border. Initially, the image source — the gradient — is fully transparent. Because both the
red
and
transparent
colors are set to start at
0%
, the browser doesn’t have any space to blend the colors, and since
transparent
comes second in the code, it gets the starting position at
0%
and fills the rest of the gradient. We’re using
border-image-slice: 1
to create
1px
slices of the gradient, which creates a smooth fill of the gradient across the whole border.
Animating the border image
There’s something else to remember about gradients, specifically about animating them: we can’t by default. Transitioning (or interpolating) from one color to another is difficult because we’re essentially transitioning the starting and ending points of a gradient, which are percentages.
That’s where registering our own custom property comes into play because we can indeed animate CSS variables:
@property --p {
syntax: "<percentage>";
initial-value: 0%;
inherits: false;
}
And we can use that in our gradient:
.card {
/* ... */
border-image-source: linear-gradient(-45deg, red var(--p), transparent 0%);
}
Now let’s say that we want
--p
to transition from its default value of
0%
to
100%
when the
.card
is hovered:
.card {
/* ... */
border-image-source: linear-gradient(-45deg, red var(--p), transparent 0%);
/* ... */
&:hover {
--p: 100%;
}
}
This tells the browser that red should finish its transition at the 100% mark (the very end). The gradient will grow to be a solid red color, like the border is drawing itself:
Variations
We could have stopped with that last example, but there’s so much else we can do here! We can change direction, make it faster or slower, stop in other positions, or even use different types of gradients. Seriously, it’s a lot of fun.
Let’s try using a
conic-gradient
instead of a linear one. We can animate the
border-image-slice
value, and use
border-image-repeat: round
to tile the slices within the borders. The
round
tiling tries to fit a whole number of tiles in the border and if it couldn’t, the image slightly stretches or squeezes until the fit occurs.
.card {
/* Creates a sharp, expanding color wheel using the custom angle variable */
border-image-source: conic-gradient(from 0deg, red 0deg, transparent 0%);
/* Repeats the sliced regions smoothly without clipping */
border-image-repeat: round;
border-image-width: 5px;
border-image-slice: 1;
}
We still need to register custom properties to move the gradient around, this time to transition the
border-image-slice
value (we’ll call it
--n
) and the conic-gradient’s angle (which we’ll call
--a
):
Note: You can also animate
border-image-slice
directly and not through a registered custom property. The result will slightly vary based on how the browser handles those changes.
/* Registers a custom property for the border-image-slice */
@property --n {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
/* Registers a custom property for the gradient angle */
@property --a {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
Let’s drop those into our original CSS:
.card {
border-image-source: conic-gradient(from var(--a), red var(--a), transparent 0%);
border-image-width: 5px;
border-image-slice: var(--n);
border-image-repeat: round;
}
…and tell the CSS exactly which properties we’re transitioning, which is good for performance:
.card {
border-image-source: conic-gradient(from var(--a), red var(--a), transparent 0%);
border-image-width: 5px;
border-image-slice: var(--n);
border-image-repeat: round;
transition-property: --n, --a;
transition-duration: 0.6s;
}
…and then make the properties transition on hover:
.card {
border-image-source: conic-gradient(from var(--a), red var(--a), transparent 0%);
border-image-width: 5px;
border-image-slice: var(--n);
border-image-repeat: round;
transition-property: --n,--a;
transition-duration: 0.6s;
&:hover {
/* Increases slicing depth to repeat/tile the border pattern */
--n: 20;
/* Rotates the gradient full circle to draw the border in */
--a: 360deg;
}
}
The slice value (
--n
) is initially
1
. It goes up to
20
on hover. That means the slice gets bigger (up to
20px
thick). You’ll notice this when you see the breaks in the borders.
More examples!
Like I said earlier, there’s so much we can do with animated border images. We can animate multiple colors, use repeating gradients. Here are the examples I pulled together.
Your homework: pick these apart — and please let me know if you make any other effects! I’d love to see them.
منتشرشده در
CSS-Tricks
ادامه مطالعه




