Skip to content

Animate an Original Line Drawing for a Treatment Sample

Advertising

Animate an Original Line Drawing for a Treatment Sample

The board has a drawing on it. A chair, say — finished, confident, every line closed and correct. Underneath the image, the line says the chair draws itself.

What the board does not have is the order in which the chair arrived. A finished illustration is a destination. The sequence is the journey the treatment is actually promising, and the drawing on the page has nothing to say about it. If you animate that illustration with a wipe that sweeps left to right, you have promised a hand and delivered a curtain.

The alternative fits in a small file. An original drawing split into a handful of deliberate strokes, each one with an identity, a measured length, a start time and a reason for that start time. Five paths, about a second and a half, and a page that still explains itself when nothing moves.

What that gives you is an inspectable drawing mechanism — you can point at a stroke, say what it is, and change when it happens. It does not prove that a physical hand can produce the same drawing in one continuous take, and it is not a substitute for the finished illustration the director already approved.

Give each deliberate stroke its own identity

Start with a small original drawing whose meaning changes as the marks accumulate. This matters more than the drawing being pretty. If the image is recognizable from the first line and stays equally recognizable throughout, the reveal is decoration and the order is arbitrary. You want a drawing where the second stroke changes what the first one was.

A chair works. Before the seat exists, a rectangle is just a rectangle.

Give every stroke you intend to draw separately its own identifier, then inspect three things about it: where it starts, where it ends, and which way it travels. Direction is the part people skip. A path that runs right-to-left will reveal right-to-left, and no amount of timing will make it look like a left-handed start.

Here are five strokes, in SVG, with the identifiers attached:

<svg viewBox="0 0 320 260" width="320" height="260" role="img"
     aria-labelledby="s-title s-desc">
  <title id="s-title">A chair drawn as five separate strokes</title>
  <desc id="s-desc">Back panel, seat, left leg, right leg and cross-brace,
  each a separate path with its own identifier.</desc>

  <g class="stroke">
    <path id="back"  d="M90 45  H210 V155 H90 Z"/>
    <path id="seat"  d="M60 150 H245 V180 H60 Z"/>
    <path id="leg-l" d="M80 180 V245"/>
    <path id="leg-r" d="M225 180 V245"/>
    <path id="brace" d="M80 215 H225"/>
  </g>
</svg>

Two things to notice in that markup, both of which will matter later.

The first is that #back is a closed rectangle whose fourth side is created by the Z command. A closed outline is a fine final illustration, but as a drawing action it means the pen goes up the left side after coming back along the bottom — and the left side is the last thing to appear. If you wanted the back panel to read as two upright posts first, this path cannot do it. The path's shape and the hand's sequence are different pieces of information, and only one of them is written down in the geometry.

The second is that #back runs to y=155 while the seat's top edge sits at y=150. That puts five units of each back post below the seat's top edge, which gives you two genuine crossings — at (90,150) and (210,150) — where one stroke passes through another. Keep that. You will be looking at it shortly.

The awkward version, for comparison, is the same geometry in one path:

<g class="stroke">
  <path id="everything"
        d="M90 45 H210 V155 H90 Z
           M80 180 V245
           M80 215 H225
           M225 180 V245
           M60 150 H245 V180 H60 Z"/>
</g>

Every coordinate is identical, and the same group carries the same styling class, so the two versions reach the stylesheet by the same route. What changed is that the drawing is now one thing. The subpaths draw in the order they are written, which means the chair stands on legs for most of the reveal and only becomes a chair at the very end, when the seat finally arrives. There is no delay you can insert inside a single dash animation, no way to reverse one stroke without rewriting the whole d, and no way to give the back panel a different pace than the brace. That is an implementation fault, not a taste question — the structure physically cannot express the sequence.

Which stroke should come second is an editorial judgment, and you are entitled to a different one than mine. The two belong in different conversations.

Measure one stroke before you animate it

