Animating background in progress bar (HTML5) Shadow DOM

581 views Asked by At

I'm trying to animate the progress bar (HTML5 <progess> tag). I managed to style the Shadow DOM elements, but I can't animate the background (repeating linear gradient). It works in Firefox, but not in Chrome and Edge.

<ins>By it does not work I mean, the striped background is shown, but it is not animated</ins>

It seems as if the @keyframe animation definition has a different scope outside of the shadow boundary.

<ins>
How can I break the scope of the Shadow DOM boundary?
Or is there a way that I can override the User Agents shadow DOM implementation?
</ins>

<ins>
I would be glad, if there is a No-JS solution
</ins>

.progress {
  position: relative;
}
.progress::before {
  content: attr(title);
  position: absolute;
  z-index: 100;
  width: 100%;
  height: 100%;
  display: -webkit-box;
  display: flex;
  -webkit-box-align: center;
          align-items: center;
  -webkit-box-pack: center;
          justify-content: center;
  text-align: center;
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.4;
}

progress[value] {
  display: block;
  width: 100%;
  min-height: 4rem;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  border-radius: 10px;
  box-shadow: inset 4px 4px 4px rgba(84, 30, 8, 0.2);
  background-color: rgba(149, 250, 61, 0.1);
  border: 1px solid #ccc;
}
progress[value]::-webkit-progress-inner-element {
  border-radius: 10px;
  overflow: hidden;
}
progress[value]::-webkit-progress-bar {
  border-radius: 10px;
  /* box-shadow: inset 4px 4px 4px rgba(84, 30, 8, 0.2); */
  background-color: transparent;
}
progress[value]::-webkit-progress-value {
  border-radius: 10px 0 0 10px / 10px 0 0 10px;
  box-shadow: inset 2px 2px 2px rgba(84, 30, 8, 0.2);
  background-color: #95fa3c;
  position: relative;
  background-image: repeating-linear-gradient(45deg, transparent 0, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.1) 12px);
  background-size-x: 800942px; /* empirical value */
  -webkit-animation: colorrush 3s infinite linear;
          animation: colorrush 3s infinite linear;
}
progress[value]::-moz-progress-bar {
  border-radius: 10px 0 0 10px / 10px 0 0 10px;
  box-shadow: inset 2px 2px 2px rgba(84, 30, 8, 0.2);
  background-color: #95fa3c;
  position: relative;
  background-image: repeating-linear-gradient(45deg, transparent 0, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.1) 12px);
  background-size: 800942px; /* empirical value */
  animation: colorrush 3s infinite linear;
}

@-webkit-keyframes colorrush {
  0% {
    background-color: #95fa3c;
    background-position-x: 0;
  }
  50% {
    background-color: #c4eea0;
  }
  100% {
    background-color: #95fa3c;
    background-position-x: 152px; /* empirical value */
  }
}

@keyframes colorrush {
  0% {
    background-color: #95fa3c;
    background-position-x: 0;
  }
  50% {
    background-color: #c4eea0;
  }
  100% {
    background-color: #95fa3c;
    background-position-x: 152px; /* empirical value */
  }
}
html {
  font-size: 62.5%;
  font-family: sans-serif;
}

body {
  font-size: 1.6rem;
}
<div class="progress" title="125 / 150 (83.33%)">
  <progress max="150" value="125">125 / 150</progress>
</div>

2

There are 2 answers

2
Temani Afif On BEST ANSWER

Inheritance can fix the issue. You apply the animation on the main element and you use a cascading inherit. Since it won't work with background-color I replaced the animation with a gradient one where I will also animation the position.

I also optimized the code to avoid the empircal value

