coderain blog

Why animationend Event Sometimes Doesn’t Fire in IE11: Troubleshooting CSS Animation Race Conditions

CSS animations have revolutionized web interactivity, enabling smooth transitions and dynamic effects without relying on heavy JavaScript libraries. A critical part of working with CSS animations is the animationend event, which fires when an animation completes—allowing developers to trigger follow-up actions like removing an element, updating content, or restarting the animation.

However, legacy browsers like Internet Explorer 11 (IE11) often throw curveballs. One of the most frustrating issues is the animationend event failing to fire silently, leaving developers scratching their heads as their code works flawlessly in Chrome, Firefox, or Edge but breaks in IE11.

The root cause? More often than not, race conditions between the browser’s rendering pipeline and JavaScript event listener registration. IE11’s unique rendering engine and limited CSS animation support amplify these timing issues, making even straightforward animations unreliable.

In this blog, we’ll demystify why animationend fails in IE11, explore common scenarios where this occurs, and provide actionable solutions to troubleshoot and fix these race conditions.

2025-12

Table of Contents#

  1. Understanding the animationend Event
  2. Why IE11 Is a Special Case for CSS Animations
  3. Race Conditions: The Hidden Culprit
  4. Common Scenarios Where animationend Fails in IE11
  5. Troubleshooting Steps for IE11 animationend Issues
  6. Solutions and Workarounds
  7. Conclusion
  8. References

Understanding the animationend Event#

The animationend event is part of the CSS Animations API, firing on an element when its CSS animation completes. This includes:

  • Natural completion after the animation’s duration elapses.
  • Abortions (e.g., via animation-play-state: paused, though this depends on the browser).
  • Restarts (if the animation is reset, the event fires again for the new iteration).

In modern browsers, animationend is reliable: once an animation is applied to an element, the event queues and fires even if the listener is added after the animation starts (within reason). IE11, however, does not handle this queuing consistently.

Why IE11 Is a Special Case for CSS Animations#

IE11 has partial, buggy support for CSS animations. Unlike modern browsers, it:

  • Requires -ms- prefixes for most animation properties (e.g., -ms-animation, -ms-animation-duration).
  • Uses a single-threaded rendering pipeline, where JavaScript execution and CSS rendering block each other.
  • Has a slower JavaScript engine, leading to delayed event listener registration.
  • Fails to queue animationend events if the animation starts before the listener is attached.

These quirks make IE11 far more prone to race conditions between animation start and listener registration.

Race Conditions: The Hidden Culprit#

A race condition occurs when the outcome of a process depends on the timing of unrelated events. In the context of animationend in IE11, the race is between:

  1. The browser starting/running the CSS animation.
  2. JavaScript registering the animationend event listener.

If the animation finishes before the listener is registered, IE11 will never fire animationend—the event is lost. Modern browsers avoid this by buffering events or ensuring listeners added during the animation lifecycle still receive the event. IE11? Not so much.

Common Scenarios Where animationend Fails in IE11#

Let’s break down the most frequent scenarios where animationend fails in IE11, with code examples to illustrate the problem.

Scenario 1: Dynamically Adding Animations Without Proper Timing#

A common pattern is to trigger an animation by adding a CSS class via JavaScript (e.g., to fade in a modal). If the animationend listener is added after the class (and thus the animation) is applied, IE11 may miss the event entirely.

Problematic Code:

<!-- CSS -->
<style>
  .fade-in {
    -ms-animation: fadeIn 0.3s forwards; /* IE11 requires -ms- prefix */
    animation: fadeIn 0.3s forwards;
  }
  @-ms-keyframes fadeIn { /* -ms- prefix for keyframes in IE11 */
    from { opacity: 0; }
    to { opacity: 1; }
  }
  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
</style>
 
<!-- HTML -->
<div id="modal" class="hidden">Hello, IE11!</div>
 
<!-- JavaScript -->
<script>
  const modal = document.getElementById('modal');
  
  // Step 1: Add animation class (starts animation)
  modal.classList.add('fade-in');
  
  // Step 2: Add listener AFTER animation starts
  modal.addEventListener('animationend', () => {
    console.log('Animation ended!'); // Often fails in IE11
  });
</script>

Why It Fails in IE11:
Adding the fade-in class triggers the animation immediately. If the JavaScript engine is slow (or the animation is short), the animation may finish before the addEventListener call completes. IE11 does not retroactively fire animationend for listeners added post-completion.

Scenario 2: Using animation: none Before Listener Registration#

If an element initially has animation: none (to prevent accidental animation), then later has its class updated to start the animation, IE11 may not fire animationend if the listener is added after the class change.

Problematic Code:

/* Initial state: animation disabled */
.modal {
  -ms-animation: none;
  animation: none;
  opacity: 0;
}
.modal.active {
  -ms-animation: fadeIn 0.3s forwards;
  animation: fadeIn 0.3s forwards;
}
// Enable animation by adding "active" class
modal.classList.add('active');
 
// Add listener after class change
modal.addEventListener('animationend', onAnimationEnd);

Why It Fails in IE11:
IE11’s rendering engine may treat the transition from animation: none to animation: fadeIn as a “sudden” state change. If the listener isn’t registered before this transition, the browser may not recognize the animation as active and thus not fire animationend.