A dash pattern in SVG is a repeating sequence of painted and unpainted lengths laid along a stroked path. The numbers fall out of the coordinates rather than out of a guess: the back panel is 120 across the top, 110 down the right, 120 back along the bottom, and 110 up the left for the Z, which is 460. The getTotalLength() method on a path element is how you confirm that rather than re-deriving it by hand, and it returns a number in the SVG's user units — the same coordinate space as the viewBox, so it lines up with the numbers you wrote.

ID Shape Segments Length
back closed rectangle 120 + 110 + 120 + 110 460
seat closed rectangle 185 + 30 + 185 + 30 430
leg-l vertical 65 65
leg-r vertical 65 65
brace horizontal 145 145

Now set the dash pattern to that length twice — 460 460 — and set the dash offset to the same number. The painted portion is exactly as long as the path, the gap is exactly as long as the path, and the offset has slid the whole pattern backwards by one painted portion. At an offset of 460, the path sits entirely inside the gap and nothing is painted. At 0, the path sits entirely inside the dash and everything is painted. At 230, you are looking at exactly half a stroke.

Animate the offset from 460 down to 0 and the stroke draws itself. The duration is the pace; the length is the geometry. Keep them separate in your head and the rest of this is arithmetic.

.stroke path {
  fill: none;
  stroke: #111;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation-name: draw;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}

@keyframes draw {
  to { stroke-dashoffset: 0; }
}

/* Lengths from getTotalLength(). Duration = length, at one user unit
   per millisecond. Delay is the whole of the plan. */
#back  { stroke-dasharray: 460 460; stroke-dashoffset: 460;
         animation-duration: 460ms; animation-delay: 0ms; }
#seat  { stroke-dasharray: 430 430; stroke-dashoffset: 430;
         animation-duration: 430ms; animation-delay: 460ms; }
#leg-l { stroke-dasharray: 65 65;   stroke-dashoffset: 65;
         animation-duration: 65ms;  animation-delay: 1000ms; }
#leg-r { stroke-dasharray: 65 65;   stroke-dashoffset: 65;
         animation-duration: 65ms;  animation-delay: 1065ms; }
#brace { stroke-dasharray: 145 145; stroke-dashoffset: 145;
         animation-duration: 145ms; animation-delay: 1220ms; }

Two housekeeping notes, both of which will save you twenty minutes of confusion.

If you change stroke-dashoffset as a markup attribute and nothing moves, that is not a bug. Presentation attributes sit at the bottom of the cascade, below every CSS rule that touches the same property. The stylesheet above wins, so put the number in one place and change it there.

And leave pathLength unset for this study. Declaring it changes how dash values are interpreted against a declared length rather than the measured geometry, which adds a conversion your example does not need. The same instinct applies to percentages: in these two attributes a percentage resolves against the viewport, not against the path you are animating. stroke-dashoffset: 100% is not "fully hidden." Use plain numbers.

Put the strokes in an order your drawing can defend

The geometry tells you which lines exist. It does not tell you which line a person would make first. Three questions get you most of the way:

Which stroke creates the first moment of recognition? Which stroke is only meaningful once something else exists? Which stroke would a hand make without lifting?

Then write the answer down as a list before you write any more CSS. Four columns: identifier, start, duration, and what has to finish first.

Order ID Start Duration Ends Gate
1 back 0 460 460
2 seat 460 430 890 back complete
(hold) 890 110 1000 inspection pause
3 leg-l 1000 65 1065 seat complete
4 leg-r 1065 65 1130 leg-l complete
(hold) 1130 90 1220 inspection pause
5 brace 1220 145 1365 leg-r complete

The seat is second because the seat is the moment the drawing becomes a chair. The back panel alone is a rectangle; the two together are unmistakably furniture, and that is worth arriving at early. The hold after it exists so that a viewer who wants to look at the intermediate state has somewhere to look — a pause is not dead air, it is a place for the recognition to land.

The brace is last because the drawing already reads as a chair before it arrives. It is the smallest change to the meaning, so it gets a short beat: 145 milliseconds. The legs are shorter still, at 65 apiece. The pace is what stays constant, not the duration.

