Skip to content
第 130 / 250 章前端⏱ 10 分钟阅读

第 130 章:动画与过渡

学习目标

  • 掌握 Vue 3 内置的 transition 组件
  • 学会 CSS 过渡、动画、JavaScript 钩子
  • 理解 TransitionGroup 列表动画
  • 集成第三方动画库(GSAP、Animate.css)

一、Vue 3 动画体系

二、基础过渡

2.1 transition 组件

vue
<template>
  <button @click="show = !show">Toggle</button>
  <transition name="fade">
    <p v-if="show">Hello Vue</p>
  </transition>
</template>

2.2 CSS 过渡类

css
/* 进入 */
.fade-enter-active {
  transition: opacity 0.3s;
}
.fade-enter-from {
  opacity: 0;
}
.fade-enter-to {
  opacity: 1;
}

/* 离开 */
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-leave-from {
  opacity: 1;
}
.fade-leave-to {
  opacity: 0;
}

2.3 简化写法

css
/* 简写:状态类 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

三、CSS 动画

3.1 keyframes

vue
<template>
  <transition name="bounce">
    <p v-if="show">蹦跶!</p>
  </transition>
</template>

<style>
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}
</style>

3.2 自定义过渡类名

vue
<transition
  enter-active-class="animated fadeIn"
  leave-active-class="animated fadeOut"
>
  <p v-if="show">Animate.css</p>
</transition>
html
<!-- 引入 Animate.css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css" />

四、JavaScript 钩子

4.1 钩子函数

vue
<template>
  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
  >
    <p v-if="show">JS 动画</p>
  </transition>
</template>

<script setup>
const beforeEnter = (el: Element) => {
  console.log('before enter');
};

const enter = (el: Element, done: () => void) => {
  // 用 GSAP 等库
  done();
};

const afterEnter = (el: Element) => {
  console.log('after enter');
};
</script>

4.2 velocity.js

bash
pnpm add velocity-animate
typescript
import Velocity from 'velocity-animate';

const enter = (el: Element, done: () => void) => {
  Velocity(el, { opacity: 1, translateY: 0 }, { duration: 300, complete: done });
};

const leave = (el: Element, done: () => void) => {
  Velocity(el, { opacity: 0, translateY: 20 }, { duration: 300, complete: done });
};

4.3 GSAP

bash
pnpm add gsap
typescript
import gsap from 'gsap';

const enter = (el: Element, done: () => void) => {
  gsap.fromTo(el,
    { opacity: 0, y: -20 },
    { opacity: 1, y: 0, duration: 0.5, onComplete: done }
  );
};

五、过渡模式

5.1 模式

vue
<!-- in-out:先进入后离开 -->
<transition mode="in-out">
  <p v-if="show">A</p>
</transition>

<!-- out-in:先离开后进入 -->
<transition mode="out-in">
  <p v-if="show">A</p>
</transition>

5.2 初始渲染

vue
<!-- appear:首次渲染就有动画 -->
<transition appear>
  <p>初始</p>
</transition>

六、列表过渡

6.1 TransitionGroup

vue
<template>
  <button @click="add">添加</button>
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </TransitionGroup>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from {
  opacity: 0;
  transform: translateX(30px);
}

.list-leave-to {
  opacity: 0;
  transform: translateX(-30px);
}

.list-move {
  transition: transform 0.5s ease;
}
</style>

6.2 移动动画

css
/* 其他元素平滑移动 */
.list-move {
  transition: transform 0.5s;
}

/* 离开元素脱离布局流 */
.list-leave-active {
  position: absolute;
}

七、状态过渡

7.1 数字动画

vue
<script setup lang="ts">
import { ref, watch } from 'vue';
import gsap from 'gsap';

const count = ref(0);

watch(count, (newVal) => {
  gsap.to(animValue, {
    duration: 1,
    currentValue: newVal,
    onUpdate: () => {
      // 更新显示
    }
  });
});
</script>

7.2 颜色过渡

vue
<template>
  <input type="number" v-model.number="count" />
  <p :style="{ color: color }">{{ count }}</p>
</template>

<script setup>
import { ref, computed } from 'vue';

const count = ref(0);

const color = computed(() => {
  if (count.value > 0) return 'green';
  if (count.value < 0) return 'red';
  return 'black';
});
</script>

7.3 SVG 动画

vue
<template>
  <svg width="100" height="100">
    <circle
      :cx="50"
      :cy="50"
      :r="radius"
      fill="green"
    />
  </svg>
</template>

<script setup>
import { ref, watch } from 'vue';

const radius = ref(10);
const target = ref(40);

watch(target, (val) => {
  // 动画改变 radius
});
</script>

八、Vue Router 过渡

