各位靓仔靓女,大家好!我是今天的主讲人,咱们今天来聊聊Vue 3源码里transition
组件的那些事儿,重点是它的hook和CSS类管理。放心,我会尽量用大白话,保证大家听得懂,听得开心。
开场白:transition
组件,不只是动画那么简单
咱们平时用Vue做页面,动画效果少不了。transition
组件就是个好帮手。它能帮我们轻松实现元素进入、离开时的过渡效果。但是,transition
组件可不止是加点CSS动画那么简单,它的背后有很多巧妙的设计。今天,咱们就一起扒一扒它的底裤,看看它到底是怎么运作的。
第一部分:transition
组件的核心逻辑
transition
组件的核心作用,就是在元素进入和离开DOM的时候,添加一些特定的CSS类名,或者触发一些JavaScript钩子函数。通过这些类名或者钩子函数,我们就可以控制元素的动画效果。
-
transition
组件的基本用法先来回顾一下
transition
组件的基本用法。<template> <div> <button @click="show = !show">Toggle</button> <transition name="fade"> <p v-if="show">Hello, world!</p> </transition> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const show = ref(false); return { show, }; }, }; </script> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style>
在这个例子中,当
show
的值改变时,p
元素会进入或离开DOM。transition
组件会根据name
属性(这里是fade
),自动添加/移除一些CSS类名,比如fade-enter-active
、fade-leave-active
等等。这些类名,就是我们控制动画效果的关键。 -
transition
组件的propstransition
组件有很多props,可以用来控制过渡效果的行为。咱们来重点看看几个:name
: 过渡的名称。影响自动生成的CSS类名。appear
: 是否在初始渲染时应用过渡。mode
: 过渡模式。可以是in-out
或out-in
。type
: 指定过渡的类型。可以是transition
或animation
。duration
: 指定过渡的持续时间。enter-from-class
,enter-active-class
,enter-to-class
: 自定义进入过渡的CSS类名。leave-from-class
,leave-active-class
,leave-to-class
: 自定义离开过渡的CSS类名。before-enter
,enter
,after-enter
,enter-cancelled
: 进入过渡的JavaScript钩子函数。before-leave
,leave
,after-leave
,leave-cancelled
: 离开过渡的JavaScript钩子函数。
这些props给了我们很大的灵活性,可以定制各种各样的过渡效果。
第二部分:transition
组件的CSS类名管理
transition
组件最常用的功能之一,就是自动管理CSS类名。它会根据元素的进入/离开状态,自动添加/移除一些类名,让我们只需要关注CSS动画的编写。
-
自动生成的CSS类名
如果
transition
组件没有指定自定义的CSS类名,它会根据name
属性自动生成一些类名。假设name
是fade
,那么生成的类名如下:类名 描述 fade-enter-from
元素进入过渡的起始状态。在元素插入DOM之前添加。 fade-enter-active
元素进入过渡的激活状态。在元素插入DOM时添加。 fade-enter-to
元素进入过渡的结束状态。在元素插入DOM之后添加。 fade-leave-from
元素离开过渡的起始状态。在元素离开DOM之前添加。 fade-leave-active
元素离开过渡的激活状态。在元素离开DOM时添加。 fade-leave-to
元素离开过渡的结束状态。在元素离开DOM之后添加。 fade-appear-from
元素初始渲染过渡的起始状态 (如果 appear
prop 被传递)。fade-appear-active
元素初始渲染过渡的激活状态 (如果 appear
prop 被传递)。fade-appear-to
元素初始渲染过渡的结束状态 (如果 appear
prop 被传递)。这些类名的添加和移除时机非常重要,直接影响了动画效果。
-
自定义CSS类名
如果自动生成的CSS类名不满足需求,我们可以使用
enter-from-class
、enter-active-class
、enter-to-class
、leave-from-class
、leave-active-class
和leave-to-class
这些props,自定义CSS类名。<template> <div> <button @click="show = !show">Toggle</button> <transition enter-from-class="custom-enter-from" enter-active-class="custom-enter-active" enter-to-class="custom-enter-to" leave-from-class="custom-leave-from" leave-active-class="custom-leave-active" leave-to-class="custom-leave-to" > <p v-if="show">Hello, world!</p> </transition> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const show = ref(false); return { show, }; }, }; </script> <style> .custom-enter-active, .custom-leave-active { transition: opacity 0.5s; } .custom-enter-from, .custom-leave-to { opacity: 0; } </style>
这样,
transition
组件就会使用我们自定义的类名,而不是自动生成的类名。 -
源码分析:CSS类名是如何管理的
虽然我们直接使用
transition
组件不需要关心底层实现,但是了解源码可以帮助我们更好地理解其工作原理。以下是一段简化的伪代码,展示了transition
组件是如何管理CSS类名的:// 假设el是需要过渡的元素 function applyTransition(el, type, name, customClass) { const enterClass = customClass?.enterFrom || `${name}-enter-from`; const enterActiveClass = customClass?.enterActive || `${name}-enter-active`; const enterToClass = customClass?.enterTo || `${name}-enter-to`; const leaveClass = customClass?.leaveFrom || `${name}-leave-from`; const leaveActiveClass = customClass?.leaveActive || `${name}-leave-active`; const leaveToClass = customClass?.leaveTo || `${name}-leave-to`; if (type === 'enter') { // 1. 添加 enter-from 类名 addClass(el, enterClass); // 2. 强制reflow,保证浏览器应用了 enter-from 的样式 forceReflow(); // 3. 移除 enter-from 类名,添加 enter-to 和 enter-active 类名 removeClass(el, enterClass); addClass(el, enterToClass); addClass(el, enterActiveClass); // 4. 监听 transitionend 事件,移除 enter-active 和 enter-to 类名 onTransitionEnd(el, () => { removeClass(el, enterActiveClass); removeClass(el, enterToClass); }); } else if (type === 'leave') { // 1. 添加 leave-from 类名 addClass(el, leaveClass); // 2. 强制reflow,保证浏览器应用了 leave-from 的样式 forceReflow(); // 3. 移除 leave-from 类名,添加 leave-to 和 leave-active 类名 removeClass(el, leaveClass); addClass(el, leaveToClass); addClass(el, leaveActiveClass); // 4. 监听 transitionend 事件,移除 leave-active 和 leave-to 类名 onTransitionEnd(el, () => { removeClass(el, leaveActiveClass); removeClass(el, leaveToClass); }); } } function addClass(el, className) { el.classList.add(className); } function removeClass(el, className) { el.classList.remove(className); } function forceReflow() { // 触发浏览器的重排,保证样式应用 return el.offsetWidth; } function onTransitionEnd(el, callback) { el.addEventListener('transitionend', callback); }
这段代码简化了
transition
组件的CSS类名管理逻辑。它首先根据name
和自定义类名生成CSS类名,然后在元素进入或离开DOM时,按照一定的顺序添加和移除这些类名。- 强制reflow:
forceReflow
函数的作用是强制浏览器进行重排(reflow),确保浏览器应用了enter-from
或leave-from
的样式。这是非常关键的一步,可以避免动画效果失效。 transitionend
事件:onTransitionEnd
函数用于监听transitionend
事件。当过渡动画结束时,会触发这个事件,然后移除enter-active
和enter-to
或leave-active
和leave-to
类名。
- 强制reflow:
第三部分:transition
组件的JavaScript钩子函数
除了CSS类名,transition
组件还提供了一系列的JavaScript钩子函数,让我们可以在过渡的不同阶段执行一些自定义的逻辑。
-
钩子函数列表
transition
组件提供的钩子函数如下:钩子函数 描述 before-enter
在元素插入DOM之前调用。 enter
在元素插入DOM时调用。可以配合 done
回调函数,手动控制过渡的完成。after-enter
在元素插入DOM之后调用。 enter-cancelled
当过渡被取消时调用。 before-leave
在元素离开DOM之前调用。 leave
在元素离开DOM时调用。可以配合 done
回调函数,手动控制过渡的完成。after-leave
在元素离开DOM之后调用。 leave-cancelled
当过渡被取消时调用。 这些钩子函数可以让我们在过渡的不同阶段执行一些自定义的逻辑,比如修改元素的样式、发送请求等等。
-
钩子函数的使用
<template> <div> <button @click="show = !show">Toggle</button> <transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter" @enter-cancelled="enterCancelled" @before-leave="beforeLeave" @leave="leave" @after-leave="afterLeave" @leave-cancelled="leaveCancelled" > <p v-if="show">Hello, world!</p> </transition> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const show = ref(false); const beforeEnter = (el) => { console.log('beforeEnter', el); }; const enter = (el, done) => { console.log('enter', el); // 手动控制过渡的完成 // 可以使用setTimeout模拟一个过渡效果 setTimeout(() => { console.log('enter done'); done(); }, 1000); }; const afterEnter = (el) => { console.log('afterEnter', el); }; const enterCancelled = (el) => { console.log('enterCancelled', el); }; const beforeLeave = (el) => { console.log('beforeLeave', el); }; const leave = (el, done) => { console.log('leave', el); // 手动控制过渡的完成 setTimeout(() => { console.log('leave done'); done(); }, 1000); }; const afterLeave = (el) => { console.log('afterLeave', el); }; const leaveCancelled = (el) => { console.log('leaveCancelled', el); }; return { show, beforeEnter, enter, afterEnter, enterCancelled, beforeLeave, leave, afterLeave, leaveCancelled, }; }, }; </script>
在这个例子中,我们定义了所有的钩子函数,并在控制台中打印了相关的信息。
enter
和leave
钩子函数接收两个参数:el
是需要过渡的元素,done
是一个回调函数。如果我们在enter
或leave
钩子函数中使用了异步操作,需要手动调用done
回调函数,告诉transition
组件过渡已经完成。 -
源码分析:钩子函数是如何调用的
以下是一段简化的伪代码,展示了
transition
组件是如何调用钩子函数的:function performTransition(el, type, hooks) { if (type === 'enter') { // 1. 调用 beforeEnter 钩子函数 hooks.beforeEnter?.(el); // 2. 调用 enter 钩子函数 const done = () => { // 4. 调用 afterEnter 钩子函数 hooks.afterEnter?.(el); }; if (hooks.enter) { // 如果定义了 enter 钩子函数,则调用它,并传入 done 回调函数 hooks.enter(el, done); } else { // 如果没有定义 enter 钩子函数,则直接调用 done 回调函数 done(); } } else if (type === 'leave') { // 1. 调用 beforeLeave 钩子函数 hooks.beforeLeave?.(el); // 2. 调用 leave 钩子函数 const done = () => { // 4. 调用 afterLeave 钩子函数 hooks.afterLeave?.(el); }; if (hooks.leave) { // 如果定义了 leave 钩子函数,则调用它,并传入 done 回调函数 hooks.leave(el, done); } else { // 如果没有定义 leave 钩子函数,则直接调用 done 回调函数 done(); } } }
这段代码简化了
transition
组件的钩子函数调用逻辑。它首先判断过渡的类型(enter
或leave
),然后按照一定的顺序调用钩子函数。如果定义了enter
或leave
钩子函数,则调用它,并传入done
回调函数。如果没有定义,则直接调用done
回调函数。
第四部分:transition
组件的mode
属性
transition
组件的mode
属性可以控制过渡的模式。它有两个可选值:in-out
和out-in
。
-
in-out
模式in-out
模式表示先进入,后离开。也就是说,新元素先进入DOM,然后旧元素再离开DOM。<template> <div> <button @click="show = !show">Toggle</button> <transition name="fade" mode="in-out"> <p :key="show" v-if="show">Hello, world!</p> <p :key="!show" v-else>Goodbye, world!</p> </transition> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const show = ref(false); return { show, }; }, }; </script> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style>
在这个例子中,当
show
的值改变时,新元素会先进入DOM,然后旧元素再离开DOM。 -
out-in
模式out-in
模式表示先离开,后进入。也就是说,旧元素先离开DOM,然后新元素再进入DOM。<template> <div> <button @click="show = !show">Toggle</button> <transition name="fade" mode="out-in"> <p :key="show" v-if="show">Hello, world!</p> <p :key="!show" v-else>Goodbye, world!</p> </transition> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const show = ref(false); return { show, }; }, }; </script> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style>
在这个例子中,当
show
的值改变时,旧元素会先离开DOM,然后新元素再进入DOM。 -
源码分析:
mode
属性是如何实现的transition
组件的mode
属性是通过控制元素的插入和移除时机来实现的。以下是一段简化的伪代码,展示了mode
属性是如何影响过渡效果的:function performTransition(el, type, mode, hooks) { if (mode === 'in-out') { // 先执行 enter 过渡 if (type === 'enter') { // 调用 enter 钩子函数 hooks.beforeEnter?.(el); // ... } else if (type === 'leave') { // 等待 enter 过渡完成后再执行 leave 过渡 // ... } } else if (mode === 'out-in') { // 先执行 leave 过渡 if (type === 'leave') { // 调用 leave 钩子函数 hooks.beforeLeave?.(el); // ... } else if (type === 'enter') { // 等待 leave 过渡完成后再执行 enter 过渡 // ... } } }
这段代码简化了
transition
组件的mode
属性实现逻辑。它根据mode
的值,决定先执行enter
过渡还是leave
过渡。
总结:transition
组件,动画的艺术
今天,我们一起深入了解了Vue 3源码中transition
组件的hook和CSS类管理。我们学习了transition
组件的基本用法、props、CSS类名的自动管理和自定义、JavaScript钩子函数的使用和调用、以及mode
属性的实现原理。
transition
组件是Vue中非常重要的一个组件,它可以帮助我们轻松实现各种各样的动画效果。掌握transition
组件的原理,可以让我们更好地理解Vue的动画机制,从而编写出更加优雅和高效的动画效果。
希望今天的分享对大家有所帮助!下次再见!