Back to Learn SVG Animation

SVG Line Drawing Animation: A Practical Guide

By Published Updated education

SVG line drawing animation reveals a stroked path by animating stroke-dashoffset from the path’s full length to zero. Use pathLength or JavaScript measurement for accurate timing.

How SVG line drawing animation works

An SVG line-drawing effect does not redraw the geometry. It changes how the browser paints a stroke. The stroke-dasharray property defines alternating painted and unpainted distances, while stroke-dashoffset shifts that pattern along the shape. Give the stroke one painted dash at least as long as the geometry, offset that dash by the same distance so the path begins hidden, and animate the offset to zero. The moving dash looks like a pen revealing the line.

The path data never changes, so this technique is easier to maintain than morphing. Set fill to none when the effect should reveal only an outline, and choose a clear stroke color, width, line cap, and line join. Rounded caps often suit signatures and friendly illustrations; butt caps make technical diagrams feel more exact. If you need a broader introduction before this pattern, start with your first SVG animation with CSS.

Prefer unitless dash values for path-relative work. Percentages are easy to misread: the SVG specification resolves percentage dash values against the current viewport, not simply against the path length. That is a common reason a copied animation works in one viewBox but breaks in another.

A minimal normalized example

The most portable authoring shortcut is to give the path a declared length of 1. Then 1 means the entire path for the stroke calculations, regardless of the curve’s actual dimensions.

<svg class="line-demo" viewBox="0 0 320 120"
     role="img" aria-labelledby="line-title">
  <title id="line-title">A curved route drawing from left to right</title>
  <path class="draw-path"
        pathLength="1"
        d="M20 92 C80 18 150 18 300 88" />
</svg>
.draw-path {
  fill: none;
  stroke: #5b5bd6;
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: draw-line 1200ms cubic-bezier(.22, 1, .36, 1) forwards;
}

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

A one-value dash array is repeated as a dash and a gap. With pathLength set to 1, both occupy one complete author-defined path length. The offset initially moves the painted section away; the keyframe returns it. The forwards fill mode keeps the final line visible rather than snapping it back to its hidden state.

Choose a reliable path-length strategy

Hardcoded dash values can be acceptable for a tiny one-off icon, but guessed numbers become fragile when artwork changes. Use one of two explicit strategies: normalize authored geometry with pathLength, or measure the rendered geometry with JavaScript.

Normalize with pathLength

The pathLength attribute calibrates the browser’s distance calculations to an author-supplied total. The SVG 2 specification confirms that this calibration affects stroke dashing, including dash array and offset. A value of 1 makes reusable CSS especially clean: every participating path can start at an offset of 1 and end at 0.

This approach works well for static illustrations, icon systems, and component libraries because designers may alter a curve without requiring a new magic number. It also works on several basic SVG shapes, not only paths. Avoid percentage dash values here because pathLength does not redefine percentage distance calculations.

Measure with getTotalLength()

For imported artwork, unknown geometry, or timing based on physical length, call getTotalLength(). It returns the browser’s computed path length in user units. Measure after the inline SVG exists in the document, and treat zero or non-finite results as an asset error rather than starting a broken animation.

const paths = document.querySelectorAll('[data-draw]');

for (const path of paths) {
  const length = path.getTotalLength();

  if (!Number.isFinite(length) || length <= 0) {
    continue;
  }

  path.style.setProperty('--path-length', String(length));
  path.classList.add('is-measured');
}
[data-draw].is-measured {
  fill: none;
  stroke-dasharray: var(--path-length);
  stroke-dashoffset: var(--path-length);
  animation: measured-draw 1100ms ease-out forwards;
}

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

The final stroke remains the no-JavaScript fallback; the script enhances it when measurement succeeds. For above-the-fold artwork, run the small initializer as soon as the inline graphic is available to minimize a flash of the completed line. Do not repeatedly measure unchanged paths during every frame or resize.

Control playback with CSS or the Web Animations API

CSS is the best default for a one-time, predictable reveal. It is concise, inspectable, and needs no animation dependency. JavaScript becomes useful when the draw must replay, pause, reverse, wait for application state, or calculate duration from content. The native Element.animate() method creates and immediately plays a Web Animation and returns an Animation object with playback controls.

