Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 13.2Intermediate ~25 min · shot 2 of 6

Drawing Lines

In one line

A line draws itself when its stroke is one dash as long as the path and the dash is slid into view with stroke-dashoffset. pathLength="1" makes that length 1 for every path, and three traps (round caps, gaps in the path, dashed strokes) need known fixes.

0/6 · 0%

Think of it like this

A garden hose lying on the lawn with a long sleeve of paint on it, the sleeve exactly as long as the hose. Slide the sleeve off the far end and the hose is bare. Slide it back and the hose appears, starting from the near end. Drawing a line in SVG slides a dash along the path like that sleeve.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

stroke
The outline of a shape, or the line of a path. Its colour is stroke and its thickness stroke-width. A path with fill: none is only a stroke.
stroke-dasharray
The pattern of drawn and empty lengths along a stroke. 1 alone means 1 drawn, 1 empty, repeating.
stroke-dashoffset
How far the dash pattern is slid backwards along the path. Offset equal to the dash length moves the first dash out of sight.
pathLength
An attribute that tells the browser to treat the path as this many units long for dashing, so a dash of 1 always means the whole path.
getTotalLength()
A method of path elements: the real length in user units. It ignores the pathLength attribute.
stroke-linecap
The shape of the stroke's ends: butt (flat, the default), round or square. A round cap draws a dot even for a dash of length 0.
mask
An SVG element that hides parts of whatever uses it: white parts show, black parts hide.

Step by step

01Draw the route

Press Draw again on "A route drawing itself". The console says getTotalLength(): 385.20, pathLength attribute: 1, dasharray 1px, resting dashoffset 0px. Pause at 0.1× and step: the Web Animations track is stroke-dashoffset, 1400 ms, ease-in-out, with an amber (paint) cost mark.

02Read the offset at the half way point

Pause at 700 ms. The stroke-dashoffset track's graph is at 0.5 and half of the line is drawn. ease-in-out is symmetric, so the middle of the time is the middle of the line.

03Find the dot

In "Why a rounded number leaves a dot" the first box (A) is empty, the second (B) shows a dot at the start of the line, and the third (C) shows one too. The console says why: 0.20 of a dash starts B, and C has a dash of length 0 with a round cap.

04Compare two pieces and a dashed line

Press Draw in "Two pieces and a dashed line". Pause at 300 ms: in the top row both pieces have grown by 60 units. In the middle row only the first piece is drawn, and the second waits for its 450 ms delay. The blue dashed line grows with the mask.

Try it yourself

  1. 1

    Remove pathLength

    In the first example delete pathLength="1" from the <path> and press Draw again. Pause at 700 ms: the full line is already on screen and stroke-dashoffset reads 0.5. The line never draws, because 1 is now a 1-unit dash on a path 385 units long.

  2. 2

    Make B clean

    In the second example change #b's offset from 384.8 to 385: the dot at the start of B goes. Then put path { stroke-linecap: butt; } at the end of the CSS: B and C show nothing either, because a flat-ended dash of 0.2 units, or of length 0, has no visible area.

  3. 3

    Reorder the pieces

    In the third example change the delay of p2 from 450 to 0. Both middle pieces now grow together, like the top row. The top row is one path, so it can never do the sequence.

Code & diagrams

A route drawing itself

The resting state is the finished line. The animation starts from offset 1 (empty) and ends at 0.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

getTotalLength(): 385.20 user units
pathLength attribute: 1
dasharray 1px, resting dashoffset 0px
Why a rounded number leaves a dot

Round caps make any dash with length, or none, show as a dot. Press **Draw A** to draw the clean one. The button uses `fill: "forwards"` only so the demo stays drawn; Topic 8.4 says why the project should not.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

the path is 385.20 long
A (offset 385): the whole line is in the gap, nothing shows
B (offset 384.8): 0.20 of a dash starts the line, so a round cap shows
C (dasharray 0): a zero-length dash still gets a round cap
Two pieces and a dashed line

Row one: one path, two pieces, drawn together. Row two: two paths, the second delayed by 450 ms. Row three: a dashed line uncovered by a mask whose path draws.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

one path, two pieces: total 200 = 90 + 110

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Hide the dashed line's mask with the default units

In the third example delete maskUnits="userSpaceOnUse" x="0" y="0" width="300" height="120" from the <mask> and press Draw.

terminal
$ Press Draw and pause at 1000 ms
── what you'll see ──
The two orange rows draw, and the blue dashed row never appears, at any time.

Break #2

Hide with a rounded offset

In the second example set #a { stroke-dashoffset: 384.8; } (it is 385 now).

terminal
$ Look at box A
── what you'll see ──
A dot appears at the start of the line, exactly like B. Pressing **Draw A** still draws the line, but it already looked started before the click.

