Home

Fun with stroke-dasharray

27 September 2023

The stroke-dasharray attribute in SVG is more flexible compared to the dashed style in CSS. While there are numerous articles and examples about its usages, including Line animations, Pie charts, Circular progress bars, or even Hamburger menu icons, there's still something new to explore.

Unlike CSS, stroke-dasharray accepts multiple values, which will be picked cyclically according to the stroke length. Imagine it as an infinite repeated value sequence, the odd-numbered positions are dashes, and the even-numbered positions are gaps.

stroke-dasharray: 1 2 1;

/* which means */
stroke-dasharray: 1 2 1 1 2 1 1 2 1 ...

pathLength

The total length of a line stroke or path determines where the dashes end. To precisely control the endpoints, you may want to use the getTotalLength() method to get the exact length with JavaScript.

Manual estimation of path length is possible for relatively simple shapes. Though the actual result may differ between browsers.

  • Path length ≈ 2π * 5

You know what?

There's the lesser-known pathLength attribute for manually specifying the length value, browsers will handle the calculations automatically. Sometimes it's convienient to set an arbitrary value and don't rely on JavaScript at all. I first learned it from Amelia Bellamy-Royds in this comment.

svg {
  viewBox: -9 -9 18 18;
  circle {
    r: 8;
    fill: none;
    stroke: #000;
    stroke-dasharray: 1 2 1;
    pathLength: .99;
    animate {
      attributeName: pathLength;
      repeatCount: indefinite;
      values: 1;100;1;
      dur: 20s;
    }
  }
}

But still, each browser performs slightly different for pathLength and Firefox does the best. I guess Chrome has a bug at calculation, that's why .99 was used instead of 1 in the above example.

Nevertheless, it's easy to control the dashes with customized pathLength. Setting the pathLength to 40 in a rect and each side gets 10.

svg {
  viewBox: 0 0 50 50 p .5;
  rect {
    width, height: 50;
    fill: none;
    stroke: #000;
    pathLength: 40;
    stroke-dasharray: 2 8;
    stroke-dashoffset: 1;
  }
}

Interesting Shapes

Some interesing shapes can be made by tweaking stroke-dasharray and its related atrributes.

svg {
  viewBox: 0 0 10 10 p 10;
  rect {
    width, height: 10;
    stroke: #000;
    stroke-dasharray: 2 8;
    stroke-width: 20;
  }
}

Abstract forms.

svg {
  viewBox: 0 0 10 10 p 5;
  rect {
    width, height: 10;
    stroke: #000;
    stroke-dasharray: .079 9.9;
    stroke-width: 10;
  }
}
svg {
  viewBox: 0 0 10 10 p 10;
  rect {
    width, height: 10;
    stroke: #000;
    stroke-dasharray: .1 19 .1 .5 .31 .4;
    stroke-width: 20;
  }
}

Hearts at the corners.

svg {
  viewBox: 0 0 10 10 p 2;
  rect {
    width, height: 10;
    fill: none;
    stroke: #000;
    stroke-linecap: round;
    stroke-dasharray: 4 6;
    stroke-dashoffset: 2;
    stroke-width: 4;
  }
}

If you're using Safari, you might have noticed the top left corner has a rounded appearance. This could be a rendering bug or perhaps a feature.

Zero-sized dashes

When the dash values are set to 0 and the stroke-linecap attribute is set to either square or round, the caps will remain visible as if the dashes are still there.

This is an unique feature which offers some creative usages.

svg {
  viewBox: 0 0 100 100 p 24.5;
  rect {
    width, height: 100;
    fill: none;
    stroke: #000;
    stroke-linecap: square;
    stroke-dasharray: 0 50 50 0;
    stroke-width: 49;
  }
}

Two half round caps will form a full circle.

svg {
  viewBox: 0 0 100 100 p 10;
  rect {
    width, height: 100;
    fill: none;
    stroke: #000;
    stroke-dasharray: 0 10 10;
    stroke-linecap: round;
    stroke-width: 10;
  }
}
svg {
  viewBox: -50 -50 100 100 p 8;
  circle {
    r: 50;
    fill: none;
    stroke: #000;
    pathLength: 96;
    stroke-linecap: round;
    stroke-dasharray: 0 6 0 6 0;
    stroke-width: 16;
  }
}
svg {
  viewBox: -50 -50 100 100 p 7;
  circle {
    r: 50;
    fill: none;
    stroke: #000;
    pathLength: 96;
    stroke-linecap: round;
    stroke-width: 14;
    stroke-dasharray:
      0 6 0 6 0 6 0 0
      6 0 6 0 6 0 6;
  }
}

Generating dots

With sufficiently long path we can generate lots of dots using stroke-dasharray and the round caps.

