Chuyển đến nội dung chính

Lesson 17: Animations & Transitions

Vue Transition, TransitionGroup. Page transitions, layout transitions. GSAP, Motion One integration. View Transitions API. Scroll-driven animations.

💻 Programming — Lesson 17 Lesson 17: Animations & Transitions

Vue.js & Nuxt: From Basics to Advanced

Part 5: Advanced Patterns

xdev.asia

1. Vue Transition

<script setup lang="ts">
const isVisible = ref(true)
</script>

<template>
  <button @click="isVisible = !isVisible">Toggle</button>

  <Transition name="fade">
    <div v-if="isVisible" class="box">Fade content</div>
  </Transition>

  <Transition name="slide" mode="out-in">
    <div :key="currentTab">{{ currentTab }}</div>
  </Transition>
</template>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

.slide-enter-active, .slide-leave-active {
  transition: all 0.3s ease;
}
.slide-enter-from {
  transform: translateX(30px);
  opacity: 0;
}
.slide-leave-to {
  transform: translateX(-30px);
  opacity: 0;
}
</style>

2. TransitionGroup

<script setup lang="ts">
const items = ref([1, 2, 3, 4, 5])

function addItem() {
  const num = Math.max(...items.value) + 1
  items.value.splice(Math.floor(Math.random() * items.value.length), 0, num)
}

function removeItem(item: number) {
  items.value = items.value.filter(i => i !== item)
}
</script>

<template>
  <button @click="addItem">Add</button>
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item" @click="removeItem(item)">
      {{ item }}
    </li>
  </TransitionGroup>
</template>

<style>
.list-enter-active, .list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from, .list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
.list-move {
  transition: transform 0.5s ease;
}
.list-leave-active {
  position: absolute;
}
</style>

3. Page Transitions (Nuxt)

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
    layoutTransition: { name: 'layout', mode: 'out-in' },
  },
})
/* assets/css/transitions.css */
.page-enter-active, .page-leave-active {
  transition: all 0.3s;
}
.page-enter-from, .page-leave-to {
  opacity: 0;
  filter: blur(1rem);
}

.layout-enter-active, .layout-leave-active {
  transition: all 0.4s;
}
.layout-enter-from, .layout-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
<!-- Per-page custom transition -->
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'rotate',
    mode: 'out-in',
  },
})
</script>

4. GSAP Integration

<script setup lang="ts">
import gsap from 'gsap'

const boxRef = ref<HTMLElement | null>(null)

onMounted(() => {
  if (boxRef.value) {
    gsap.from(boxRef.value, {
      duration: 1,
      y: 100,
      opacity: 0,
      ease: 'power3.out',
    })
  }
})

// Stagger animation
function animateList() {
  gsap.from('.list-item', {
    y: 50,
    opacity: 0,
    duration: 0.5,
    stagger: 0.1,
    ease: 'power2.out',
  })
}
</script>

<template>
  <div ref="boxRef" class="box">Animated box</div>
</template>

5. View Transitions API

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    viewTransition: true, // Enable View Transitions API
  },
})
<template>
  <!-- Assign transition name -->
  <img
    :src="post.image"
    :style="{ viewTransitionName: `post-image-${post.id}` }"
  />
  <h1 :style="{ viewTransitionName: `post-title-${post.id}` }">
    {{ post.title }}
  </h1>
</template>

Next article: Advanced Patterns & Custom Modules — renderless, render functions, custom modules.