Howdy_McGee
Howdy_McGee

Reputation: 10635

Wait for CSS Animation to Complete

I'm working on a directional flip with an animation. I thought that I found a solution using the below binding but it I hadn't noticed the below issues...

bind( 'transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd', function() {

The problem is that if you hover left then top the animation flips diagonally or will spin - you can hover multiple part of the div quickly and it will do some crazy stuff. What I want to happen is for either the animation to complete or reset instead of immediately trying to process the next hover animation. This issue is occurring in all modern browsers ( IE Edge / 11, Chrome, Firefox. These are what I've tested it in ).

Am I going about this the wrong way? How can I have my javascript wait for the flip animation to complete?


Related issue is that sometimes the animation will get stuck on a left-to-right or top-to-bottom animation no matter which direction because it's not waiting to complete so it can remove the other classes.

jQuery(document).ready(function() {
  $('.galleryWrapper.bot .gallery-item').hover(
    function(e) {
      $(this).unbind('transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd');
      $(this).removeClass('rtl');
      var w = $(this).width();
      var h = $(this).height();
      var x = (e.pageX - this.offsetLeft - (w / 2)) * (w > h ? (h / w) : 1);
      var y = (e.pageY - this.offsetTop - (h / 2)) * (h > w ? (w / h) : 1);
      var direction = Math.round(Math.atan2(y, x) / 1.57079633 + 5) % 4;

      switch (direction) {
        case 0: // Top
          $(this).addClass('utd');
          break;

        case 1: // Right
          $(this).addClass('rtl');
          break;

        case 2: // Bottom
          $(this).addClass('dtu');
          break;

        case 3: // Left
          $(this).addClass('ltr');
          break;
      }
    },

    function(e) {
      $(this).on('transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd', function() {
        $(this).removeClass('utd rtl dtu ltr').addClass('rtl');
      });
    }
  );
});
.galleryWrapper {
  display: -webkit-flex;
  display: flex;
  -webkit-flex-wrap: wrap;
  flex-wrap: wrap;
}
.galleryWrapper .gallery-item {
  margin: 10px;
  -webkit-perspective: 1000;
  -moz-perspective: 1000;
  -ms-perspective: 1000;
  perspective: 1000;
  -ms-transform: perspective(1000px);
  -moz-transform: perspective(1000px);
  -moz-transform-style: preserve-3d;
  -ms-transform-style: preserve-3d;
}
.galleryWrapper .gallery-item,
.galleryWrapper .gallery-item .item .side {
  width: 100px;
  height: 100px;
  box-sizing: border-box;
}
.galleryWrapper .gallery-item .item {
  position: relative;
  -webkit-transition: 0.6s;
  -webkit-transform-style: preserve-3d;
  -ms-transition: 0.6s;
  -moz-transition: 0.6s;
  -moz-transform: perspective(1000px);
  -moz-transform-style: preserve-3d;
  -ms-transform-style: preserve-3d;
  transition: 0.6s;
  transform-style: preserve-3d;
}
.galleryWrapper .gallery-item .item .side {
  position: absolute;
  top: 0;
  left: 0;
  -webkit-backface-visibility: hidden;
  -moz-backface-visibility: hidden;
  -ms-backface-visibility: hidden;
  backface-visibility: hidden;
}
.galleryWrapper .gallery-item .item-front {
  z-index: 2;
  background-color: red;
}
.galleryWrapper .gallery-item .item-back {
  background-color: blue;
}
.galleryWrapper .gallery-item.ltr .item-front {
  -webkit-transform: rotateY(0deg);
  -moz-transform: rotateY(0deg);
  -o-transform: rotateY(0deg);
  -ms-transform: rotateY(0deg);
  transform: rotateY(0deg);
}
.galleryWrapper .gallery-item.ltr .item-back {
  -webkit-transform: rotateY(180deg);
  -moz-transform: rotateY(180deg);
  -o-transform: rotateY(180deg);
  -ms-transform: rotateY(180deg);
  transform: rotateY(180deg);
}
.galleryWrapper .gallery-item.ltr:hover .item {
  -webkit-transform: rotateY(180deg);
  -moz-transform: rotateY(180deg);
  -o-transform: rotateY(180deg);
  -ms-transform: rotateY(180deg);
  transform: rotateY(180deg);
}
.galleryWrapper .gallery-item.rtl .item-front {
  -webkit-transform: rotateY(0deg);
  -moz-transform: rotateY(0deg);
  -o-transform: rotateY(0deg);
  -ms-transform: rotateY(0deg);
  transform: rotateY(0deg);
}
.galleryWrapper .gallery-item.rtl .item-back {
  -webkit-transform: rotateY(-180deg);
  -moz-transform: rotateY(-180deg);
  -o-transform: rotateY(-180deg);
  -ms-transform: rotateY(-180deg);
  transform: rotateY(-180deg);
}
.galleryWrapper .gallery-item.rtl:hover .item {
  -webkit-transform: rotateY(-180deg);
  -moz-transform: rotateY(-180deg);
  -o-transform: rotateY(-180deg);
  -ms-transform: rotateY(-180deg);
  transform: rotateY(-180deg);
}
.galleryWrapper .gallery-item.utd .item,
.galleryWrapper .gallery-item.dtu .item {
  transform-origin: 100% 50px;
}
.galleryWrapper .gallery-item.dtu .item-front {
  -webkit-transform: rotateX(0deg);
  -moz-transform: rotateX(0deg);
  -o-transform: rotateX(0deg);
  -ms-transform: rotateX(0deg);
  transform: rotateX(0deg);
}
.galleryWrapper .gallery-item.dtu .item-back {
  -webkit-transform: rotateX(180deg);
  -moz-transform: rotateX(180deg);
  -o-transform: rotateX(180deg);
  -ms-transform: rotateX(180deg);
  transform: rotateX(180deg);
}
.galleryWrapper .gallery-item.dtu:hover .item {
  -webkit-transform: rotateX(180deg);
  -moz-transform: rotateX(180deg);
  -o-transform: rotateX(180deg);
  -ms-transform: rotateX(180deg);
  transform: rotateX(180deg);
}
.galleryWrapper .gallery-item.utd .item-front {
  -webkit-transform: rotateX(0deg);
  -moz-transform: rotateX(0deg);
  -o-transform: rotateX(0deg);
  -ms-transform: rotateX(0deg);
  transform: rotateX(0deg);
}
.galleryWrapper .gallery-item.utd .item-back {
  -webkit-transform: rotateX(-180deg);
  -moz-transform: rotateX(-180deg);
  -o-transform: rotateX(-180deg);
  -ms-transform: rotateX(-180deg);
  transform: rotateX(-180deg);
}
.galleryWrapper .gallery-item.utd:hover .item {
  -webkit-transform: rotateX(-180deg);
  -moz-transform: rotateX(-180deg);
  -o-transform: rotateX(-180deg);
  -ms-transform: rotateX(-180deg);
  transform: rotateX(-180deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="galleryWrapper bot">

  <div class="gallery-item">
    <div class="item">
      <div class="side item-front"></div>
      <div class="side item-back"></div>
    </div>
  </div>
  <!-- class="gallery-item" -->

  <div class="gallery-item">
    <div class="item">
      <div class="side item-front"></div>
      <div class="side item-back"></div>
    </div>
  </div>
  <!-- class="gallery-item" -->

  <div class="gallery-item">
    <div class="item">
      <div class="side item-front"></div>
      <div class="side item-back"></div>
    </div>
  </div>
  <!-- class="gallery-item" -->

  <div class="gallery-item">
    <div class="item">
      <div class="side item-front"></div>
      <div class="side item-back"></div>
    </div>
  </div>
  <!-- class="gallery-item" -->

</div>
<!-- class="galleryWrapper" -->

View on JSFiddle

I'm basing this animation off of CSS Flip by David Walsh

Upvotes: 4

Views: 13660

Answers (3)

codefreaK
codefreaK

Reputation: 3662

Solved Demo

Second Demo with fast paced movement

Fiddle with .on() method instead of .bind()

Example given in your question with simple modifications according to my way

[Tested on chrome Version 50.0.2661.87 m (64-bit), opera 36.0.2130.65,Firefox 45.0.2 IE version 11.0.9600.17843 ]

I have kept it simple to make things easily noticeable I used simple bounce animation with 3 seconds delay and the issue that was happening with your logic was the with each new hover you were starting new animation (Each time position is calculated and switch statement adds the class). I just put an end to it so after each transition effect is completed only new transition will begin so the issue which you were talking about regarding the right left motion followed by sudden top causing both transition happening without one completing other never happens

  1. Added a finished class to div by default
  2. Then what I did was to check it inside switch if that class was there
  3. Then remove it once entered if case
  4. Next Add css class like you do
  5. Next inside bind method remove animation class
  6. Finally add finished class

$(".box").on("webkitAnimationEnd oAnimationEnd msAnimationEnd animationend", function(e) {

  $(this).removeClass("animated animatedL animatedR animatedT");
  $(this).addClass("finished");
})

$(".box").hover(function(e) {
  var $class = $(this).hasClass("finished");
  //$(this).addClass("animated");    
  /* */
  var w = $(this).width();
  var h = $(this).height();
  var x = (e.pageX - this.offsetLeft - (w / 2)) * (w > h ? (h / w) : 1);
  var y = (e.pageY - this.offsetTop - (h / 2)) * (h > w ? (w / h) : 1);
  var direction = Math.round(Math.atan2(y, x) / 1.57079633 + 5) % 4;

  switch (direction) {
    case 0: // Top
      if ($class) {
        $(this).removeClass("finished");
        $(this).addClass('animatedT');
      }
      break;

    case 1: // Right
      if ($class) {
        $(this).removeClass("finished");
        $(this).addClass('animatedR');
      }
      break;

    case 2: // Bottom
      if ($class) {
        $(this).removeClass("finished");
        $(this).addClass('animated');
      }
      break;

    case 3: // Left
      if ($class) {
        $(this).removeClass("finished");
        $(this).addClass('animatedL');
      }


  }
})
@-webkit-keyframes bounce {
  0% {
    top: 0;
    animation-timing-function: ease-out;
  }
  17% {
    top: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    top: 0;
    animation-timing-function: ease-out;
  }
  51% {
    top: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    top: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    top: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 0;
  }
}
@-moz-keyframes bounce {
  0% {
    top: 0;
    animation-timing-function: ease-out;
  }
  17% {
    top: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    top: 0;
    animation-timing-function: ease-out;
  }
  51% {
    top: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    top: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    top: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 0;
  }
}
@keyframes bounce {
  0% {
    top: 0;
    animation-timing-function: ease-out;
  }
  17% {
    top: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    top: 0;
    animation-timing-function: ease-out;
  }
  51% {
    top: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    top: 0px;
  }
  85% {
    top: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 0;
  }
}
@-webkit-keyframes bounceL {
  0% {
    left: 0;
    animation-timing-function: ease-out;
  }
  17% {
    left: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    left: 0;
    animation-timing-function: ease-out;
  }
  51% {
    left: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    left: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    left: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    left: 0;
  }
}
@-moz-keyframes bounceL {
  0% {
    left: 0;
    animation-timing-function: ease-out;
  }
  17% {
    left: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    left: 0;
    animation-timing-function: ease-out;
  }
  51% {
    left: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    left: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    left: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    left: 0;
  }
}
@keyframes bounceL {
  0% {
    left: 0;
    animation-timing-function: ease-out;
  }
  17% {
    left: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    left: 0;
    animation-timing-function: ease-out;
  }
  51% {
    left: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    left: 0px;
  }
  85% {
    left: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    left: 0;
  }
}
@-webkit-keyframes bounceR {
  0% {
    right: 0;
    animation-timing-function: ease-out;
  }
  17% {
    right: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    right: 0;
    animation-timing-function: ease-out;
  }
  51% {
    right: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    right: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    right: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    right: 0;
  }
}
@-moz-keyframes bounceR {
  0% {
    right: 0;
    animation-timing-function: ease-out;
  }
  17% {
    right: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    right: 0;
    animation-timing-function: ease-out;
  }
  51% {
    right: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    right: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    right: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    right: 0;
  }
}
@keyframes bounceR {
  0% {
    right: 0;
    animation-timing-function: ease-out;
  }
  17% {
    right: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    right: 0;
    animation-timing-function: ease-out;
  }
  51% {
    right: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    right: 0px;
  }
  85% {
    right: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    right: 0;
  }
}
@-webkit-keyframes bounceT {
  0% {
    top: 0;
    animation-timing-function: ease-out;
  }
  17% {
    top: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    top: 0;
    animation-timing-function: ease-out;
  }
  51% {
    top: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    top: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    top: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 0;
  }
}
@-moz-keyframes bounceT {
  0% {
    top: 0;
    animation-timing-function: ease-out;
  }
  17% {
    top: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    top: 0;
    animation-timing-function: ease-out;
  }
  51% {
    top: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    top: 0px;
    animation-timing-function: ease-out;
  }
  85% {
    top: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 0;
  }
}
@keyframes bounceT {
  0% {
    top: 0;
    animation-timing-function: ease-out;
  }
  17% {
    top: 15px;
    animation-timing-function: ease-in;
  }
  34% {
    top: 0;
    animation-timing-function: ease-out;
  }
  51% {
    top: 8px;
    animation-timing-function: ease-in;
  }
  68% {
    top: 0px;
  }
  85% {
    top: 3px;
    animation-timing-function: ease-in;
  }
  100% {
    top: 0;
  }
}
#container {
  position: relative;
}
.box {
  position: relative;
  float: left;
  background: #f00;
  width: 50px;
  height: 50px;
  margin-right: 5px;
  margin: 50px;
}
.box.animated {
  -moz-animation: bounce .5s;
  -webkit-animation: bounce .5s;
  animation: bounce .5s;
}
.box.animatedL {
  -moz-animation: bounceL .5s;
  -webkit-animation: bounceL .5s;
  animation: bounceL .5s;
}
.box.animatedR {
  -moz-animation: bounceR .5s;
  -webkit-animation: bounceR .5s;
  animation: bounceR .5s;
}
.box.animatedT {
  -moz-animation: bounceT .5s;
  -webkit-animation: bounceT .5s;
  animation: bounceT .5s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<div id="container">
  <div class="box finished"></div>
  <div class="box finished"></div>
  <div class="box finished"></div>
  <div class="box finished"></div>
</div>

EDIT-

change .bind() method instead use .on() as bind is deprecated

$(".box").on("webkitAnimationEnd oAnimationEnd msAnimationEnd animationend", function (e){

 $(this).removeClass("animated animatedL animatedR animatedT");
  $(this).addClass("finished");  
  })

In your original fiddle I just made a few changes I just added class name
.finished and then inside the switch case added a if loop inside to check if the hovered element has .finished class in its list of classes using .hasClass() which returns boolean if true only then the animation class is added and inside your .on() function I just added .finished class indicating end of animation. This sequence prevents from animation overlap and then again to be on the safe side you can add a delay of a 100 milliseconds if you like after transition end

Upvotes: 6

Woody Payne
Woody Payne

Reputation: 605

You don't need Javascript or Jquery for this. Use a transition time of 0.6s when you hover and an animation time of 0.01 when you hover off. This way, the animation will reset itself to its original position pretty much immediately and stop the funky behaviour. Remember, you can have different animation effects on hovered and unhovered elements. The same applies for timing.

So, specific to your example, change the timing on your element from 0.6 to 0.01 (or another equally teeny number, not sure how low it can go...) and add a timing of 0.6 to the :hover of your element. That way, the animation will behave as you expect on hover and snap back to the original if you hover off.

In regards to Javascript knowing when the animation is completely, that's unfortunately down to manual timing on your part. You could use a timeout of 0.6 seconds that activates when you hover and then do whatever you wanted after that timeout. I've found timing CSS and Javascript together is a bit of a nightmare though, and you should generally go for one or the other. If you want an example of the timeout method let me know and I'll edit the answer with one.

Edit for your comment:

Yes, I think you're going about this the wrong way. As I said previously, timing with CSS and Javascript is tricky, but the fundamental problem here is there's no way of CSS communicating with your page to tell you an animation is currently active.

What you're looking for is to throttle the animation if one is already going. That can't be done with CSS, but it can with Javascript.

Instead of having the animation in a CSS class, have it in Javascript. Then, create a hover event in Javascript that will add a class, eg 'active'. When you hover add your animation to the element using jQuery's animate function and inside the done function, remove the class 'active':

Element.on('hover',function(){
    if( !$(this).hasClass('active'))
        $(this).addClass('active').animate({'rotate:etc'},600,function(){
            $(this).removeClass('active')
        })
}

You can then use a mouseout event to remove the class if the user hovers off prematurely.

Upvotes: 0

Medda86
Medda86

Reputation: 1630

I had a similar problem the other day with a rotate I wanted to finish. I got it working like this, maybe it can help you in some way.

function completeRotate(obj){
        obj.one('animationiteration webkitAnimationIteration', function() {
        $(this).off('animationiteration webkitAnimationIteration');
        $(this).removeClass('rotating');
    });
};

See reference for one and off here http://api.jquery.com/one/

Peace amd love

Upvotes: 0

Related Questions