Dash March

draw

Marching ants: a fixed dash pattern crawls along every stroke, forever. Under a non-scaling stroke the pattern is in screen pixels, so the dashes keep their size at any scale.

Demo shape

Every animation on the page redraws with the shape you pick.

Align
Ease
Duration
s
0.13
Stagger
s
00.2
Delay
s
02
<!-- 1. Markup -->
<svg viewBox="0 0 120 120" class="k-dash-march" aria-hidden="true">
  <circle cx="60" cy="60" r="34" fill="none" stroke="currentColor" stroke-width="8" vector-effect="non-scaling-stroke" />
  <line x1="60" y1="12" x2="60" y2="38" stroke="currentColor" stroke-width="8" stroke-linecap="round" vector-effect="non-scaling-stroke" />
  <line x1="60" y1="82" x2="60" y2="108" stroke="currentColor" stroke-width="8" stroke-linecap="round" vector-effect="non-scaling-stroke" />
  <circle cx="60" cy="60" r="10" fill="currentColor" />
</svg>

// 2. JS
function dashMarch(svg) {
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return () => {}

  const shapes = paths(svg).filter((s) => s.getAttribute('stroke'))
  // One dash and one gap. The gap outruns a round cap's overhang on an 8px
  // stroke, which would otherwise close it up into a solid line.
  const dash = 8
  const gap = 14
  const animations = shapes.map((shape) => {
    shape.style.strokeDasharray = `${dash} ${gap}`
    return shape.animate([{ strokeDashoffset: 0 }, { strokeDashoffset: -(dash + gap) }], {
      duration: 0.8 * 1000,
      iterations: Infinity,
      easing: 'linear',
    })
  })

  return () => {
    animations.forEach((a) => a.cancel())
    shapes.forEach((shape) => {
      shape.style.strokeDasharray = ''
    })
  }
}

function paths(svg) {
  return [...svg.querySelectorAll('path, line, polyline, polygon, circle, ellipse, rect')].filter(
    (el) => !el.closest('defs, clipPath, mask'),
  )
}


dashMarch(document.querySelector('.k-dash-march'))