admin管理员组

文章数量:1289517

Having some trouble building a CSS3 loader using keyframe animations.

The loader consists of 4 boxes that animate going up and down. The issue I'm having is that when the animation is supposed to stop, the boxes jump to the initial position. The behaviour I'm looking for is: loader is animating infinitely until loading is done, at which point it should animate to the initial position and stop, sort of like having animation-iteration-count: infinite and changing it to animation-iteration-count: 1 to stop the animation. (which doesn't work btw).

See this fiddle to see what I mean: / (when clicking the stop button, the boxes should animate to the initial position, instead of jumping)

The basic setup is:

<div class="loader-wrapper"><span></span><span></span><span></span><span></span></div>

To start the loader, I'm adding a loader-active class that contains the animation to the loader-wrapper.

LESS:

.loader-wrapper {
  &.loader-active {
    span {
      .animation-name(loader);
      .animation-duration(1200ms);
      .animation-timing-function(ease-in-out);
      .animation-play-state(running);
      .animation-iteration-count(infinite);

      &:nth-child(1) {
      }
      &:nth-child(2) {
        .animation-delay(300ms);
      }
      &:nth-child(3) {
        .animation-delay(600ms);
      }
      &:nth-child(4) {
        .animation-delay(900ms);
      }
    }
  }
}

I've tried adding the animation to the spans in the loader-wrapper class w/o loader-active and playing around with animation-iteration-count and animation-play-state when loader-active is added without any luck.

Having some trouble building a CSS3 loader using keyframe animations.

The loader consists of 4 boxes that animate going up and down. The issue I'm having is that when the animation is supposed to stop, the boxes jump to the initial position. The behaviour I'm looking for is: loader is animating infinitely until loading is done, at which point it should animate to the initial position and stop, sort of like having animation-iteration-count: infinite and changing it to animation-iteration-count: 1 to stop the animation. (which doesn't work btw).

See this fiddle to see what I mean: https://jsfiddle/cazacuvlad/qjmhm4ma/ (when clicking the stop button, the boxes should animate to the initial position, instead of jumping)

The basic setup is:

<div class="loader-wrapper"><span></span><span></span><span></span><span></span></div>

To start the loader, I'm adding a loader-active class that contains the animation to the loader-wrapper.

LESS:

.loader-wrapper {
  &.loader-active {
    span {
      .animation-name(loader);
      .animation-duration(1200ms);
      .animation-timing-function(ease-in-out);
      .animation-play-state(running);
      .animation-iteration-count(infinite);

      &:nth-child(1) {
      }
      &:nth-child(2) {
        .animation-delay(300ms);
      }
      &:nth-child(3) {
        .animation-delay(600ms);
      }
      &:nth-child(4) {
        .animation-delay(900ms);
      }
    }
  }
}

I've tried adding the animation to the spans in the loader-wrapper class w/o loader-active and playing around with animation-iteration-count and animation-play-state when loader-active is added without any luck.

Share Improve this question edited Jul 22, 2021 at 14:11 Brian Tompsett - 汤莱恩 5,89372 gold badges61 silver badges133 bronze badges asked Feb 19, 2016 at 14:18 Vlad CazacuVlad Cazacu 1,54012 silver badges12 bronze badges 5
  • You can't do that with CSS. You'd need what I suspect would be quick plex Javascript. – Paulie_D Commented Feb 19, 2016 at 14:20
  • Doing it in JS is the backup solution. I think this is valid use case for animations, there should be a pure CSS way of doing this. – Vlad Cazacu Commented Feb 19, 2016 at 14:26
  • The animations are fine but CSS can't detect where an animation is in it's (mid)cycle and then run down based on a condition. I'm not even sure if JS can do that but if it can, that would be the way. – Paulie_D Commented Feb 19, 2016 at 14:28
  • 1 It might not be impossible but it would not be straight-forward. You can refer to this thread. Within that answer I have linked to another answer by vals which shows a method to achieve something like this. (Note: I linked to my answer instead of directly linking to vals' because there is some explanation in my answer which might also be helpful for you.) – Harry Commented Feb 19, 2016 at 14:55
  • 1 I found a pretty simple workaround, not entirely happy with it since it still involves JS, but it works well. Posting it as an answer in case someone else stumbles on this. – Vlad Cazacu Commented Feb 19, 2016 at 15:04
Add a ment  | 

2 Answers 2

Reset to default 8

Found a pretty simple workaround. Still not pure CSS, it involves a bit of JS, but it works well.

Updated fiddle: https://jsfiddle/cazacuvlad/qjmhm4ma/2/

What I did was to move the loader-active class to each span (instead of the wrapper), listen to the animationiteration event on each span and stop the animation then.

$('.loader-wrapper span').on('animationiteration webkitAnimationIteration', function () {
  var $this = $(this);

  $this.removeClass('loader-active');

  $this.off();
});

This basically stops the animation at the very end of an iteration cycle.

Updated LESS

.loader-wrapper {
  span {
    &.loader-active {
      .animation-name(loader);
      .animation-duration(1200ms);
      .animation-timing-function(ease-in-out);
      .animation-play-state(running);
      .animation-iteration-count(infinite);

      &:nth-child(1) {
      }
      &:nth-child(2) {
        .animation-delay(300ms);
      }
      &:nth-child(3) {
        .animation-delay(600ms);
      }
      &:nth-child(4) {
        .animation-delay(900ms);
      }
    }
  }
}

You can also add a class which specifies the iteration count to stop the infinite loop. The advantage of this approach is that you can change the duration and timing-function which can be nice for easing out some animation (Like a rotating logo for example).

.animate-end {
  animation-iteration-count: 3;
  animation-duration: 1s;
  animation-timing-function: ease-out;
}

We can add this class with js and it will now stop the animation at count 3.

document.querySelector(".loader-wrapper").classList.add("animate-end");

But you can also end the current itertion by counting it and change the style of the element dynamcly with Js.

let iterationCount = 0;
document.querySelector(".loader-wrapper span").addEventListener('animationiteration', () => {
//count iterations
  iterationCount++;
});    

yourElement.style.animationIterationCount = iterationCount + 1;

Here is a demo with your code:

document.querySelector("#start_loader").addEventListener("click", function(){
  document.querySelector(".loader-wrapper").classList.add("loader-active");  
})


let iterationCount = 0;
document.querySelector(".loader-wrapper span").addEventListener('animationiteration', () => {
//count iterations
  iterationCount++;
  console.log(`Animation iteration count: ${iterationCount}`);
});

document.querySelector("#stop_loader").addEventListener("click", function(){
    
    //For some animation it can be nice to change the duration or timing animation
    document.querySelector(".loader-wrapper").classList.add("animate-end");
    
    //End current iteration
     document.querySelectorAll(".loader-wrapper span").forEach(element => {
        element.style.animationIterationCount = iterationCount + 1;
    });


    //Remove Classes with a timeout or animationiteration event
    setTimeout(() => {
        document.querySelector(".loader-wrapper").classList.remove("loader-active");
         document.querySelector(".loader-wrapper").classList.remove("animate-end");
     }, 1200);
    

})
@-moz-keyframes 'loader' {
  0% {
    -moz-transform: translate3d(0, 0, 0);
  }
  50% {
    -moz-transform: translate3d(0, -10px, 0);
  }
  100% {
    -moz-transform: translate3d(0, 0, 0);
  }
}

@-webkit-keyframes 'loader' {
  0% {
    -webkit-transform: translate3d(0, 0, 0);
  }
  50% {
    -webkit-transform: translate3d(0, -10px, 0);
  }
  100% {
    -webkit-transform: translate3d(0, 0, 0);
  }
}

@-o-keyframes 'loader' {
  0% {
    -o-transform: translate3d(0, 0, 0);
  }
  50% {
    -o-transform: translate3d(0, -10px, 0);
  }
  100% {
    -o-transform: translate3d(0, 0, 0);
  }
}

@keyframes 'loader' {
  0% {
    transform: translate3d(0, 0, 0)
  }
  50% {
    transform: translate3d(0, -10px, 0)
  }
  100% {
    transform: translate3d(0, 0, 0)
  }
}
.loader-wrapper {
  margin-bottom: 30px;
}


.loader-wrapper.loader-active span {
  -webkit-animation-name: loader;
  -moz-animation-name: loader;
  -ms-animation-name: loader;
  -o-animation-name: loader;
  animation-name: loader;
  -webkit-animation-duration: 1200ms;
  -moz-animation-duration: 1200ms;
  -ms-animation-duration: 1200ms;
  -o-animation-duration: 1200ms;
  -webkit-animation-timing-function: ease-in-out;
  -moz-animation-timing-function: ease-in-out;
  -ms-animation-timing-function: ease-in-out;
  -o-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
  -webkit-animation-play-state: running;
  -moz-animation-play-state: running;
  -ms-animation-play-state: running;
  -o-animation-play-state: running;
  animation-play-state: running;
  -webkit-animation-iteration-count: infinite;
  -moz-animation-iteration-count: infinite;
  -ms-animation-iteration-count: infinite;
  -o-animation-iteration-count: infinite;
  animation-iteration-count: infinite;
}


.loader-wrapper.animate-end span {
    /* Works great for some animations */
    /*animation-iteration-count: 1;*/
    /*animation-duration: 1s;*/
}

.loader-wrapper.loader-active span:nth-child(1) {}

.loader-wrapper.loader-active span:nth-child(2) {
  animation-delay: 300ms;
}

.loader-wrapper.loader-active span:nth-child(3) {
  animation-delay: 600ms;
}

.loader-wrapper.loader-active span:nth-child(4) {
  animation-delay: 900ms;
}

.loader-wrapper span {
  margin-right: 5px;
  display: inline-block;
  vertical-align: middle;
  background: black;
  width: 10px;
  height: 10px;
}
<div class="loader-wrapper"><span></span><span></span><span></span><span></span></div>

<button id="start_loader">Start</button>
<button id="stop_loader">Stop</button>

本文标签: javascriptStop infinite CSS3 animation and smoothly revert to initial stateStack Overflow