Search code examples
svgsvg-animate

Is it possible to create an SVG stroke animation that animates for the same duration regardless of stroke length?


In the example provided, the smaller shape animates much faster than the larger shape because its stroke length is much less.

My understanding is that setting the stroke-dasharray to 100% rather than a pixel amount should achieve this but doing so has unexpected results where the stroke doesn't extend the full 100% of the shape.

.page-wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  grid-column-gap: 2em;
  grid-row-gap: 2em;
}

.radar-wrapper {
  width: 300px;
  height: 300px;
}

.shape {
  fill: #ff4040;
  fill-opacity: .4;
  stroke: #ff4040;
  stroke-width: 2;
  stroke-dasharray: 2000;
  stroke-dashoffset: 0;
  animation: draw 5s infinite linear;
}

@keyframes draw {
  from {
    stroke-dashoffset: 2000;
  }
  to {
    stroke-dashoffset: 0;
  }
}
<div class="page-wrapper">
  <div class="radar-wrapper">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
  <polygon class="shape" points="116.67 3.97 155.96 123.88 243.39 32.89 174.14 138.38 299.79 150 174.14 161.62 243.39 267.11 155.96 176.12 116.67 296.03 133.3 170.95 15.05 214.99 123.21 150 15.05 85.01 133.3 129.05 116.67 3.975"></polygon>
</svg>
  </div>
  <div class="radar-wrapper">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
  <polygon class="shape" points="144.04 176.12 133.3 170.95 125.86 161.62 123.21 150 125.86 138.38 133.3 129.05 144.04 123.88 155.96 123.88 166.7 129.05 174.14 138.38 176.79 150 174.14 161.62 166.7 170.95 155.96 176.12 144.04 176.12"></polygon>
</svg>
  </div>
</div>


Solution

  • You can circumvent this problem by setting a pathLength attribute to normalize the animated path lengths

    From mdn docs

    the pathLength attribute lets authors specify a total length for the path, in user units. This value is then used to calibrate the browser's distance calculations with those of the author, by scaling all distance computations using the ratio pathLength / (computed value of path length).

    This way you can ensure both animations take the same time and it also facilitates the dash-array property calculations as you only need to animate between

    stroke-dasharray: 0 100 (no visible stroke)
    and
    stroke-dasharray: 100 100 (full path length)

    .page-wrapper {
      display: flex;
      justify-content: center;
      align-items: center;
      grid-column-gap: 2em;
      grid-row-gap: 2em;
    }
    
    .radar-wrapper {
      width: 300px;
      height: 300px;
    }
    
    .shape {
      fill: #ff4040;
      fill-opacity: .4;
      stroke: #ff4040;
      stroke-width: 2;
      stroke-dasharray: 0 100;
      animation: draw 2s infinite linear;
    }
    
    @keyframes draw {
      from {
        stroke-dasharray: 0 100;
      }
      to {
        stroke-dasharray: 100 100;
      }
    }
    <div class="page-wrapper">
      <div class="radar-wrapper">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
          <polygon class="shape" pathLength="100" points="116.67 3.97 155.96 123.88 243.39 32.89 174.14 138.38 299.79 150 174.14 161.62 243.39 267.11 155.96 176.12 116.67 296.03 133.3 170.95 15.05 214.99 123.21 150 15.05 85.01 133.3 129.05 116.67 3.975" />
        </svg>
      </div>
      <div class="radar-wrapper">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
          <polygon class="shape" pathLength="100" points="144.04 176.12 133.3 170.95 125.86 161.62 123.21 150 125.86 138.38 133.3 129.05 144.04 123.88 155.96 123.88 166.7 129.05 174.14 138.38 176.79 150 174.14 161.62 166.7 170.95 155.96 176.12 144.04 176.12" />
        </svg>
      </div>
    </div>