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

Advanced Animation Patterns

4 min read
In this lesson5

In NOVA LAB's advanced systems, we need sophisticated effects - cascading dashboard element loading, animation on first page display, precise duration control, and respect for users' motion preferences. Learn the patterns that give interfaces a professional feel.

Appear - Animation on Mount

By default, <Transition> only animates the element when toggling v-if/v-show. The appear attribute causes the animation to also run on the first render (mount) of the component:

1<Transition appear name="fade">
2  <div>This element animates on page load</div>
3</Transition>

This is the ideal solution for elements visible right after loading - the station logo, dashboard header, status panel. Without appear, these elements would simply "be there" without any enter animation.

You can use special CSS classes for the appear animation (independent from the normal enter classes):

1<Transition
2  appear
3  appear-from-class="appear-start"
4  appear-active-class="appear-running"
5  appear-to-class="appear-end"
6>
7  <div>Custom appear animation</div>
8</Transition>

Or with libraries:

1<Transition
2  appear
3  appear-active-class="animate__animated animate__fadeInDown"
4>
5  <header>NOVA LAB Control Center</header>
6</Transition>

Staggered List Animation

One of the most impressive patterns - cascading appearance of list elements, where each successive element appears with an increasing delay. It looks like data displaying on a NOVA LAB holographic dashboard, element by element:

1<TransitionGroup
2  appear
3  :css="false"
4  @before-enter="onBeforeEnter"
5  @enter="onEnter"
6  tag="div"
7>
8  <div v-for="(item, index) in items" :key="item.id" :data-index="index">
9    {{ item.name }}
10  </div>
11</TransitionGroup>

The key is the :data-index attribute - it passes the element index to the JS hook, which calculates the delay:

1function onBeforeEnter(el) {
2  el.style.opacity = 0
3  el.style.transform = 'translateY(20px)'
4}
5
6function onEnter(el, done) {
7  // Each successive element has a 150ms greater delay
8  const delay = el.dataset.index * 150
9
10  setTimeout(() => {
11    el.style.transition = 'all 0.5s ease'
12    el.style.opacity = 1
13    el.style.transform = 'translateY(0)'
14    el.addEventListener('transitionend', done, { once: true })
15  }, delay)
16}

The effect: element 0 appears immediately, element 1 after 150ms, element 2 after 300ms, etc. For 10 elements, the entire animation takes 1.5s + 0.5s (animation duration itself) = ~2s.

Tip: Limit the delay to a max of ~1s so the user doesn't wait too long for the last elements:

1const delay = Math.min(el.dataset.index * 100, 1000)

Explicit Duration

When an element has both CSS transition and animation, or when nested elements have different animation durations, Vue may not correctly detect the end point. The :duration attribute lets you explicitly specify the duration in milliseconds:

1<!-- Same duration for enter and leave -->
2<Transition :duration="500">...</Transition>
3
4<!-- Different durations for enter and leave -->
5<Transition :duration="{ enter: 300, leave: 500 }">...</Transition>

This is particularly useful when:

  • Animating nested elements with different durations
  • Combining CSS transition and animation on the same element
  • Using external libraries whose duration isn't automatically detected

Accessibility - prefers-reduced-motion

Respect users' motion settings. People with vestibular disorders or epilepsy may have the prefers-reduced-motion preference enabled. Your CSS should honor this:

1/* Normal animations */
2.fade-enter-active,
3.fade-leave-active {
4  transition: opacity 0.3s ease, transform 0.3s ease;
5}
6
7.fade-enter-from,
8.fade-leave-to {
9  opacity: 0;
10  transform: translateY(10px);
11}
12
13/* Disable animations for users who don't want them */
14@media (prefers-reduced-motion: reduce) {
15  .fade-enter-active,
16  .fade-leave-active {
17    transition: none;
18  }
19
20  .fade-enter-from,
21  .fade-leave-to {
22    opacity: 1;
23    transform: none;
24  }
25}