Notice that the durations are the lengths. At one user unit per millisecond, every stroke reveals at the same pace, which is what makes the whole thing read as one hand moving at one speed. If you had given every stroke the same duration instead — say 300 milliseconds each — the back panel would draw at about 1.5 units per millisecond and the legs at about 0.22. Same total time, seven times the difference in apparent speed. Equal duration is not equal speed, and unequal paths make the gap visible immediately.

The legs are the one place where the ordering is genuinely arguable. Run them sequentially, as above, and the drawing reads as one continuous hand. Give them the same delay, so both arrive together, and the drawing reads as a diagram assembling — symmetric, mechanical, faster. Neither is provably clearer. That is a question about your drawing, and the honest answer is that I have not put either version in front of a viewer, so pick the one that matches what the treatment is claiming and be ready to change it.

Slow it down and inspect the joins

Once the sequence exists, multiply every delay and every duration by five and watch it again. At a fifth speed the places where strokes meet stop being invisible.

Take the two crossings at (90,150) and (210,150). The back panel's posts finish five units below the seat's top edge, so in the finished still there are two short nubs sitting inside the seat's rectangle. Depending on your drawing, that is either a joint or a mistake. If it is a mistake, you have two honest repairs: shorten both posts to end at y=150 so they meet the seat's edge exactly, or fill the seat so the nubs are hidden behind it.

The second repair is where people get caught. stroke-dasharray and stroke-dashoffset reveal strokes. They do not reveal filled shapes, and they will not erase anything. A fill is a separate event with its own element and its own timing, and both timings have a visible cost. Bring the fill in when the seat stroke begins and a solid block appears where the seat will be, covering the posts before any outline exists to explain it. Bring it in when the seat stroke ends and the nubs vanish in a single frame. You are choosing which artifact to show, not avoiding one.

The brace meets the legs at (80,215) and (225,215), which are joins rather than crossings — two strokes arriving at the same point. With round caps, a join that overlaps by a few milliseconds shows a small bulge; a brace that starts before its legs exist floats in space under nothing. Gate it, as the timing list does.

When a stroke reads wrong, the fix is almost never more speed. A forty-millisecond cut is a fault you have hidden from yourself; the same fault at four hundred milliseconds is a note to fix. Four symptoms and their causes:

A stroke grows outward from its middle. The path starts at its midpoint. Redraw the d so it begins where a pen would land.

A stroke travels across empty space. Two strokes you meant to separate are subpaths of one path. Split them and give each its own delay.

A stroke never finishes appearing. The dash pair is shorter than the path's measured length, or a CSS rule is overriding your offset.

A stroke jumps between disconnected pieces. There is an M inside the path. That is a hard split, not a timing problem.

Make the study work when nothing is moving

Three things turn this from a demo into something you can hand to someone.

The stopped final state comes first. With animation-fill-mode: forwards, every offset finishes at 0 and the chair stays complete. There is no reason for a drawing of a chair to be invisible the moment you stop looking at it.

Replay comes second, and it needs to reset rather than resume. Restarting a CSS animation generally means removing the class that carries it, forcing the browser to recompute styles, and putting the class back:

const svg = document.querySelector('svg');
document.querySelector('#replay').addEventListener('click', () => {
  svg.classList.add('reset');              // animations off
  void svg.getBoundingClientRect().width;  // force recompute
  svg.classList.remove('reset');           // animations on again
});

with .reset .stroke path { animation: none; } alongside the rest. Whether that behaves cleanly in your target browser is a thing to check, not to assume — I have not run it.

The static explanation comes third, and it is the part that has to survive every failure. A labelled still of the same five paths with all offsets back at 0, each identifier named in draw order, one short line saying what the stroke is and when it arrives. That is ordinary page content. It does not depend on the animation, the script or the replay control, and it is what a reviewer reads when the file will not play.

Then the motion preference, which is a starting position rather than a certification:

@media (prefers-reduced-motion: reduce) {
  .stroke path {
    animation: none;
    stroke-dasharray: none;
    stroke-dashoffset: 0;
  }
}