progress[value] {
  display: block;
  width: 100%;
  min-height: 4rem;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  border-radius: 10px;
  box-shadow: inset 4px 4px 4px rgba(84, 30, 8, 0.2);
  background-color:rgba(149, 250, 61, 0.1);
  border: 1px solid #ccc;
  animation: colorrush 5s infinite linear;
}
progress[value]::-webkit-progress-inner-element {
  border-radius: 10px;
  overflow: hidden;
  background-position:inherit;
}
progress[value]::-webkit-progress-bar {
  border-radius: 10px;
  background-color: transparent;
  background-position:inherit;
}
progress[value]::-webkit-progress-value {
  border-radius: 10px 0 0 10px / 10px 0 0 10px;
  box-shadow: inset 2px 2px 2px rgba(84, 30, 8, 0.2);
  background: 
    repeating-linear-gradient(45deg, transparent 0 6px, rgba(0, 0, 0, 0.1) 6px 12px),
    linear-gradient(#95fa3c,#c4eea0,#95fa3c);  
  background-size:
     calc(12px/0.707) 100%, /* 0.707 = cos(45deg)*/
     100% 800%;
  background-position:inherit;
}
progress[value]::-moz-progress-bar {
  border-radius: 10px 0 0 10px / 10px 0 0 10px;
  box-shadow: inset 2px 2px 2px rgba(84, 30, 8, 0.2);
  background:
    repeating-linear-gradient(45deg, transparent 0 6px, rgba(0, 0, 0, 0.1) 6px 12px),
    linear-gradient(#95fa3c,#c4eea0,#95fa3c);   
    background-size:
     calc(12px/0.707) 100%, /* 0.707 = cos(45deg)*/
     100% 800%;
  background-position:inherit;
}


@keyframes colorrush {
  0% {
    background-position:0 0;
  }
  100% {
    /* the 10 multiplier will allow me to use a big duration and be able
       to slow down the color animation
    */
    background-position: calc(10*(12px/0.707)) 100%;
  }
}
html {
  font-size: 62.5%;
  font-family: sans-serif;
}

body {
  font-size: 1.6rem;
}
<progress max="150" value="125">125 / 150</progress>

Related question to understand the math behind the background-size: Animated CSS background pattern, sliding infinitely

Another related one to understand the use of percentage value with background-position: Using percentage values with background-position on a linear-gradient

1
Axwabo On

As far as I know, you can't keyframe CSS gradients. What you can do is animate it with JS:

var rgb1 = {
  r: 0,
  g: 0,
  b: 0
}
var rgb2 = {
  r: 0,
  g: 0,
  b: 0
}
var increment1 = {
  r: 1,
  g: 0,
  b: 0
}
var increment2 = {
  r: 0,
  g: 0,
  b: 0
}
setInterval(function() {
  rgb1.r += increment1.r;
  rgb1.g += increment1.g;
  rgb1.b += increment1.b;
  rgb2.r += increment2.r;
  rgb2.g += increment2.g;
  rgb2.b += increment2.b;
  if (rgb1.r > 255) rgb1.r = 0; //rgb is range 0-255
  if (rgb1.g > 255) rgb1.g = 0;
  if (rgb1.b > 255) rgb1.b = 0;
  if (rgb2.r > 255) rgb2.r = 0;
  if (rgb2.g > 255) rgb2.g = 0;
  if (rgb2.b > 255) rgb2.b = 0;
  document.getElementById("anim1").style.backgroundImage = `linear-gradient(to top, rgb(${rgb1.r}, ${rgb1.g}, ${rgb1.b}), rgb(${rgb2.r}, ${rgb2.g}, ${rgb2.b}))`;
}, 10);
div {
  width: 256px;
  height: 256px;
}
input[type=range] {
width: 256px;
}
<div id="anim1"></div>
<input type="range" min="0" max="255" value="1" oninput="increment1.r = parseInt(this.value);document.getElementById('inc1r').innerHTML = 'Increment R1: ' + this.value"> <label id="inc1r">Increment R1: 1</label><br>
<input type="range" min="0" max="255" value="0" oninput="increment1.g = parseInt(this.value);document.getElementById('inc1g').innerHTML = 'Increment G1: ' + this.value"> <label id="inc1g">Increment G1: 0</label><br>
<input type="range" min="0" max="255" value="0" oninput="increment1.b = parseInt(this.value);document.getElementById('inc1b').innerHTML = 'Increment B1: ' + this.value"> <label id="inc1b">Increment B1: 0</label><br>

<input type="range" min="0" max="255" value="0" oninput="increment2.r = parseInt(this.value);document.getElementById('inc2r').innerHTML = 'Increment R2: ' + this.value"> <label id="inc2r">Increment R2: 0</label><br>
<input type="range" min="0" max="255" value="0" oninput="increment2.g = parseInt(this.value);document.getElementById('inc2g').innerHTML = 'Increment G2: ' + this.value"> <label id="inc2g">Increment G2: 0</label><br>
<input type="range" min="0" max="255" value="0" oninput="increment2.b = parseInt(this.value);document.getElementById('inc2b').innerHTML = 'Increment B2: ' + this.value"> <label id="inc2b">Increment B2: 0</label><br>