Drawing a signature with GSAP DrawSVG
How the signature on this site writes itself: font outlines from opentype.js, a stroked mask, and a pen that moves at an even speed.
31 Oct 20262 min readGSAP, SVG, Animation
The first thing you see on this site is my name writing itself. It isn't a video or a hand-traced path. It is a real font, turned into SVG and revealed by GSAP's DrawSVG plugin.
Why not just animate the text?
A script font looks hand-written, but the browser draws each glyph in one go. To make it look like a pen is moving, you need the glyph's path, so you can reveal it a little at a time.
Step 1: turn the font into paths
opentype.js reads a font file in the browser and gives you each glyph's outline as SVG path data.
const font = opentype.parse(await (await fetch(FONT_URL)).arrayBuffer())
let x = padding
const paths = [...text].map((char) => {
const glyph = font.charToGlyph(char)
const d = glyph.getPath(x, baseline, fontSize).toPathData(3)
x += glyph.advanceWidth * (fontSize / font.unitsPerEm)
return d
})One path per letter keeps the animation in your hands: you can time every letter on its own.
Step 2: reveal the fill with a stroked mask
A font glyph is an outline. If you draw its stroke, you get a thin line tracing the edge of each letter, which looks nothing like ink.
The trick is to draw the outline as a thick white stroke inside a mask, and put the solid glyph underneath. As the stroke draws, it uncovers the fill.
<mask id={maskId} maskUnits="userSpaceOnUse">
{paths.map((d, i) => (
<path key={i} d={d} fill="none" stroke="white" strokeWidth={fontSize * 0.18} />
))}
</mask>
<g mask={`url(#${maskId})`}>
{paths.map((d, i) => <path key={i} d={d} fill={color} />)}
</g>Step 3: one pen, even speed
Animating every letter with the same duration and a stagger looks mechanical: a short i and a long M take the same time.
A real pen moves at a steady speed, so each letter gets time in proportion to its length:
const lengths = strokes.map((el) => el.getTotalLength())
const total = lengths.reduce((sum, l) => sum + l, 0)
const pen = gsap.timeline({ paused: true })
let cursor = 0
strokes.forEach((el, i) => {
const span = lengths[i] / total
pen.to(el, { drawSVG: "100%", duration: span, ease: "sine.inOut" }, cursor)
cursor += span
})sine.inOut on each letter gives a small slow-down at every pen lift. Then one outer tween drives the whole thing, with its own ease, so the signature starts with a flick and settles at the end:
gsap.to(pen, { progress: 1, duration: 1.2, ease: "power2.inOut" })Step 4: a nib on the tip
A small dot that follows the tip sells the illusion. On every update, find the letter being drawn and ask the path where its tip is:
const point = strokes[i].getPointAtLength(local * lengths[i])
nib.setAttribute("cx", String(point.x))
nib.setAttribute("cy", String(point.y))Wrapping up
The whole effect is four ideas: real glyph paths, a stroked mask, time weighted by length, and a nib. None of them is heavy, and the signature stays crisp at any size because it is still a font.
Need this on your site?
I build interfaces like this for agencies, startups and SaaS teams.
Let's talk