Howdy_McGee Howdy_McGee - 7 months ago 21
Javascript Question

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

Answer

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 defualt
  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 orginal 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 safe side you can add a delay of a 100 milliseconds if you like after transition end

Comments