You can also handle this in JavaScript:

1const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
2
3function onEnter(el, done) {
4  if (prefersReducedMotion) {
5    done() // Skip the animation
6    return
7  }
8  // Normal animation...
9}

Performance Best Practices

  1. Only animate transform and opacity - these properties are GPU-accelerated and don't cause reflow/repaint. Avoid animating width, height, top, left, margin, padding.

  2. Use will-change for heavy animations - it informs the browser about an upcoming animation, allowing for optimization:

1.heavy-animation-enter-active {
2  will-change: transform, opacity;
3  transition: all 0.5s ease;
4}
  1. Prefer CSS over JS - CSS animations are optimized by the browser and run on a separate thread. Use JS hooks only when you need dynamic values.

  2. Short durations - UI animations should last 200-500ms. Longer animations slow down interaction and frustrate users.

  3. Test on mobile devices - animations that run smoothly on desktop may lag on older phones.

Code for this lesson: App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Appear - animacja przy mount
5const items = ref([
6  { id: 1, name: 'Life Support', delay: 0 },
7  { id: 2, name: 'Navigation', delay: 100 },
8  { id: 3, name: 'Communications', delay: 200 },
9  { id: 4, name: 'Power Grid', delay: 300 },
10  { id: 5, name: 'Science Lab', delay: 400 }
11])
12
13function onBeforeEnter(el) {
14  el.style.opacity = 0
15  el.style.transform = 'translateY(20px)'
16}
17
18function onEnter(el, done) {
19  const delay = el.dataset.index * 150
20  setTimeout(() => {
21    el.style.transition = 'all 0.5s ease'
22    el.style.opacity = 1
23    el.style.transform = 'translateY(0)'
24    el.addEventListener('transitionend', done, { once: true })
25  }, delay)
26}
27</script>
28
29<template>
30  <div class="nova-lab">
31    <h1>Staggered List + Appear</h1>
32
33    <TransitionGroup
34      appear
35      :css="false"
36      @before-enter="onBeforeEnter"
37      @enter="onEnter"
38      tag="div"
39      class="systems"
40    >
41      <div
42        v-for="(item, index) in items"
43        :key="item.id"
44        :data-index="index"
45        class="system-card"
46      >
47        <span class="dot">●</span>
48        {{ item.name }}
49      </div>
50    </TransitionGroup>
51  </div>
52</template>
53
54<style scoped>
55.nova-lab {
56  background: #0a0e27;
57  color: #00ff88;
58  min-height: 100vh;
59  padding: 20px;
60  font-family: monospace;
61}
62h1 { color: #00ff88; }
63.systems { margin-top: 20px; }
64.system-card {
65  display: flex;
66  align-items: center;
67  gap: 12px;
68  background: rgba(0, 180, 216, 0.1);
69  border: 1px solid #00b4d8;
70  padding: 14px 20px;
71  margin: 8px 0;
72  border-radius: 8px;
73  font-size: 14px;
74}
75.dot { color: #00ff88; }
76</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. What does the appear attribute do in <Transition>?

  2. 2. How do you set an explicit animation duration in <Transition>?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Vertical ordering

    Arrange the steps for implementing a staggered (delayed) list animation:

  • Code editor

    Use data-index and gsap/setTimeout for staggered animation in TransitionGroup.

  • Horizontal ordering

    Arrange the appear attribute with a custom class:

  • Click in order

    Arrange the code for passing an index to a list element:

  • Vertical ordering

    Arrange the animation techniques from simplest to most advanced:

  • Horizontal ordering

    Arrange the duration syntax with different enter/leave times:

  • Click in order

    Arrange the JavaScript hooks in the order they are called (leave animation):

  • Click in order

    Arrange the CSS transition shorthand declaration:

  • Vertical ordering

    Arrange the 6 CSS classes added by <Transition> in chronological order:

Useful articles