Scenario 3: Fast or Zero-Duration Animations#

Animations with very short durations (e.g., 0ms, 1ms) or zero duration are prone to animationend failure in IE11. The animation finishes so quickly that the listener—even if added milliseconds later—misses the event.

Problematic Code:

.flash {
  -ms-animation: flash 0ms forwards; /* Zero duration */
  animation: flash 0ms forwards;
}
modal.classList.add('flash');
modal.addEventListener('animationend', () => {
  // Rarely fires in IE11: animation finishes instantly
});

Scenario 4: Animation Restarts or Multiple Animations#

If an animation is restarted (e.g., by removing and readding the animation class), IE11 may fail to fire animationend for the restarted animation. This is due to how IE11 handles animation state resets.

Problematic Code:

// Restart animation by removing/adding the class
modal.classList.remove('fade-in');
modal.classList.add('fade-in'); // Animation restarts
modal.addEventListener('animationend', onEnd); // May not fire for the restart

Scenario 5: Missing -ms- Prefixes#

IE11 requires -ms- prefixes for CSS animation properties (e.g., -ms-animation, -ms-animation-duration) and keyframes (@-ms-keyframes). Forgetting these prefixes means the animation never runs at all—so animationend never fires.

Problematic Code:

/* Missing -ms- prefixes */
.modal.active {
  animation: fadeIn 0.3s forwards; /* IE11 ignores unprefixed animation */
}
@keyframes fadeIn { /* IE11 ignores unprefixed keyframes */
  from { opacity: 0; }
  to { opacity: 1; }
}

Why It Fails:
Without -ms- prefixes, IE11 doesn’t recognize the animation, so no animationend event is ever triggered.

Troubleshooting Steps for IE11 animationend Issues#

If animationend isn’t firing in IE11, use these steps to diagnose the root cause:

  1. Verify the Animation Runs: Use IE11’s F12 Developer Tools (Elements > Styles) to confirm the animation properties (with -ms- prefixes) are applied to the element. If not, fix prefixes.

  2. Check Listener Registration: Add a console.log immediately before addEventListener to ensure the listener is being registered. If the log doesn’t appear, debug your JavaScript execution flow.

  3. Test with a Longer Duration: Temporarily increase the animation duration (e.g., to 3s) to see if animationend fires. If it does, the issue is likely a race condition with short animations.

  4. Inspect Event Support: Use 'animationend' in window to confirm IE11 recognizes the event (it should, but older IE versions may not).

Solutions and Workarounds#

Now, let’s fix these issues with targeted solutions.

Solution 1: Register Listeners Before Starting the Animation#

The golden rule for IE11: Add the animationend listener before triggering the animation. This ensures the listener is registered when the animation starts, eliminating the race condition.

Fixed Code:

// Step 1: Add listener FIRST
modal.addEventListener('animationend', onAnimationEnd);
 
// Step 2: Start animation SECOND
modal.classList.add('fade-in');

Solution 2: Force Reflow to Sync CSS and JavaScript#

IE11 sometimes batches CSS updates, delaying the animation start until after JavaScript execution. To force the browser to process the CSS change immediately (and ensure the animation starts after the listener is registered), trigger a reflow by reading a layout property (e.g., offsetHeight).

Fixed Code:

modal.addEventListener('animationend', onAnimationEnd);
 
// Force reflow: read a layout property to flush CSS updates
modal.offsetHeight; // Triggers reflow
 
// Now start the animation
modal.classList.add('fade-in');

Why this works: Reading offsetHeight forces the browser to recalculate the element’s layout, ensuring the animation class is applied after the listener is registered.

Solution 3: Avoid Zero-Duration Animations#

Replace 0ms animations with minimal durations (e.g., 10ms) to give the listener time to register. For “instant” animations, use CSS transitions instead (IE11 supports transitions more reliably).

Solution 4: Use animationstart to Validate Execution#

Listen for animationstart to confirm the animation is running. If animationstart doesn’t fire, the issue is likely with the animation itself (e.g., missing prefixes).

modal.addEventListener('animationstart', () => {
  console.log('Animation started!'); // Confirms animation is active
});
modal.addEventListener('animationend', onEnd);
modal.classList.add('fade-in');

Solution 5: Fall Back to Timeouts for Critical Paths#

For mission-critical animations, use a timeout as a fallback. Calculate the timeout duration to match the animation duration (plus a small buffer for IE11’s slowness).

const ANIMATION_DURATION = 300; // ms
modal.addEventListener('animationend', onEnd);
modal.classList.add('fade-in');
 
// Fallback: trigger onEnd if animationend doesn't fire
setTimeout(() => {
  if (!animationCompleted) { // Track completion with a flag
    onEnd();
  }
}, ANIMATION_DURATION + 100); // Add 100ms buffer

Conclusion#

The animationend event’s unreliability in IE11 stems from race conditions between animation start and listener registration, compounded by IE11’s quirky rendering engine. By registering listeners before starting animations, forcing reflows, avoiding zero-duration animations, and using fallbacks like timeouts, you can mitigate these issues.

While IE11 is a legacy browser, many enterprise and government environments still rely on it. With the solutions above, you can ensure your CSS animations—and their animationend handlers—work reliably, even in this outdated but persistent browser.

References#