This version measures a path, honors reduced motion before starting, and commits the completed visual state after playback:

const path = document.querySelector('[data-waapi-draw]');
const reduceMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;
const length = path.getTotalLength();

path.style.strokeDasharray = String(length);

if (reduceMotion) {
  path.style.strokeDashoffset = '0';
} else {
  path.style.strokeDashoffset = String(length);

  const animation = path.animate(
    [
      { strokeDashoffset: String(length) },
      { strokeDashoffset: '0' }
    ],
    {
      duration: 1400,
      easing: 'cubic-bezier(.22, 1, .36, 1)',
      fill: 'forwards'
    }
  );

  animation.finished.then(() => {
    path.style.strokeDashoffset = '0';
    animation.cancel();
  });
}

Keep the motion layer separate from application state. A replay button should restart one known animation, not clone new animations on every click. If a full sequencing library is already part of the project, compare the trade-offs before adding more code; for one line, CSS or WAAPI is normally sufficient.

Animate multiple paths in a deliberate sequence

Complex drawings are usually several paths, and revealing all of them at once can look like an accidental wipe. Group segments by meaning, then stagger them in the visual order a person would naturally draw or read them. The DOM order is a convenient default, but it is not automatically the right storytelling order.

<path class="draw-segment draw-segment--1" pathLength="1"
      d="M20 80 C70 20 120 20 160 70" />
<path class="draw-segment draw-segment--2" pathLength="1"
      d="M160 70 C205 115 250 105 300 35" />
<path class="draw-segment draw-segment--3" pathLength="1"
      d="M292 36 L304 34 L301 47" />
.draw-segment {
  fill: none;
  stroke: currentColor;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: draw-segment 700ms ease-out var(--delay) forwards;
}

.draw-segment--1 { --delay: 0ms; }
.draw-segment--2 { --delay: 420ms; }
.draw-segment--3 { --delay: 840ms; }

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

Overlap adjacent segments slightly so the illustration keeps moving instead of stopping between pieces. If path lengths vary dramatically, measure them and assign longer durations to longer paths; a nearly constant drawing speed usually feels more natural than identical durations. See how sequencing changes the result in a self-drawing navigation logo, a headline underline sweep, and a scroll-triggered stat line.

Respect reduced motion and preserve meaning

Line drawing is normally decorative enhancement, so the reduced-motion version should show the useful final state immediately. The prefers-reduced-motion media feature detects a user request to reduce non-essential motion. Put the override after the main animation rules so equal-specificity declarations win.

@media (prefers-reduced-motion: reduce) {
  .draw-path,
  .draw-segment,
  [data-draw].is-measured {
    animation: none;
    transition: none;
    stroke-dashoffset: 0;
  }
}

Apply the same decision in JavaScript before calling WAAPI, as the earlier example does. If your interface stays open while system preferences can change, listen for changes on the media query, cancel active motion, and reveal every stroke. The site’s guide to respecting prefers-reduced-motion covers the broader pattern.

Decide whether the graphic is meaningful or decorative

Animation must not be the only way to communicate a route, status, signature, or process. If nearby text already conveys the same information, remove a decorative SVG from the accessibility tree. If the graphic adds meaning, give it an image role and a concise accessible name; add a description only when it helps. W3C’s SVG accessible-name rule expects an SVG with an explicit image role to have a non-empty accessible name.

<svg aria-hidden="true" focusable="false" viewBox="0 0 320 120">
  <path class="draw-path" pathLength="1" d="..." />
</svg>

<svg role="img" aria-labelledby="map-title map-desc"
     viewBox="0 0 320 120">
  <title id="map-title">Walking route to the west entrance</title>
  <desc id="map-desc">The route starts at the station and crosses Oak Street.</desc>
  <path class="draw-path" pathLength="1" d="..." />
</svg>

Do not announce every animated segment to a screen reader. The accessible name should describe the finished graphic, which remains true before, during, and after the visual effect.

Performance, testing, and troubleshooting

