'Why does my div's background-color change when my CSS animation starts?
Goal
I want to animate a div so that:
- Over the span of 10 seconds, the
widthchanges from 100% to 0%. - When 5 seconds have elapsed, the
background-colorshould change from green to orange. - When 7.5 seconds have elapsed, the
background-colorshould change from orange to red. - EDIT: I want the color transitions to occur over a period of 2 seconds.
Problem
When I start the animation, the color of my div changes immediately to orange. The change is not animated, the color just goes from 100% green to 100% orange.
These Requirements Are working
- The width animation works correctly.
- At 7.5 seconds, the
orange-to-redanimation works correctly.
Notes
I start the animation by adding the class
timer-animationtodiv#timerBar. I'm not sure if this is the preferred way to start the animation.At 5 seconds, the
animationstartevent fires forgreen-to-orange(as coded), but thedivdoes not change color as it is already orange.I'm new to CSS animations, but I have been coding for 30 years.
let timerBar = document.getElementById('timerBar');
let animationNameList = document.getElementById('animation-names');
const startAnimation = function() {
timerBar.classList.remove('pause');
timerBar.classList.toggle('timer-animation');
animationNameList.replaceChildren();
};
const pauseAnimation = function() {
timerBar.classList.toggle('pause');
};
timerBar.addEventListener('animationstart', (e) => {
let li = document.createElement('li');
li.innerText = e.animationName;
animationNameList.appendChild(li);
});
document
.getElementById('startButton')
.addEventListener('click',(startAnimation));
document
.getElementById('pauseButton')
.addEventListener('click',(pauseAnimation));
#timerBar {
background-color: #41b883;
height: 20px;
width: 100%;
}
.timer-animation {
animation-timing-function: linear;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both;
animation-name: shrink-width,green-to-orange,orange-to-red;
animation-delay:0ms,5000ms,7500ms;
animation-duration:10000ms,2000ms,2000ms;
}
.pause {
animation-play-state: paused;
}
@keyframes shrink-width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes green-to-orange {
from {
background-color: #41b883;
}
to {
background-color: #ffa500;
}
}
@keyframes orange-to-red {
from {
background-color: #ffa500;
}
to {
background-color: #ff0000;
}
}
<div id="timerBar"></div>
<br/>
<button id="startButton">Start / Reset</button>
<button id="pauseButton">Pause</button>
<br />
<h4>When an animationstart event fires, its name will appear in this list.</h4>
<ul id="animation-names"></ul>
Solution 1:[1]
The problem is in your CSS code. If we see the CSS code we can see that you are using two same animation keyframes green-to-orange & orange-to-red in the same class. Though you have set an animation-delay still it neglects the green-to-orange animation and takes the initial color from the orange-to-red keyframe. So the best option will be to use a single keyframe green-to-orange-to-red instead of two separate keyframes green-to-orange & orange-to-red. And in the single animation green-to-orange-to-red you have to use x% instead of the from & to. You can check the following code, I've updated the code's CSS & I hope it will help you with your question.
let timerBar = document.getElementById('timerBar');
let animationNameList = document.getElementById('animation-names');
const startAnimation = function() {
timerBar.classList.remove('pause');
timerBar.classList.toggle('timer-animation');
animationNameList.replaceChildren();
};
const pauseAnimation = function() {
timerBar.classList.toggle('pause');
};
timerBar.addEventListener('animationstart', (e) => {
let li = document.createElement('li');
li.innerText = e.animationName;
animationNameList.appendChild(li);
});
document
.getElementById('startButton')
.addEventListener('click',(startAnimation));
document
.getElementById('pauseButton')
.addEventListener('click',(pauseAnimation));
#timerBar {
background-color: #41b883;
height: 20px;
width: 100%;
}
.timer-animation {
animation-timing-function: linear;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both;
animation-name: shrink-width,green-to-orange-to-red;
animation-delay:0ms,0ms;
animation-duration:10000ms,10000ms;
}
.pause {
animation-play-state: paused;
}
@keyframes shrink-width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes green-to-orange-to-red {
0% {
background-color: #41b883;
}
50% {
background-color: #ffa500;
}
100% {
background-color: #ff0000;
}
}
<div id="timerBar"></div>
<br/>
<button id="startButton">Start / Reset</button>
<button id="pauseButton">Pause</button>
<br />
<h4>When an animationstart event fires, its name will appear in this list.</h4>
<ul id="animation-names"></ul>
Solution 2:[2]
You don't need to use multiple animation keyframes. Use a single keyframe. At 0% it will change stay green, at 50% it will gradually change from green to orange, at 75% it will change from orange to red and will stay red till 100%.
You don't need to use animation delay and set animation duration 10000ms for both keyframes.
let timerBar = document.getElementById('timerBar');
let animationNameList = document.getElementById('animation-names');
const startAnimation = function() {
timerBar.classList.remove('pause');
timerBar.classList.toggle('timer-animation');
animationNameList.replaceChildren();
};
const pauseAnimation = function() {
timerBar.classList.toggle('pause');
};
timerBar.addEventListener('animationstart', (e) => {
let li = document.createElement('li');
li.innerText = e.animationName;
animationNameList.appendChild(li);
});
document
.getElementById('startButton')
.addEventListener('click',(startAnimation));
document
.getElementById('pauseButton')
.addEventListener('click',(pauseAnimation));
#timerBar {
background-color: #41b883;
height: 20px;
width: 100%;
}
.timer-animation {
animation-timing-function: linear;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both;
animation-name: shrink-width, green-to-orange-to-red;
animation-duration:10000ms, 10000ms;
}
.pause {
animation-play-state: paused;
}
@keyframes shrink-width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes green-to-orange-to-red {
0%{
background-color: #41b883;
}
50%{
background-color: #ffa500;
}
75%{
background-color: #ff0000;
}
100%{
background-color: #ff0000;
}
}
<div id="timerBar"></div>
<br/>
<button id="startButton">Start / Reset</button>
<button id="pauseButton">Pause</button>
<br />
<h4>When an animationstart event fires, its name will appear in this list.</h4>
<ul id="animation-names"></ul>
Solution 3:[3]
Thanks for the suggestions everyone. They were helpful, but did not quite meet my requirements. I want the period of the color transitions to only be 2 seconds, rather than fading for the entire 10 seconds of the animation.
I ended up solving this in a sort of hacky mix of animations and transitions.
- I created an animation keyframe named
_dummy_, and watched for it inside theanimationstartevent. - When that happened, I ran a method to adjust the
div'sclassListto switch it to the next color. - I added
transition: background-color 2000msto thediv.
The three changes together make it work to meet my requirements.
I still don't understand why my original method of using just animations didn't work. I'd like to read some documentation to explain precisely what why my syntax is wrong.
let timerBar = document.getElementById('timerBar');
let animationNames = document.getElementById('animation-names');
let colors = ['green','orange','red'];
let colorIndex = 0;
const getNextColor = function() {
let color = colors[colorIndex++];
if(colorIndex == colors.length)
colorIndex = 0;
return color;
};
const changeColor = function() {
colors.forEach(o=>timerBar.classList.remove(o));
timerBar.classList.add(getNextColor());
};
const startAnimation = function() {
colorIndex = 0;
changeColor();
timerBar.classList.remove('pause');
timerBar.classList.toggle('timer-animation');
animationNames.replaceChildren();
};
const pauseAnimation = function() {
timerBar.classList.toggle('pause');
};
timerBar.addEventListener('animationstart', (e) => {
if(e.animationName.includes('_dummy_')) {
changeColor();
}
let li = document.createElement('li');
li.innerText = e.animationName;
animationNames.appendChild(li);
});
document
.getElementById('startButton')
.addEventListener('click',(startAnimation));
document
.getElementById('pauseButton')
.addEventListener('click',(pauseAnimation));
#timerBar {
height: 20px;
width: 100%;
background-size: 0 50%;
transition: background-color 2000ms;
}
.green {
background-color: #41b883;
}
.orange {
background-color: #ffa500;
}
.red {
background-color: #ff0000;
}
.timer-animation {
animation-timing-function: linear;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both;
animation-name: shrink-width, _dummy_, _dummy_;
animation-duration: 10000ms,0ms,0ms;
animation-delay: 0ms, 5000ms, 7500ms;
}
.pause {
animation-play-state: paused;
}
@keyframes shrink-width {
from {
width: 100%;
}
to {
width: 0%;
}
}
@keyframes _dummy_ {}
<div id="timerBar" class="green"></div>
<br/>
<button id="startButton">Start / Reset</button>
<button id="pauseButton">Pause</button>
<br />
<h4>When an animation starts, its name will appear in this list.</h4>
<ul id="animation-names"></ul>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | udoyhasan |
| Solution 2 | |
| Solution 3 | Walter Stabosz |
