Two properties, one idea
An SVG stroke can be drawn as a series of dashes instead of a solid line. stroke-dasharray describes that pattern — the length of each dash and the length of each gap between them. stroke-dashoffset then says how far along the path the pattern starts.
That is the whole model. The array decides the shape of the pattern; the offset decides its position. Because a line has no natural direction except the order of its points, sliding the pattern along it looks exactly like movement.
The flowing line effect
For a piped flow, keep the pattern modest and march the offset backwards by one full period per cycle. A period is simply the dash length plus the gap length. With a dash of 12 and a gap of 9, the period is 21; animating the offset to -42 means two full periods have passed, so the loop is seamless.
.flow {
stroke-dasharray: 12 9; /* dash + gap = 21 period */
stroke-dashoffset: 0;
animation: travel 1.15s linear infinite;
}
@keyframes travel {
to { stroke-dashoffset: -42; } /* 2 × 21 */
}
Why the negative sign matters
A decreasing offset pulls the pattern towards the start of the path, which the eye reads as forward motion. Reverse the direction of travel by flipping to a positive offset. Keep every arrow in one diagram moving the same way unless the system genuinely reverses, otherwise readers dilute the signal.
The self-drawing path effect
The same two properties draw a path on instead of shaking a flow through it. Set one dash longer than the whole path and one gap longer than the path, then animate the offset from the full path length down to zero. The line appears to sketch itself from start to finish.
.draw {
stroke-dasharray: 600; /* ≥ path length */
stroke-dashoffset: 600; /* hidden at the start */
animation: draw 1.6s ease-out forwards;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}
Pitfalls worth knowing
- Period mismatch. If your trip distance is not a whole number of periods, the loop visibly snaps. Multiply the period.
- Path length unknown. For hand-written paths, use
getTotalLength()in a console one-liner to find the exact value for a perfect draw-on. - Too much at once. Combining dashoffset with scale, blur and rotation buries the point. One moving property is enough.
- Forgetting reduced motion. Wrap every offset keyframe so it can be disabled; the still path should always remain legible.