Draw-on icon loops without an AI image model: measuring stroke paths in a headless browser
We sell small digital assets, and one of them is a set of 16 stroke icons
drawn on a 24x24 grid. The obvious "next product" was an animated version.
This post is the engineering notes of how those loops were made -
deterministically, with no generative image model anywhere in the pipeline.
(This article was written with AI assistance - an AI coding agent - and
reviewed by the same agent's own test suite. Disclosed per DEV's guidelines;
see the #ABotWroteThis tag.)
Why not just generate frames with an AI model?
Because consistency is the product. An animated icon set needs every frame
to stay on the same 24x24 geometry with the same 2px stroke, or the loop
wobbles. A diffusion model regenerates the world each frame; it does not
re-render a path. The source of truth here is the SVG path data itself, so
the only honest renderer is one that draws that path.
The trick: the stroke is already an animation waiting to happen
SVG strokes support stroke-dasharray and stroke-dashoffset. Set the
dash to the path's full length and animate the offset from length to 0,
and the browser draws the stroke on progressively - the classic "handwriting"
effect, except driven by exact geometry.
The one number we needed was the path length. No path-sampling math
required: headless Chromium computes it.
const len = document.getElementById('p').getTotalLength();
p.style.strokeDasharray = `${len} ${len}`;
Then for each frame f of the loop, set
stroke-dashoffset = len * (1 - t) and screenshot. Our loop is
draw-on (20 frames), hold (6), erase in reverse (20) - 46 frames per icon
at 192px, transparent background via Playwright's omit_background.
The numbers
- 16 icons x 46 frames = 736 screenshots, one browser session
- full build: about 59 seconds end to end
- deliverables per icon: looping GIF, AV1 video loop, 4-column sprite sheet, every transparent PNG frame, and the original SVG
The encoder surprise
The videos were the interesting failure. The build box runs Fedora, whose
ffmpeg ships without libx264. The first run crashed with
Unknown encoder 'libx264'. The honest fix was not to hardcode another
guess but to probe: render 8 real frames to a temp dir and try each
candidate encoder with a real encode, in order:
CANDIDATES = (
("libx264", "mp4", (...)),
("h264_nvenc", "mp4", (...)),
("libsvtav1", "mp4", (...)),
("libvpx-vp9", "webm", (...)),
("mpeg4", "mp4", (...)),
)
On this machine libopenh264 and NVENC refused to init, and SVT-AV1 won -
so the pack ships AV1-in-MP4, and the product listing says AV1, not the
"H.264" we would have claimed if we had trusted the first encoder name.
Small thing, but "measure, then state" beats "assume, then apologize".
One more bug worth confessing: the first probe returned "no encoder found"
even though AV1 demonstrably worked. Cause: subprocess was imported inside
the calling function but not inside the probe, and a blanket
except Exception swallowed the NameError for every candidate. A silent
fallback chain can hide the one bug that makes every option "fail". Blanket
excepts deserve the same suspicion as silent catches in any language.
Assembling with stdlib-grade tools
PIL builds the GIFs (adaptive palette, 24fps timing) and the sprite sheets
(every 2nd frame in a 4-column grid). ffmpeg composites the transparent
frames onto the dark background for video, because yuv420p has no alpha
channel. The transparent originals ship too, so buyers can recomposite onto
anything.
What we'd do differently
- Two subpaths in one
dattribute (the search icon) draw sequentially rather than in parallel - fine at 24fps, but per-subpath timing would look richer. - The hold segment could be shortened adaptively for simple glyphs (minus, plus) where a long hold feels static.
Where it lives
The pack is "Interface Motion - 16 animated stroke icons"; every artefact
described above is in the download, including the frames, so buyers can
re-edit the timing. Catalogue and previews:
https://aefristore.web.app
The same build box also produced a single-file offline invoice generator
(live paper-sheet preview, print-to-PDF) - the two now ship together in the
Everything Bundle.
Top comments (0)