Border created with a SVG path using excessive CPU resources when animating stroke dashoffset

Viewed 159

I have a very simple border created with a SVG path. If I try to animate it with it's stroke-dashoffset property, the GPU usage of Chrome spikes up to 15%. This seems excessive given the fact that I'm animating a single stroke.

Why is this happening? If this resource usage is expected, are there any alternative ways with which I can create the same effect while keeping the resource usage lower?

Demo: Place the cursor on top of the box in order to animate the svg path (and check the GPU usage of chrome)

.outer {
  border-radius: 5px;
  position: relative; 
  width: 300px;
  height: 300px;
  border: 1px solid gray;
}

.border-path {
  width: calc(100% + 10px);
  height: calc(100% + 10px);
  position: absolute;
  top: -5px;
  left: -5px;
  stroke-width: 5;
  stroke-dasharray: 8;
  stroke-dashoffset: 1000;
  stroke-opacity: 0;
  fill: transparent;
}

.outer:hover > .border-path {
  stroke: red;
  stroke-opacity: 1;
  animation: draw 30s linear infinite forwards;
}

@keyframes draw {
  to {
    stroke-dashoffset: 0;
  }
}
<div class="outer">
  <svg class="border-path">
    <rect x="0" y="0" width="100%" height="100%" />
  </svg>
</div>

1 Answers

A CSS only solution where I will animate only opacity and tranformation. Should use less ressources. Not as perfect as the SVG one but good enough as an alternative:

.outer {
  border-radius: 5px;
  position: relative;
  width: 300px;
  height: 300px;
  border: 1px solid gray;
  margin:15px;
  clip-path:inset(-5px);
}
.outer::before,
.outer::after,
.outer span::before,
.outer span::after {
  content:"";
  position: absolute;
  opacity:0;
  transition:1s;
  animation: 1s linear infinite;
}
.outer::before,
.outer::after {
  height: 2px;
  left: -5px;
  width: calc(100% + 20px);
  background: repeating-linear-gradient(to right, red 0 5px, transparent 0 10px);
  animation-name:drawX;
}

.outer span::before,
.outer span::after {
  width: 2px;
  top: -5px;
  height: calc(100% + 20px);
  background: repeating-linear-gradient(to bottom, red 0 5px, transparent 0 10px);
  animation-name:drawY;
}

.outer::before {top: -5px; animation-direction:reverse;}
.outer span::before {right: -5px; animation-direction:reverse;}

.outer::after { bottom: -5px;}
.outer span::after { left: -5px;}

.outer:hover::before,
.outer:hover::after,
.outer:hover span::before,
.outer:hover span::after {
  opacity:1;
}

@keyframes drawX {
  to {
    transform: translateX(-10px);
  }
}
@keyframes drawY {
  to {
    transform: translateY(-10px);
  }
}
<div class="outer">
  <span></span>
</div>

With Some CSS variables to control everything:

.outer {
  --th:2px; /* border thickness */
  --w:5px; /* width of the color*/
  --s:5px; /* the space between color*/
  --o:5px; /* the offset */
  --c:red;
  --g: var(--c) 0 var(--w), transparent 0 calc(var(--w) + var(--s));
  
  border-radius: 5px;
  position: relative;
  width: 200px;
  height: 200px;
  display:inline-block;
  border: 1px solid gray;
  margin:15px;
  clip-path:inset(calc(-1*var(--o)));
}
.outer::before,
.outer::after,
.outer span::before,
.outer span::after {
  content:"";
  position: absolute;
  opacity:0;
  transition:1s;
  animation: 1s linear infinite;
}
.outer::before,
.outer::after {
  height: var(--th);
  left: calc(-1*var(--o));
  width: calc(100% + var(--w) + var(--s) + 2*var(--o));
  background: repeating-linear-gradient(to right, var(--g));
  animation-name:drawX;
}

.outer span::before,
.outer span::after {
  width: var(--th);
  top: calc(-1*var(--o));
  height: calc(100% + var(--w) + var(--s) + 2*var(--o));
  background: repeating-linear-gradient(to bottom, var(--g));
  animation-name:drawY;
}

.outer::before {top: calc(-1*var(--o)); animation-direction:reverse;}
.outer span::before {right: calc(-1*var(--o)); animation-direction:reverse;}

.outer::after { bottom: calc(-1*var(--o));}
.outer span::after { left: calc(-1*var(--o));}

.outer:hover::before,
.outer:hover::after,
.outer:hover span::before,
.outer:hover span::after {
  opacity:1;
}

@keyframes drawX {
  to {
    transform: translateX(calc(-1*(var(--w) + var(--s))));
  }
}
@keyframes drawY {
  to {
    transform: translateY(calc(-1*(var(--w) + var(--s))));
  }
}
<div class="outer">
  <span></span>
</div>

<div class="outer" style="--th:4px;--o:8px;--s:8px;--w:8px;--c:blue;">
  <span></span>
</div>

Related