Vue.js course Β· Module 13: Transitions & Animations

JavaScript Animation Hooks

3 min read
In this lesson7

CSS classes are the "autopilot" of animations - they work great for simple transitions. But sometimes you need full control over animations - e.g., dynamically computed values, data-driven animations, or integration with JS libraries. In such cases, Vue offers JavaScript animation hooks - like manual rocket steering instead of autopilot.

Available Hooks

<Transition> emits events at each stage of the animation. You can listen to them:

1<Transition
2  :css="false"
3  @before-enter="onBeforeEnter"
4  @enter="onEnter"
5  @after-enter="onAfterEnter"
6  @enter-cancelled="onEnterCancelled"
7  @before-leave="onBeforeLeave"
8  @leave="onLeave"
9  @after-leave="onAfterLeave"
10  @leave-cancelled="onLeaveCancelled"
11>
12  <div v-if="show">...</div>
13</Transition>

Enter Hooks - Execution Order

  1. @before-enter(el) - called before the element is inserted into the DOM. Here you set the initial animation state (e.g., opacity: 0, scale: 0.5).

  2. @enter(el, done) - called on the next frame after the element is inserted. Here you run the actual animation. You must call done() when the animation finishes!

  3. @after-enter(el) - called after the animation finishes (after done()). Here you clean up after the animation (remove inline styles, etc.).

  4. @enter-cancelled(el) - called when the enter animation is interrupted (e.g., the user quickly toggles the element).

Leave Hooks - Analogous Order

  1. @before-leave(el) - initial state of leave, element is still visible
  2. @leave(el, done) - leave animation, you must call done()!
  3. @after-leave(el) - cleanup after leave
  4. @leave-cancelled(el) - leave animation interrupted (only with v-show)

The :css="false" Attribute

When using only JS hooks, add :css="false" so Vue doesn't try to apply CSS classes or listen for transitionend/animationend events:

1<Transition :css="false" @enter="onEnter" @leave="onLeave">
2  <div v-if="show">Control panel</div>
3</Transition>

Without this attribute, Vue will wait for both the done() callback and the CSS transition event - which can cause unpredictable behavior.

The done() Callback - A Key Element

done() is mandatory in the @enter and @leave hooks. It tells Vue that the animation has finished and it can proceed to the next step:

1function onBeforeEnter(el) {
2  // Initial state - element invisible
3  el.style.opacity = 0
4  el.style.transform = 'translateY(20px)'
5}
6
7function onEnter(el, done) {
8  // Force reflow so the browser registers the initial state
9  el.offsetHeight
10
11  // Start the animation
12  el.style.transition = 'all 0.5s ease'
13  el.style.opacity = 1
14  el.style.transform = 'translateY(0)'
15
16  // Notify Vue when the animation finishes
17  el.addEventListener('transitionend', done, { once: true })
18}
19
20function onAfterEnter(el) {
21  // Cleanup - remove inline transition
22  el.style.transition = ''
23}

Without calling done(), Vue doesn't know when the animation has finished - in the case of @leave, the element will never be removed from the DOM.

Practical Example - Data-Driven Animation

JS hooks allow dynamic values, which CSS classes cannot provide:

1function onEnter(el, done) {
2  // Dynamic animation direction based on data
3  const direction = el.dataset.direction || 'left'
4  const offset = direction === 'left' ? '-100px' : '100px'
5
6  el.style.opacity = 0
7  el.style.transform = `translateX(${offset})`
8  el.offsetHeight // reflow
9
10  el.style.transition = 'all 0.4s ease-out'
11  el.style.opacity = 1
12  el.style.transform = 'translateX(0)'
13
14  el.addEventListener('transitionend', done, { once: true })
15}

When to Use JS Hooks Instead of CSS?

Use CSS classesUse JS hooks
Simple fade, slideDynamic animation values
Fixed durationsData-dependent durations
Typical transitionsIntegration with GSAP, Anime.js
Most casesComplex animation sequences
Code for this lesson: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const showAlert = ref(false)
5
6function onBeforeEnter(el) {
7  el.style.opacity = 0
8  el.style.transform = 'scale(0.5)'
9}
10
11function onEnter(el, done) {
12  // Entry animation without CSS
13  el.offsetHeight // force reflow
14  el.style.transition = 'all 0.6s ease'
15  el.style.opacity = 1
16  el.style.transform = 'scale(1)'
17  el.addEventListener('transitionend', done, { once: true })
18}
19
20function onLeave(el, done) {
21  el.style.transition = 'all 0.4s ease'
22  el.style.opacity = 0
23  el.style.transform = 'scale(0.5) rotate(10deg)'
24  el.addEventListener('transitionend', done, { once: true })
25}
26</script>
27
28<template>
29  <div class="nova-lab">
30    <h1>JavaScript Animation Hooks</h1>
31
32    <button @click="showAlert = !showAlert" class="btn">
33      {{ showAlert ? 'Close alert' : 'Show alert' }}
34    </button>
35
36    <Transition
37      :css="false"
38      @before-enter="onBeforeEnter"
39      @enter="onEnter"
40      @leave="onLeave"
41    >
42      <div v-if="showAlert" class="alert">
43        WARNING: Gravitational anomaly detected in sector 7!
44      </div>
45    </Transition>
46  </div>
47</template>
48
49<style scoped>
50.nova-lab {
51  background: #0a0e27;
52  color: #00ff88;
53  min-height: 100vh;
54  padding: 20px;
55  font-family: monospace;
56}
57h1 { color: #00ff88; }
58.btn {
59  background: #e74c3c;
60  color: white;
61  border: none;
62  padding: 10px 20px;
63  border-radius: 8px;
64  cursor: pointer;
65  margin: 15px 0;
66}
67.alert {
68  background: rgba(231, 76, 60, 0.2);
69  border: 2px solid #e74c3c;
70  color: #e74c3c;
71  padding: 20px;
72  border-radius: 12px;
73  font-size: 16px;
74}
75</style>

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which JavaScript hook is called when an element enters the DOM?

  2. 2. What should be called at the end of the @enter hook to inform Vue that the animation has finished?

Hands-on tasks in the game

  • Vertical ordering

    Arrange the JavaScript Transition hooks in the order they are called (enter):

  • Code editor

    Implement @enter and @leave using el.style and the done() callback.

  • Click in order

    Arrange the @enter hook code in the correct order:

  • Horizontal ordering

    Arrange the Transition syntax that disables CSS classes:

  • Code editor

    Use :css='false' and JS hooks to animate opacity and scale.

  • Horizontal ordering

    Arrange the @leave hook declaration in the template:

Useful articles