Myth vs fact

Myth

You must call getTotalLength() and write the number into the CSS.

Fact

Only without pathLength. With pathLength="1" every path uses 1 and 0, in CSS alone.

Myth

A path with several pieces draws like one line.

Fact

The pattern restarts on each piece, so they draw together. Use one path per piece, with delays.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The dash trick draws a path by length, so easings apply to the length: with ease-in the line starts slowly and the end rushes. For a route that follows the scroll (Topic 13.6) use linear, so the line tracks the finger.

  • ▸

    stroke-dasharray: 0 12 with round caps draws a row of dots (the dot of box C), and a mask uncovers it like a dashed line.

Remember this

  1. 1

    The problem this solves: CSS cannot animate "how much of a line is drawn". There is no draw: 50%. But every stroke can be made of dashes, and the dash pattern can be moved. That is enough to draw a logo, a route or a signature, with no JavaScript and no video.

  2. 2

    The two properties. stroke-dasharray sets the pattern of dash and gap lengths, for example 10 4 (10 drawn, 4 empty, repeating). stroke-dashoffset slides the pattern backwards along the path by that distance. Make one dash as long as the whole path (stroke-dasharray: L) and then offset it by L: the dash has slid off the start and the line looks empty. Animate the offset from L to 0 and the line draws from its start to its end.

  3. 3

    **pathLength="1" removes the measuring.** getTotalLength() gives the real length in user units: 385.20 for the route in the first example. You would have to measure every path and write that number in the CSS. The attribute pathLength="1" tells the browser to treat the path as 1 unit long, whatever it is really, and dash lengths then use that scale. Now every path uses stroke-dasharray: 1 and an offset from 1 to 0. getTotalLength() still says 385.20, because it ignores the attribute. In the project, keep the resting state drawn (stroke-dashoffset: 0) and animate only from 1, so that a browser or a user that skips the animation shows the finished line.

  4. 4

    Rounded caps draw a dot. With stroke-linecap: round, any dash that has length, even a tiny one, gets round ends. In the second example the path is 385.20 long. Dash 385 with offset 385 hides everything, but offset 384.8 leaves 0.20 of a dash at the start and a round cap shows as a dot. A dash of length 0 (stroke-dasharray: 0 400) shows a dot too, and stroke-linecap: butt shows nothing for the same values. Hide with an offset equal to the dash, and prefer pathLength="1", so no rounded number can be off by a little.

  5. 5

    **Without pathLength, 1 is one unit of a very long path.** With stroke-dasharray: 1 the line is 1 drawn and 1 empty over 385 units, and a 5-unit round stroke fills the gaps: the line is there all the time and never draws (tryIt 1).

  6. 6

    Gaps in the path. A path with two pieces (M20 20 H110 M170 20 H280) is measured as one length: 200, which is 90 plus 110. But the dash pattern restarts at every piece. One offset therefore draws both pieces at once, each from its own start. To draw them in turn, use one <path> per piece and delay the second by the length of the first (450 ms of a 1000 ms draw in the third example: 90 is 45 percent of 200).

  7. 7

    Dashed strokes cannot use the trick, because the dash pattern is already used for the dashes. Draw a solid path inside a <mask> instead and give the real dashed line mask="url(#reveal)". The mask path draws and the dashed line is uncovered as it grows. Set maskUnits="userSpaceOnUse" with a real size: the default mask area is the box of the element that uses it, and a horizontal line has no height (breakIt 1).

  8. 8

    Measuring needs no screen, drawing does. getTotalLength() gave 242.48 for one test curve in the page, inside <defs> and inside a display: none group, and 0 for a path that is only M20 20 or has an empty d: a length of 0 means nothing to draw, not an error. A display: none path shows no dash at all.

  9. 9

    On the timeline. stroke-dashoffset measured as paint (amber): the browser draws the stroke again on each frame, on the main thread. That is fine for a logo or one route, but it cannot be pushed to the compositor like transform. Do not draw 200 lines at once on a phone without checking the track and the frame strip.

Explain it without notes

01

How does the stroke-dasharray and stroke-dashoffset trick draw a line, and why does pathLength="1" help?

02

Why does a round-capped line show a dot before it starts to draw, and how do you prevent it?

Practice

01

Draw a path #underline under a heading once, in 600 ms with ease-out, with a finished line as the resting state and no drawing for reduced motion.

02

A logo has two separate pieces (a ring and a bolt) in one <path>. Draw them one after the other.

Done when you can

  • You can draw a line with stroke-dasharray, stroke-dashoffset and pathLength="1"

  • You can say why a round cap shows a dot and how to hide the line cleanly

  • You can draw two pieces in sequence and a dashed line with a mask

  • You know the draw costs paint on the timeline