CSS allows you to create animations with transitions and keyframes that once were only possible with JavaScript or Flash. Unfortunately, with CSS there’s no way to perform a callback when an animation is complete. With JavaScript, it’s possible to detect the end of a CSS transition or animation and then trigger a function.

Separate the roles

Handle the animations (with transitions or keyframes) in your CSS; handle the event timing and triggers in your JavaScript.

.button {
  transition-property: background-color, transform;
  transition-duration: 1.5s;
  transition-timing-function: linear;
}

.animate {
  background-color: red;
  transform: translateY(50px);
}
$(".button").click(function() {
  $(this).addClass("animate");
});

Detecting and executing when transitions end with jQuery

Using JavaScript, we can detect the transitionend event; however for cross-browser, support we need to include the other browsers’ prefixes.

Then bind the event with jQuery’s one function, which ensures that it runs only once (it unbinds the event handler after it runs once). (Read more about the one function)

$(".button").click(function(){
  $(this).addClass("animate");
  $(this).one("webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend",
              function(event) {
    // Do something when the transition ends
  });
});

The above solution works, but the problem is, depending on the browser, it can fire twice (i.e. Chrome supports both webkitTransitionEnd and transitionend)

Here’s what the console returns when I log the event in Chrome:

Detect the supported event property name

We’ll introduce a function, whichTransitionEvent, to detect the supported event property name; assign a variable, in this case transitionEvent, to hold the event property name; and pass the variable as the first argument of the one function.

// Function from David Walsh: http://davidwalsh.name/css-animation-callback
function whichTransitionEvent(){
  var t,
      el = document.createElement("fakeelement");

  var transitions = {
    "transition"      : "transitionend",
    "OTransition"     : "oTransitionEnd",
    "MozTransition"   : "transitionend",
    "WebkitTransition": "webkitTransitionEnd"
  }

  for (t in transitions){
    if (el.style[t] !== undefined){
      return transitions[t];
    }
  }
}

var transitionEvent = whichTransitionEvent();

$(".button").click(function(){
  $(this).addClass("animate");
  $(this).one(transitionEvent,
              function(event) {
    // Do something when the transition ends
  });
});

This ensures that, even in Chrome, the event only fires once:

Here’s a demo:

See the Pen rEpqJ by Jonathan Suh (@jonsuh) on CodePen.

Detect when animations (keyframes) end

The above solution can be slightly tweaked to account for animations done with keyframes.

Like transitions have the transitionend event, animations have the animationend event. We’ll take the whichtransitionEvent function and swap out instances of transition for animation (case sensitive).

function whichAnimationEvent(){
  var t,
      el = document.createElement("fakeelement");

  var animations = {
    "animation"      : "animationend",
    "OAnimation"     : "oAnimationEnd",
    "MozAnimation"   : "animationend",
    "WebkitAnimation": "webkitAnimationEnd"
  }

  for (t in animations){
    if (el.style[t] !== undefined){
      return animations[t];
    }
  }
}

var animationEvent = whichAnimationEvent();

$(".button").click(function(){
  $(this).addClass("animate");
  $(this).one(animationEvent,
              function(event) {
    // Do something when the animation ends
  });
});

Vanilla JavaScript

With Vanilla JavaScript, it’s slightly trickier. Using the whichTransitionEvent function, bind transitionEvent with addEventListener.

var button = document.querySelector(".button"),
    transitionEvent = whichTransitionEvent();

button.addEventListener("click", function() {
  if (button.classList) {
    button.classList.add("animate");
  } else {
    button.className += " " + "animate";
  }

  button.addEventListener(transitionEvent, customFunction);
});

function customFunction(event) {
  button.removeEventListener(transitionEvent, customFunction);

  // Do something when the transition ends
}

The second argument must be a function name as opposed to function({}). This is important because we must unbind the listener, otherwise the listener will keep running, causing it to run multiple times.

These solutions may work in your use case, but if you’re looking for more intricate, complex animations, you may want to look into a library that is feature-rich and offer more power, like GreenSock.