tool / svg line drawingcss · no javascript

SVG line drawing generator

Paste any SVG and get it back as one self-contained file that draws itself, line by line, with CSS and no JavaScript. Set the timing, the order, a loop and a hand-drawn feel, watch it play, then download the file, copy the CSS for a page, or take the scroll version.

Shapes
7
Easings
5
Loops
3
Outputs
3
<path pathLength="1">stroke-dashoffset: 0.600.41the dash pattern, unrolled along the pathoffset 0.6the pathdash 1gap 2pathLengthuser units000.4164.81411.9× 411.9
Fig. 1 — with pathLength="1" the line is 1 long in dash units, whatever it measures (411.9 here). A dash of 1 and a gap of 2, pulled back by the offset: at 0.6 the dash covers 0 → 0.4 of the path. At 1 it covers none of it, at 0 all of it.
01The tool

Make your line drawing

The sample is a house on a hill drawn for this page from all 7 shapes, 14 of them, with a door and a sun whose fills fade in once their lines are drawn. The preview is the file you download, playing in this page. Nothing is uploaded: the SVG is read in your browser, and only its drawing elements and attributes are kept.

duration · 1.6s
stagger · 0.12s
easing
hand-drawn · off
order · document
loop
hold · loops only
fill fade-in · no fills
seed
input
stroke
stroke width · keep
02The technique

How pathLength draws it

A line drawing animates the stroke’s dash. The stroke becomes one dash with a long gap after it, and stroke-dashoffset slides the pattern along: pushed back by the whole line, nothing shows; at 0, all of it (fig. 1). pathLength="1" tells the browser to treat the shape as 1 long when it reads dash values, and to scale them by the shape’s real length itself. So every shape, long or short, takes the same numbers, and one rule draws them all. This is what the generator writes for one shape:

drawing.css20 lines
/* Every shape has pathLength="1", so it is 1 long in dash units:
   a dash of 1 and a gap of 2, pushed back by 1, hide it; 0 draws it. */
.line-drawing :is(path, line, polyline, polygon, rect, circle, ellipse) {
  stroke-dasharray: 1 2;
  animation: line-drawing-draw 1.6s ease-in-out both;
}

@keyframes line-drawing-draw {
  from { stroke-dashoffset: 1; }
  99.9% { stroke-dasharray: 1 2; }
  to { stroke-dashoffset: 0; stroke-dasharray: none; }
}

/* Less motion asked for: the finished drawing, at once. */
@media (prefers-reduced-motion: reduce) {
  .line-drawing :is(path, line, polyline, polygon, rect, circle, ellipse) {
    animation: none;
    stroke-dasharray: none;
  }
}

Before pathLength, the numbers came from JavaScript: each shape measured with getTotalLength() and its own dash set from the result.

measure.js8 lines
// Without pathLength: measure every shape, then animate that length.
document.querySelectorAll('#drawing path').forEach((path) => {
  const length = path.getTotalLength();
  path.style.strokeDasharray = `${length} ${length * 2}`;
  path.style.strokeDashoffset = length;
});
// It needs a script, it runs only once the SVG is in the page,
// it measures again whenever a path changes, and in an <img> it cannot run.
Stagger
In a single run each shape carries its place in the order as --i, and animation-delay multiplies it by the stagger, so changing one number in the CSS re-times the whole drawing. A hand-drawn feel adds --d, a seeded factor on each shape’s duration. A fill fades in from fill-opacity: 0 to its own value once its line is drawn. A loop gives each shape its own keyframes instead, named in --n.
036 checks

What the browsers do

Each of these was measured in the three engines, Chromium, Firefox and WebKit (Safari’s engine), by drawing the shape and counting its pixels. They are why the output looks the way it does.

CaseIn the browsersThe generator
pathLength="1" on path, line, polyline, polygon, rect, circle, ellipseAll three draw every shape whole at offset 0, half at 0.5 and not at all at 1, counted in pixels against the same shape with no dash.Puts pathLength="1" on every shape. Nothing is measured.
stroke-dasharray: 1 at offset 1The next dash begins exactly where the line ends. WebKit draws it: a dot at the corner of a closed, round-capped triangle. Chromium and Firefox show nothing.Writes 1 2, a gap twice the line, so the next dash is far past the end in every engine.
A closed shape’s first cornerWhile the stroke is dashed, a closed shape can lose the join where its outline starts and ends, so a mitred corner loses its tip: a polygon in all three, a rect in WebKit.Drops the dash at 99.9% (stroke-dasharray: none, a step at the very end), so the corner is whole once drawn.
vector-effect="non-scaling-stroke"The dash is then measured on screen, not along the path: at offset 1 part of the line still shows. In all three.Takes it off, and says so. The stroke then scales with the drawing.
The file as an <img> or CSS backgroundThe CSS animation inside runs in all three, both ways, and currentColor is black. The reduced-motion rule inside applies under the browser’s own setting in Chromium and Firefox; WebKit could not be set to it here.Puts the animation and the reduced-motion rule inside the file, with no script.
scrollDraw on a shape that keeps pathLength="1"scrollDraw sets the dash to the shape’s real length, which pathLength then scales up: the line is whole from the first moment of scroll.Gives the scroll version its markup without pathLength.
Checked
On 11 November 2026 in Playwright 1.65: Chromium 158, Firefox 159 and WebKit 27.2.
04Time or scroll