Stroke animation can be lightweight, but it is still painted work. MDN’s animation performance guide notes that code-based animation can consume CPU and cause jank even when its download size is small. Avoid promising compositor-only performance for stroke-dashoffset. Test the actual asset and surrounding page.

  • Simplify exported geometry and remove invisible, duplicate, or microscopic paths.
  • Limit the number of strokes animating at the same time, especially on mobile.
  • Avoid combining the draw with large blurs, filters, masks, and continuously moving backgrounds unless profiling supports it.
  • Run one purposeful reveal rather than an infinite loop, and stop off-screen work.
  • Measure each stable path once, cache the result, and reuse it for replay.

The practical SVG animation performance checklist is a useful companion when the illustration grows beyond a few paths.

Test the complete experience

  • Load with JavaScript enabled and disabled; meaningful artwork must remain understandable.
  • Enable reduced motion at the operating-system level and confirm the final line is visible without a transition.
  • Check current Chrome, Firefox, and Safari at narrow and wide sizes and at browser zoom.
  • Record a performance trace on a representative phone, not only a fast desktop.
  • Keyboard-test replay or trigger controls and give them clear visible focus.
  • Verify that changing the viewBox or responsive size does not expose extra dashes.

Diagnose common failures

  • Nothing appears: confirm the stroke is not none, the width is positive, the path has valid geometry, and the final offset can reach zero.
  • The line starts partly visible: the dash is shorter than the real path, or a percentage is being resolved against the viewport. Normalize or measure it.
  • The fill appears before the outline: set fill to none, or animate a separate fill only after the stroke completes.
  • The draw runs backward: the effect follows path-data direction. Reverse the path in the source tool or intentionally animate from the opposite offset.
  • A closed path shows a seam: move the path’s start point to a less noticeable location and check the line cap and join.
  • Several paths finish unevenly: base duration on measured length or split artwork into visually balanced stages.
document.querySelectorAll('[data-draw]').forEach((path, index) => {
  const styles = getComputedStyle(path);

  console.log({
    index,
    length: path.getTotalLength(),
    stroke: styles.stroke,
    strokeWidth: styles.strokeWidth,
    dasharray: styles.strokeDasharray,
    dashoffset: styles.strokeDashoffset
  });
});

This small diagnostic exposes the values the browser is actually using. It often reveals a zero-length path, an overridden CSS property, or a dash value copied from different geometry.

Quick answers

Does line drawing work only on paths?

No. Stroke dashing applies to paths and several basic shapes, including lines, circles, ellipses, rectangles, polygons, and polylines. Paths remain popular because they can represent almost any outline and have a familiar length-measurement API.

Should I use pathLength or getTotalLength()?

Use pathLength when you control static markup and want one reusable 0-to-1 animation. Use getTotalLength() when artwork is imported, generated, frequently changed, or needs timing proportional to actual geometry. Both remove guesswork; choose the one that best fits the content pipeline.

Can the effect start when it enters the viewport?

Yes. Use IntersectionObserver to add the animation class or start a paused Web Animation once the graphic intersects. Keep the completed image as the fallback, avoid retriggering on every small scroll, and disconnect a once-only observer after it starts.

Can I reveal a fill after the line?

Yes. Keep stroke and fill as separate stages: draw the outline, then fade in the fill with a short overlap. In reduced-motion mode, show both immediately. Avoid hiding essential text or controls until decorative motion finishes.

What is the production-ready default?

For most illustrations: inline clean SVG, pathLength set to 1, a one-time CSS animation from offset 1 to 0, a static reduced-motion state, and a meaningful or decorative accessibility decision. Add JavaScript only for measurement, interaction, scroll triggers, or sequencing that CSS cannot express clearly.

Final implementation checklist

  • Preserve the original path geometry and animate stroke painting rather than path data.
  • Normalize or measure every path instead of guessing dash lengths.
  • Keep the finished graphic visible after playback and when motion or JavaScript is unavailable.
  • Sequence multiple paths according to visual meaning, with restrained overlap.
  • Test accessibility, reduced motion, responsive sizing, browser behavior, and real-device performance before release.

The core idea is simple: hide one full-length painted dash with an equal offset, then animate that offset to zero. The production quality comes from accurate length handling, intentional sequencing, an accessible static result, and testing the real artwork under the conditions your users will encounter.

Sources and further reading

Continue learning
Guide

SVG Animation with the View Transition API: 2026 Guide

Build accessible SVG transitions with the View Transition API. Covers browser support, same- and cross-document patterns, fallbacks, performance, and testing.