svg {
  viewBox: -40 -40 80 80;
  style border: 5px solid #000;
  polyline {
    stroke: #000;
    fill: none;
    stroke-linecap: round;
    stroke-dasharray: 0 3;
    points: @m400.@Plot(
      turn: 40; r: t/3;
    );
  }
}

The dots are just caps with dashes length in 0. They're positioned where the path leads, so adjust the path the dots will follow.

/* ... */

points: @m199.@Plot(
  turn: 40; r: t/3;
);

At this point, I genuinely wish SVG gradients could follow along the SVG path so that I can apply different colors to the dots. Currently using mask is the only option to get a similar result.

svg {
  viewBox: -40 -40 80 80;
  style background: #04195a;
  style border: 6px solid #04195a;
  g {
    circle*200 {
      r, cx, cy: @r20 @r(±40) @r(±40);
      fill: @p(crimson, yellow, deepskyblue);
    }
    mask: defs mask {
      polyline {
        fill: none;
        stroke: #fff;
        stroke-linecap: round;
        stroke-dasharray: 0 3;
        points: @m200.Plot(turn: -40; r: t/3)
      }
    }
  }
}

Curvy strokes

After generating different paths using Math equations, adding dashes with stroke-dasharray will result in some peculiar shapes.

svg {
  viewBox: -25 -25 50 50 p 1;
  polyline {
    stroke: #000;
    fill: none;
    stroke-linecap: round;
    stroke-dasharray: 0 5;
    stroke-width: 1.5;
    points: @m200.Plot(
      turn: .37;
      x: 25 * cos(180t);
      y: 25 * sin(180t);
    );
  }
}

I'm not sure if they can be anyting practical. It's just intriguing to see.

/* ... */

stroke-dasharray: 0 10;
points: @m399.Plot(
  turn: .37;
  x: 25 * cos(180t);
  y: 25 * sin(180t);
);

Ornaments

Despite its interesting aspects, I find stroke-dasharray to be very useful in some specific cases.

Here's a symmetrical shape resembling a window lattice, yet the central part seems a little dense.

svg {
  viewBox: -50 -50 100 100;
  style border: 8px solid #000;
  fill: none;
  stroke: #000;
  stroke-width: 1.4;
  g*12 {
    transform: rotate($(360/@N*@n));
    polyline {
      points: @M12.Plot(r: t^4)
    }
    polyline {
      points: @M12.Plot(r: t^4; turn: -1)
    }
  }
}

How can it be improved? One option is to position a circle at the center. Another approach is to modify the lines with stroke-dasharray.

/* ... */

stroke-linecap: round;
stroke-dasharray: 1.5 8.5 100;

There are many options. For example, the one below looks much like a flower in its middle.

/* ... */

stroke-linecap: round;
stroke-dasharray: 0 3 5 2 100;

Using stroke-dasharray not only prevent lines from overlapping but also tend to change the feels based on different patterns.

svg {
  viewBox: 0 0 100 100;
  fill: none;
  style background: #0ce5f2;
  path*30 {
    stroke-linecap: round;
    stroke-dasharray: 0 8 5 6 3 12;
    stroke: @pn(
      #ff4ea5, yellow, @m4(#fff)
    );
    d: M 37 40
       Q @Plot(r: 80) @Plot(r: 150);
  }
}

Text strokes

It's also easy to apply a dashed stroke to the texts in SVG.

However, Safari again has a strange behavior for rendering stroke-dasharray. Maybe that's caused by the length calculation. The pathLength attribute is not available for text strokes so I can't get a consistant result in every browser.

For the same stroke-dasharray value, dashes in Safari are about 5 times more compact than Chrome and Firefox. And when the size of SVG changes its dashes will turn into another shape.

letter abcd in Safari
Safari
letter abcd in Chrome
Chrome, Firefox

Design with dashes

If the character is complex enough, like 䲜 or 靐, it would contain more dashes, which is something that can be utilized for design.

svg {
  viewBox: 0 0 100 100 p -15;
  style border: 8px solid #000;
  text {
    content: "龘";
    font-size: 120;
    x, y: 50, 90;
    font-family: sans-serif;
    text-anchor: middle;
    fill: none;
    stroke: #000;
    stroke-linejoin: round;
    stroke-linecap: round;
    stroke-dasharray: 0 6 20;
    stroke-width: 2;
  }
}

The original text becomes hard to recognize after adding dashes. Its meaning fades away, preserving only the shapes.

text {
  /* ... */

  content "M";
  stroke-dasharray: 0 6 0 4 20;
  transform: rotate(90);
}

Another complex character.

text {
  /* ... */

  content: "ﷺ";
  stroke-dasharray: 0 2 9;
}

There are plenteous Unicode characters and different fonts to try. But it's important to note that fonts in different platforms may not look the same. The result is highly dependant on the shape of the text.

This article describes the syntax that was used to write SVG here. All the code examples can be run at css-doodle.com/svg.