This is a one-line drawing of my face, done by my partner Sophia.
A single stroke drawing is easy to animate, since it can be converted to one SVG path and revealed with a CSS animation.
Once we have the SVG (it’s been a while so I can’t remember which tool we used for this), setting pathLength="1" gives us a normalized value for the full path, rather than having to measure the length with JavaScript (path.getTotalLength(), but this way we are JS-free).
stroke-dasharray takes a list of lengths, which alternate between dashes and gaps along the path, repeating until the path runs out. stroke-dasharray: 1 1 gives us a dash of length 1 followed by a gap of length 1.
stroke-dashoffset slides the dash pattern along the path before drawing starts. Because a dasharray is a repeating pattern, stroke-dashoffset: 1 can shift the pattern by one to a gap, hiding the dash.
As the CSS animation changes the offset from 1 to 0 over four seconds, the dash gradually replaces the gap, revealing the drawing from start to finish.
A nice challenge would be to build something similar out of multiple paths, managing the animation timings to get a similar ‘see how it’s drawn’ effect.
Here’s the Face.astro component, with the long SVG path data omitted for readability.
---
interface Props {
scale?: number
}
const { scale = 1 } = Astro.props
const width = 300 * scale
const height = 340 * scale
---
<svg
class="face"
viewBox="0 0 350 500"
role="img"
width={width}
height={height}
>
<title>A one-line portrait of my face</title>
<path
pathLength="1"
d="…"
stroke="currentColor"
stroke-width="8"
stroke-linecap="round"
fill-opacity="0"
/>
</svg>
<style>
.face {
display: block;
max-width: 100%;
max-height: calc(100svh - 3rem);
}
.face path {
stroke-dasharray: 1 1;
stroke-dashoffset: 1;
animation: draw-face 4s ease-out forwards;
}
@keyframes draw-face {
to {
stroke-dashoffset: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.face path {
animation: none;
stroke-dashoffset: 0;
}
}
</style>