Dropping the dash pattern entirely means the full stroke is painted regardless of offset — belt and braces. But that media query reports a preference. It does not tell you what someone can perceive, and matching it does not make the page accessible. The annotation text is doing the real work, and the animation is the extra.

When you do get it in front of a browser, the checklist is shorter than most people's and covers the things a screenshot review cannot. Whether the replay control is keyboard reachable and visibly focused. Whether replay resets to the first frame instead of resuming from wherever it stopped. And what the drawing looks like when the run is interrupted — a hidden tab, a preference switched mid-draw, a script that throws after the third stroke. The endpoint after an interruption is the state most likely to be wrong and least likely to be photographed.

What the study establishes

Take it back to the board and it does one specific job: it shows the order. The chair arrives as a back, then a seat, then legs, then a brace, and every one of those beats was a decision you can point at and defend.

Two references informed the attribute behaviour described here — MDN's pages for stroke-dasharray and stroke-dashoffset, checked on 13 September 2026. Both describe how a stroked path is rendered. Neither says anything about the order in which a drawing should be made, and neither is a test of this example. The chair above is a proposed asset. Its coordinates are worked out and its arithmetic checks, but it has not been rendered in a browser, timed against a viewer, or shown to anyone. The millisecond figures are a starting position, not a result.

So open the file, watch it once at full speed and once at a fifth, and write down which stroke reads wrong. That is the actual deliverable. When the leg grows outward from its middle, or the brace floats before its legs exist, or the seat arrives before the back it sits behind, you can name the stroke, find its identifier and change one line. A wipe across a finished illustration gives you nothing to fix.

Frequently asked questions

Why split a line drawing into strokes instead of animating a finished illustration with a wipe?

A finished illustration is a destination; the sequence is the journey the treatment is promising. A wipe across the illustration promises a hand and delivers a curtain. An original drawing split into a handful of deliberate strokes gives each line an identity, a measured length, a start time, and a reason for that start time. That makes the drawing mechanism inspectable, but it does not prove a physical hand could produce the same drawing in one continuous take, and it is not a substitute for the finished approved illustration.

Why does stroke direction matter, and what makes a drawing suitable for this?

Give every separately drawn stroke its own identifier and inspect where it starts, where it ends, and which way it travels. A path that runs right-to-left will reveal right-to-left, and timing will not make it look like a left-handed start. The drawing should change meaning as marks accumulate; if it is recognizable from the first line and stays equally recognizable throughout, the reveal is decoration and the order is arbitrary. A chair works because before the seat exists, a rectangle is just a rectangle.

How do you measure a stroke and set up the dash animation?

Compute each path’s length from its coordinates or confirm it with getTotalLength(). In the example: back is 460, seat 430, each leg 65, brace 145. Set stroke-dasharray to the length twice, set stroke-dashoffset to the same length, then animate the offset down to 0. At offset 460 the stroke is hidden; at 230 it is half drawn; at 0 it is fully painted. Duration is pace; length is geometry. At one user unit per millisecond, duration equals length. Leave pathLength unset, and use plain numbers rather than percentages for these dash attributes.

How do you decide the order of strokes?

Geometry tells you which lines exist, not which line a person would make first. Ask which stroke creates the first moment of recognition, which stroke is only meaningful once something else exists, and which stroke a hand would make without lifting. In the chair example, the back comes first, the seat second because that is when the drawing becomes a chair, then the legs, then the brace last. The legs are genuinely arguable: sequential reads as one continuous hand; simultaneous arrival reads as a diagram assembling. Neither is provably clearer, and the example has not been put in front of a viewer.

What should happen when the animation is stopped, replayed, or reduced?

The stopped final state should remain complete, which fill-mode forwards provides. Replay needs to reset rather than resume; the example uses removing a class, forcing a style recompute, and adding the class back, but says to check the target browser rather than assume. The static explanation is the part that must survive every failure: a labelled still of the same five paths with offsets at 0, identifiers in draw order, and one short line saying what each stroke is and when it arrives. A prefers-reduced-motion query can remove the animation and dash pattern, but that reports a preference and does not make the page accessible; the annotation text does the real work.

More in Advertising Browse all articles