On time or on scroll

The file plays over time, when it loads. To draw as the reader scrolls instead, the Scroll version tab gives the same drawing to scrollDraw: each line takes a stretch of the scroll, and the lines overlap as they do in the CSS. Here is how each setting carries over.

SettingThe CSS filescrollDraw
durationanimation-durationNone: the scroll sets the pace. The drawing starts when its top is 85% down the window and its last line lands at 35%.
staggeranimation-delay, one stagger per place in the orderstagger, as stagger ÷ duration of the trigger window: 0.12s of 1.6s is 0.075.
easingThe CSS keywordThe library’s easing of the same name. Its ease-in and ease-out are quadratics, close to the CSS curves; CSS ease becomes ease-out, the nearest.
orderAny: --i is each shape’s placeDocument order only.
looprepeat or alternateScrolling back undraws it; once: true keeps it drawn.
fill fade-inAfter each line, back to its own fill-opacityfillOpacity: [0, 1] as each line draws, or [0, x] when every fill has the same fill-opacity x. It sets one value on every shape, so fills that differ, like the sample’s door and sun (1 and 0.18), are left as they are.
hand-drawnEach shape its own durationNot carried over: every line gets the same stretch of scroll.
pathLengthOn every shapeLeft off: scrollDraw measures every shape itself.
main.js14 lines
// <div id="drawing"> + the markup below + </div>

import { scrollDraw } from 'svg-scroll-draw';

// Draws as the page scrolls: it starts when the drawing's top is 85% down
// the window and the last line lands at 35%. Scrolling back undraws it.
// The fills are left as they are: fillOpacity sets one opacity on every shape,
// and these fills have 2 (1, 0.18).
scrollDraw('#drawing', {
  selector: 'path, line, polyline, polygon, rect, circle, ellipse', // the default has no ellipse
  easing: 'ease-in-out',
  stagger: 0.075,
  trigger: { start: 'top 85%', end: 'top 59.7%' },
});
057 questions

Line drawing questions

Q1

How do I make an SVG line drawing animation?

Give every shape pathLength="1", set stroke-dasharray: 1 2 and animate stroke-dashoffset from 1 to 0, with an animation-delay on each shape so they draw one after another. pathLength makes every line 1 long in dash units, so one CSS rule draws them all. The generator above writes that CSS into the SVG itself, so the file draws itself with no JavaScript.

Q2

How do I animate an SVG stroke with CSS only?

Animate stroke-dashoffset in a CSS @keyframes rule. The stroke is cut into one dash with a long gap after it, and the offset slides the dash along: pushed back by its whole length nothing shows, at 0 the whole line does. Without pathLength you need each shape’s length from getTotalLength() in a script; with pathLength="1" every length is 1 and the CSS needs no numbers from JavaScript.

Q3

Does pathLength work on circle, rect and the other shapes?

Yes. pathLength="1" on path, line, polyline, polygon, rect, circle and ellipse was checked in the Chromium, Firefox and WebKit engines: each shape draws whole at offset 0, half at 0.5 and not at all at 1. Two things break it: vector-effect="non-scaling-stroke", which measures the dash on screen, and a gap no longer than the line, which in WebKit shows a dot at the end. The generator removes the first and writes a gap of 2.

Q4

Will the animation play when the SVG is used as an <img> or a background image?

Yes. The animation is CSS inside the file, and CSS animations run in an SVG image; a script would not. Two differences from an inline SVG: currentColor is black, because an image does not take the page’s text colour, so choose a stroke colour for a file you use that way; and the reduced-motion rule inside follows the browser’s setting, which was checked in Chromium and Firefox.

Q5

How do I make an SVG line animation loop?

Choose repeat or alternate under loop. repeat draws the whole drawing, holds it, clears it and draws it again; alternate undraws it in reverse, last line first. Each shape gets its own @keyframes over the whole cycle, because with one shared rule and animation-delay every line would restart on its own schedule and the drawing would never clear at once.

Q6

Why is my SVG line drawing not animating?

The usual reasons: the shapes have no stroke, so there is nothing to draw; a fill covers the line before it draws; vector-effect="non-scaling-stroke" is on; the stroke was set in a class or a <style> block that did not come with the SVG; or pathLength is still on the shapes when a script such as scrollDraw sets the dash itself, which draws the whole line at once. The generator warns about the first four and leaves pathLength out of its scroll version.

Q7

How do I draw the SVG on scroll instead of over time?

Use the Scroll version tab: the same drawing as markup without pathLength, and one scrollDraw call from svg-scroll-draw that draws each line as the page scrolls, with the same overlap as the timed version. scrollDraw measures every shape itself, so the dash is in the SVG’s own units.

Measure and check the paths first in the SVG path viewer, or turn shapes into paths with the shape to path converter. All of svg-scroll-draw 2.23.3: the docs.