8.1 路由切换

vue
<template>
  <router-view v-slot="{ Component }">
    <transition name="fade" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

8.2 滚动行为

typescript
const router = createRouter({
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition;
    return { top: 0, behavior: 'smooth' };
  }
});

九、第三方动画库

9.1 @vueuse/motion

bash
pnpm add @vueuse/motion
typescript
import { Motion } from '@vueuse/motion';

// 注册
app.use(Motion);
vue
<template>
  <div
    v-motion
    :initial="{ opacity: 0, y: 100 }"
    :enter="{ opacity: 1, y: 0, transition: { duration: 500 } }"
  >
    内容
  </div>
</template>

9.2 auto-animate

bash
pnpm add @formkit/auto-animate
vue
<script setup>
import { useAutoAnimate } from '@formkit/auto-animate/vue';

const [parent] = useAutoAnimate();
const items = ref([1, 2, 3]);
</script>

<template>
  <ul ref="parent">
    <li v-for="item in items" :key="item">{{ item }}</li>
  </ul>
</template>

9.3 Anime.js

bash
pnpm add animejs
typescript
import anime from 'animejs';

anime({
  targets: '.box',
  translateX: 250,
  duration: 800,
  easing: 'easeInOutQuad'
});

十、性能优化

10.1 transform / opacity

css
/* ✅ GPU 加速 */
.fade-enter-active {
  transition: opacity 0.3s, transform 0.3s;
}

/* ❌ 触发 layout */
.fade-enter-active {
  transition: width 0.3s, height 0.3s;
}

10.2 will-change

css
.animated {
  will-change: transform, opacity;
}

10.3 减少动画

vue
<!-- 跨设备检测 -->
<template>
  <transition :name="reducedMotion ? '' : 'fade'">
    <p v-if="show">内容</p>
  </transition>
</template>

<script setup>
import { ref } from 'vue';

const reducedMotion = ref(window.matchMedia('(prefers-reduced-motion: reduce)').matches);
</script>

十一、复用动画

11.1 封装 Hook

typescript
// composables/useFadeIn.ts
import { ref } from 'vue';

export function useFadeIn(duration = 300) {
  const visible = ref(false);
  const opacity = ref(0);

  function show() {
    visible.value = true;
    requestAnimationFrame(() => {
      opacity.value = 1;
    });
  }

  function hide() {
    opacity.value = 0;
    setTimeout(() => {
      visible.value = false;
    }, duration);
  }

  return { visible, opacity, show, hide };
}

11.2 动画组件

vue
<!-- FadeIn.vue -->
<script setup lang="ts">
interface Props {
  duration?: number;
  delay?: number;
}

const props = withDefaults(defineProps<Props>(), {
  duration: 300,
  delay: 0
});

const visible = ref(false);
onMounted(() => {
  setTimeout(() => {
    visible.value = true;
  }, props.delay);
});
</script>

<template>
  <transition name="fade" :duration="duration">
    <div v-if="visible" class="fade-content">
      <slot />
    </div>
  </transition>
</template>

<style scoped>
.fade-enter-active {
  transition: opacity 0.3s;
}
.fade-enter-from {
  opacity: 0;
}
</style>

十二、组件库中的动画

12.1 Modal 淡入淡出

vue
<template>
  <transition name="modal">
    <div v-if="modelValue" class="modal-mask">
      <div class="modal-wrapper">
        <slot />
      </div>
    </div>
  </transition>
</template>

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

12.2 Collapse 折叠

vue
<template>
  <button @click="open = !open">Toggle</button>
  <transition name="collapse">
    <div v-if="open" class="collapse-content">
      <slot />
    </div>
  </transition>
</template>

<style scoped>
.collapse-enter-active,
.collapse-leave-active {
  transition: max-height 0.3s ease;
  overflow: hidden;
}
.collapse-enter-from,
.collapse-leave-to {
  max-height: 0;
}
.collapse-enter-to,
.collapse-leave-from {
  max-height: 500px;
}
</style>

十三、本章小结

概念关键
transition单元素过渡
TransitionGroup列表过渡
CSS 类enter/leave + from/to/active
JS 钩子before/after + enter/leave
模式in-out / out-in
性能transform / opacity
第三方GSAP / VueUse Motion

动手练习

  1. 淡入淡出:实现一个 v-model 控制的 Modal
  2. 列表动画:实现 todo 列表的添加/删除/移动动画
  3. 数字滚动:用 GSAP 实现数字滚动效果
  4. 折叠面板:用 transition 实现高度过渡
  5. 路由切换:为路由变化添加过渡动画

推荐阅读


下一章第 131 章:Vite 进阶配置

本站基于 VitePress 构建 · 由 Codebook 团队维护