Transition
- Transition 是一个内置组件,它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上;进入或离开可以由以下的条件之一触发:
- 由 v-if 所触发的切换
- 由 v-show 所触发的切换
- 由特殊元素 component 切换的动态组件
- 改变特殊的 key 属性
- Transition 仅支持单个元素或组件作为其插槽内容,如果内容是一个组件,这个组件必须仅有一个根元素;
- 当一个 Transition 组件中的元素被插入或移除时,会发生下面这些事情:
- Vue 会自动检测目标元素是否应用了 CSS 过渡或动画;如果是,则一些 CSS 过渡 class 会在适当的时机被添加和移除;
- 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用;
- 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行;
基于 CSS 的过渡效果
CSS 过渡 class
-
v-enter-from:进入动画的起始状态;在元素插入之前添加,在元素插入完成后的下一帧移除;
-
v-enter-active:进入动画的生效状态;应用于整个进入动画阶段;在元素被插入之前添加,在过渡或动画完成之后移除;这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型;
-
v-enter-to:进入动画的结束状态;在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时) ,在过渡或动画完成之后移除;
-
v-leave-from:离开动画的起始状态;在离开过渡效果被触发时立即添加,在一帧后被移除;
-
v-leave-active:离开动画的生效状态;应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除;这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型;
-
v-leave-to:离开动画的结束状态;在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时) ,在过渡或动画完成之后移除;
为过渡效果命名
-
可以给 Transition 组件传一个 name prop 来声明一个过渡效果名;
<Transition name="fade"> ... </Transition>
-
对于一个有名字的过渡效果,对它起作用的过渡 class 会以其名字而不是 v 作为前缀;
.fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; }
CSS 的 transition
-
Transition 一般都会搭配原生 CSS 过渡一起使用;
-
控制 enter-active、leave-active 实现不同的持续时间和速度曲线来过渡多个属性;
HTMLCSS<Transition name="slide-fade"> <p v-if="show">hello</p> </Transition>
/* 进入和离开动画可以使用不同 持续时间和速度曲线。 */ .slide-fade-enter-active { transition: all 0.3s ease-out; } .slide-fade-leave-active { transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1); } .slide-fade-enter-from, .slide-fade-leave-to { transform: translateX(20px); opacity: 0; }
CSS 的 animation
-
原生 CSS 动画和 CSS transition 的应用方式基本上是相同的,只有一点不同,那就是 *-enter-from 不是在元素插入后立即移除,而是在一个 animationend 事件触发时被移除;
-
对于大多数的 CSS 动画,可以简单地在 *-enter-active 和 *-leave-active class 下声明它们;
HTMLCSS<Transition name="bounce"> <p v-if="show" style="text-align: center;"> Hello here is some bouncy text! </p> </Transition>
.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); } }
自定义过渡 class
-
可以向 Transition 传递以下的 props 来指定自定义的过渡 class:
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
-
传入的这些 class 会覆盖相应阶段的默认 class 名;这个功能在想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如
Animate.css
:<!-- 假设你已经在页面中引入了 Animate.css --> <Transition name="custom-classes" enter-active-class="animate__animated animate__tada" leave-active-class="animate__animated animate__bounceOutRight" > <p v-if="show">hello</p> </Transition>
同时使用 transition 和 animation
-
Vue 需要附加事件监听器,以便知道过渡何时结束;可以是 transitionend 或 animationend,这取决于所应用的 CSS 规则;如果仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型;
-
然而在某些场景中,或许想要在同一个元素上同时使用它们两个;举例来说,Vue 触发了一个 CSS 动画,同时鼠标悬停触发另一个 CSS 过渡;此时需要显式地传入 type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation 或 transition:
<Transition type="animation">...</Transition>
深层级过渡与显式过渡时长
-
尽管过渡 class 仅能应用在 Transition 的直接子元素上,还是可以使用深层级的 CSS 选择器,在深层级的元素上触发过渡效果:
HTMLCSS<Transition name="nested"> <div v-if="show" class="outer"> <div class="inner"> Hello </div> </div> </Transition>
/* 应用于嵌套元素的规则 */ .nested-enter-active .inner, .nested-leave-active .inner { transition: all 0.3s ease-in-out; } .nested-enter-from .inner, .nested-leave-to .inner { transform: translateX(30px); opacity: 0; } /* ... 省略了其他必要的 CSS */
-
甚至可以在深层元素上添加一个过渡延迟,从而创建一个带渐进延迟的动画序列:
/* 延迟嵌套元素的进入以获得交错效果 */ .nested-enter-active .inner { transition-delay: 0.25s; }
-
然而,这会带来一个小问题;默认情况下,Transition 组件会通过监听过渡根元素上的第一个 transitionend 或者 animationend 事件来尝试自动判断过渡何时结束;而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成;
-
在这种情况下,可以通过向 Transition 组件传入 duration prop 来显式指定过渡的持续时间 (以毫秒为单位);总持续时间应该匹配延迟加上内部元素的过渡持续时间:
<!-- 控制进入离开的时间都是 550ms --> <Transition :duration="550">...</Transition> <!-- 也可以用对象的形式传入,分开指定进入和离开所需的时间 --> <Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
JavaScript 钩子
-
可以通过监听 Transition 组件事件的方式在过渡过程中挂上钩子函数,这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用;
HTMLJavaScript<Transition @before-enter="onBeforeEnter" @enter="onEnter" @after-enter="onAfterEnter" @enter-cancelled="onEnterCancelled" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" @leave-cancelled="onLeaveCancelled" > <!-- ... --> </Transition>
// 在元素被插入到 DOM 之前被调用 // 用这个来设置元素的 "enter-from" 状态 function onBeforeEnter(el) {} // 在元素被插入到 DOM 之后的下一帧被调用 // 用这个来开始进入动画 function onEnter(el, done) { // 调用回调函数 done 表示过渡结束 // 如果与 CSS 结合使用,则这个回调是可选参数 done() } // 当进入过渡完成时调用。 function onAfterEnter(el) {} // 当进入过渡在完成之前被取消时调用 function onEnterCancelled(el) {} // 在 leave 钩子之前调用 // 大多数时候,你应该只会用到 leave 钩子 function onBeforeLeave(el) {} // 在离开过渡开始时调用 // 用这个来开始离开动画 function onLeave(el, done) { // 调用回调函数 done 表示过渡结束 // 如果与 CSS 结合使用,则这个回调是可选参数 done() } // 在离开过渡完成、 // 且元素已从 DOM 中移除时调用 function onAfterLeave(el) {} // 仅在 v-show 过渡中可用 function onLeaveCancelled(el) {}
-
在使用仅由 JavaScript 执行的动画时,最好是添加一个
:css="false"
prop;显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测;除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果:<Transition ... :css="false" > ... </Transition>
-
在有了
:css="false"
后,就自己全权负责控制什么时候过渡结束了;这种情况下对于 @enter 和 @leave 钩子来说,回调函数 done 就是必须的;否则,钩子将被同步调用,过渡将立即完成;
可复用过渡效果
-
要创建一个可被复用的过渡,需要为 Transition 组件创建一个包装组件,并向内传入插槽内容:
<template> <!-- 包装内置的 Transition 组件 --> <Transition name="my-transition" @enter="onEnter" @leave="onLeave"> <slot></slot> <!-- 向内传递插槽内容 --> </Transition> </template> <!-- MyTransition.vue --> <script> // JavaScript 钩子逻辑... </script> <style> /* 必要的 CSS... 注意:避免在这里使用 <style scoped> 因为那不会应用到插槽内容上 */ </style>
-
现在 MyTransition 可以在导入后像内置组件那样使用了:
<MyTransition> <div v-if="show">Hello</div> </MyTransition>
出现时过渡
-
如果想在某个节点初次渲染时应用一个过渡效果,可以添加 appear prop:
<Transition appear> ... </Transition>
元素间过渡
-
除了通过
v-if / v-show
切换一个元素,也可以通过v-if / v-else / v-else-if
在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可:<Transition> <button v-if="docState === 'saved'">Edit</button> <button v-else-if="docState === 'edited'">Save</button> <button v-else-if="docState === 'editing'">Cancel</button> </Transition>
过渡模式
-
在之前的例子中,进入和离开的元素都是在同时开始动画的,因此不得不将它们设为
position: absolute
以避免二者同时存在时出现的布局问题; -
然而,很多情况下这可能并不符合需求;可能想要先执行离开动画,然后在其完成之后再执行元素的进入动画;手动编排这样的动画是非常复杂的,好在可以通过向 Transition 传入一个 mode prop 来实现这个行为:
<Transition mode="out-in"> ... </Transition>
组件间过渡
-
Transition 也可以作用于动态组件之间的切换:
<Transition name="fade" mode="out-in"> <component :is="activeComponent"></component> </Transition>
动态过渡
-
Transition 的 props (比如 name) 也可以是动态的!这让我们可以根据状态变化动态地应用不同类型的过渡:
<Transition :name="transitionName"> <!-- ... --> </Transition>
-
这个特性的用处是可以提前定义好多组 CSS 过渡或动画的 class,然后在它们之间动态切换;
使用 Key Attribute 过渡
-
有时为了触发过渡,你需要强制重新渲染 DOM 元素;
<script setup> import { ref } from 'vue'; const count = ref(0); setInterval(() => count.value++, 1000); </script> <template> <Transition> <span :key="count">{{ count }}</span> </Transition> </template>
-
如果不使用 key attribute,则只有文本节点会被更新,因此不会发生过渡;但是,有了 key 属性,Vue 就知道在 count 改变时创建一个新的 span 元素,因此 Transition 组件有两个不同的元素在它们之间进行过渡;
TransitionGroup
TransitionGroup 是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果;
和 Transition 的区别
TransitionGroup 支持和 Transition 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
- 默认情况下,它不会渲染一个容器元素,但可以通过传入 tag prop 来指定一个元素作为容器元素来渲染;
- 过渡模式在这里不可用,因为不再是在互斥的元素之间进行切换;
- 列表中的每个元素都必须有一个独一无二的 key attribute;
- CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上;
进入 / 离开动画
-
这里是 TransitionGroup 对一个 v-for 列表添加进入 / 离开动画的示例:
HTMLCSS<TransitionGroup name="list" tag="ul"> <li v-for="item in items" :key="item"> {{ item }} </li> </TransitionGroup>
.list-enter-active, .list-leave-active { transition: all 0.5s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); }
移动动画
-
上面的示例有一些明显的缺陷:当某一项被插入或移除时,它周围的元素会立即发生“跳跃”而不是平稳地移动;
-
可以通过添加一些额外的 CSS 规则来解决这个问题:
.list-move, /* 对移动中的元素应用的过渡 */ .list-enter-active, .list-leave-active { transition: all 0.5s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); } /* 确保将离开的元素从布局流中删除 以便能够正确地计算移动的动画。 */ .list-leave-active { position: absolute; }
-
自定义过渡组 class:还可以通过向 TransitionGroup 传递 moveClass prop 为移动元素指定自定义过渡 class,类似于自定义过渡 class;
渐进延迟列表动画
-
通过在 JavaScript 钩子中读取元素的 data attribute,可以实现带渐进延迟的列表动画;首先,把每一个元素的索引渲染为该元素上的一个 data attribute:
<TransitionGroup tag="ul" :css="false" @before-enter="onBeforeEnter" @enter="onEnter" @leave="onLeave" > <li v-for="(item, index) in computedList" :key="item.msg" :data-index="index" > {{ item.msg }} </li> </TransitionGroup>
-
接着,在 JavaScript 钩子中,基于当前元素的 data attribute 对该元素的进场动画添加一个延迟;以下是一个基于 GSAP library 的动画示例:
function onEnter(el, done) { gsap.to(el, { opacity: 1, height: '1.6em', delay: el.dataset.index * 0.15, onComplete: done }) }
vue3🛫 Suspense 组件
上一篇