Walter Stabosz
Walter Stabosz

Reputation: 7735

Why does my div's background-color change when my CSS animation starts?

Goal

I want to animate a div so that:

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

Notes

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>

Upvotes: 0

Views: 142

Answers (3)

Walter Stabosz
Walter Stabosz

Reputation: 7735

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.

  1. I created an animation keyframe named _dummy_, and watched for it inside the animationstart event.
  2. When that happened, I ran a method to adjust the div's classList to switch it to the next color.
  3. I added transition: background-color 2000ms to the div.

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>

Upvotes: 0

Rifat Mahmud
Rifat Mahmud

Reputation: 175

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>

Upvotes: 0

udoyhasan
udoyhasan

Reputation: 2096

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>

Upvotes: 1

Related Questions