Transitioning a clip-path polygon shape
I was building a layout where I wanted to show links with images that are cropped to a diamond shape. When the user points their mouse at the link the shape should change to an octagon.
This can be achieved in CSS by using SVG masks or by defining a polygon clip-path
. Because I wanted to animate the change of the shape, I opted for the clip-path
.
When the link is focused through keyboard navigation, I un-set the clip-path
because otherwise the focus outline would become invisible as it is also clipped.
<a href="..." class="shaped">
<img src="kitten.jpg" alt="A cute kitten">
</a>
.shaped {
display: inline-block;
clip-path: polygon(
50% 0%,
100% 50%,
50% 100%,
0% 50%
);
}
.shaped:hover {
clip-path: polygon(
25% 0%,
75% 0%,
100% 25%,
100% 75%,
75% 100%,
25% 100%,
0% 75%,
0% 25%
);
}
@media screen and (prefers-reduced-motion: no-preference) {
.shaped {
transition: clip-path 1s;
}
}
.shaped:focus-visible {
clip-path: none;
}
You will notice that the transition is not working. This is because the number of vertices in the two polygons don't match up. Vertices are the value pairs that are used for drawing the polygon shape. So in order to get this working we have to make sure the two polygons which should be transitioned have the same number of vertices. In this example we can repeat each of the vertices in the initial polygon so that the four corners of the diamond are defined by two identical coordinates:
.shaped {
clip-path: polygon(
50% 0%,
50% 0%,
100% 50%,
100% 50%,
50% 100%,
50% 100%,
0% 50%
0% 50%
);
}
.shaped:hover {
clip-path: polygon(
25% 0%,
75% 0%,
100% 25%,
100% 75%,
75% 100%,
25% 100%,
0% 75%,
0% 25%
);
}
For the two simple shapes in this example one might also use a custom property and some calculations to avoid repetitve code.
That way it also becomes impossible to get a mismatch of vertices between the neutral and the hover state.
.shaped {
display: inline-block;
--clip-offset: 50%;
clip-path: polygon(
var(--clip-offset) 0%,
calc(100% - var(--clip-offset)) 0%,
100% var(--clip-offset),
100% calc(100% - var(--clip-offset)),
calc(100% - var(--clip-offset)) 100%,
var(--clip-offset) 100%,
0% calc(100% - var(--clip-offset)),
0% var(--clip-offset)
);
}
.shaped:hover {
--clip-offset: 25%;
}
@media screen and (prefers-reduced-motion: no-preference) {
.shaped {
transition: clip-path 1s;
}
}
.shaped:focus-visible {
clip-path: none;
}
Kitten image courtesy of Sy, licensed under CC BY-NC-SA 2.0.
This article has been published on on my blog. Here's a list of all articles if you're interested in this kind of stuff.
Feedback
If you have feedback, questions, or want to comment on this, please send me an e-mail or contact me through any other channel.