Looking at several ways to animate a border in CSS
Setting borders
There are a few methods available to set a border on an element: border
, outline
, and box-shadow
. As detailed in The 3 CSS Methods for Adding Element Borders by Stephanie Eckles, each approach comes with its own advantages and disadvantages–especially when it comes to animating the borders. The main reason to not use a proper CSS border
is for animation purposes.
An article that recently caught my attention is Fantastic CSS border animation, where author Coco explored more options. By injecting generated content using ::before
and ::after
they create a faux border which is then animated.
What stands out the most to me are the supporting animated visualizations used in the article. They really help explain what exactly is being done to achieve the desired effect.
Both the white layer and colored lines are generated content. By fading the white layer in and out, it becomes clear how they stack and how the animation works.
Retaining the box model
A disadvantage of using Generated Content to imitate a border is that you end up with a broken box model: the content can now obscure the faux border because said “border” is painted underneath. To mitigate, you have to apply the desired border-width
as the padding
.
To have a true border–and thus retain the workings of the box model–you can use multiple backgrounds which you then stretch out into the border area.
The basics
Let’s start by creating a dotted border and adding the multiple backgrounds.
/* Size of the border */
--border-size: 0.5rem;
/* Create a dotted border */
border: var(--border-size) dotted lime;
/* Create two background layers:
1. A white semi-transparent
2. A layer with the colored boxes
*/
background-image:
linear-gradient(to right, rgb(255 255 255 / 0.5), rgb(255 255 255 / 0.5)),
conic-gradient(
from 45deg,
#d53e33 0deg 90deg,
#fbb300 90deg 180deg,
#377af5 180deg 270deg,
#399953 270deg 360deg
)
;
Sizing the backgrounds with background-origin
As you can see there is something funny going on with the backgrounds here: they are painted into the border, but the conic-gradient
seems to be all wrong. This is actually intended behavior: by default background images do not draw into the border as their origin is the padding-box
of the element. To create a border after all, the set background images are repeated in the border itself, yielding the weird visual effect.
To solve this problem, you need to stretch out the background so it also occupies the size of the border. You could do this manually by stretching and repositioning the background, but best is to use the background-origin
property to size the background against the border-box
.
/* Manually add or offset the size of the border where needed */ background-position: calc(var(--border-size) * -1) calc(var(--border-size) * -1); background-size: calc(var(--border-size) * 2 + 100%) calc(var(--border-size) * 2 + 100%);
background-origin: border-box;
This one addition makes everything look much better:
Shrinking the white background layer with background-clip
With the backgrounds taking up all the space now, the semi-transparent layer needs to be shrunk down again. Instead of fiddling with background-size
again, there is an easier way to do so: use background-clip
and set it to padding-box
. That way the background is no longer drawn underneath the area of the border.
background-clip:
padding-box, /* Clip white semi-transparent to the padding-box */
border-box /* Clip colored boxes to the border-box (default) */
;
Finally, make the border transparent
to have the full effect.
border: 0.3rem dotted transparent;
Animation
To restore animation of the border, you can manipulate the start angle of the conic-gradient
.
--angle: 0deg;
conic-gradient(
from var(--angle),
#d53e33 0deg 90deg,
#fbb300 90deg 180deg,
#377af5 180deg 270deg,
#399953 270deg 360deg
);
Thanks to @property this becomes a breeze in browsers that support it:
@property --angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
@keyframes rotate {
to {
--angle: 360deg;
}
}
All combined, the code becomes this:
Bonus Content: border-image
A previously covered approach to draw a gradient border is to use CSS border-image
.
It allows for more simplified code as you do not need to deal with overlapping backgrounds. Animation can be applied in the same manner as before.
/* Create a border */
border: 0.5rem solid transparent;
/* Paint an image in the border */
border-image:
conic-gradient(
from var(--angle),
#d53e33 0deg 90deg,
#fbb300 90deg 180deg,
#377af5 180deg 270deg,
#399953 270deg 360deg
) 1
;
However, you’ll notice a few things no longer work with this approach:
- The
border-image
does not follow theborder-radius
; it will always remain rectangular. - When setting
border-image-slice
to fill, theborder-image
is not painted underneath the setbackground
but on top. This can be troublesome if you want the background to be semi-transparent.
In closing
There’s a multitude of possibilities to animate borders in CSS. Depending on the use-case, you might lean into one or the other.