# vue-transition过渡动画
Vue 提供 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<!--2. 自定义两组样式,来控制transition 内部的元素实现动画-->
<style>
/*v-enter 是进入之前,元素的起始状态*/
/*v-leave-to 离开之后动画的终止状态*/
.v-enter,.v-leave-to{
opacity: 0;/*透明度*/
transform: translateX(150px);
color:red;
}
/*入场(离场)动画的时间段 */
.v-enter-active,.v-leave-active{
transition: all 0.8s ease;
color:blue;
}
.my-enter,.my-leave-to{
opacity: 0;/*透明度*/
transform: translateY(70px);
}
.my-enter-active,.my-leave-active{
transition: all 0.8s ease;
}
</style>
</head>
<body>
<div id="app">
<input type="button" value="toggle" @click="flag=!flag">
<input type="button" value="toggle2" @click="flag2=!flag2">
<!--1. 使用transition元素把需要被动画控制的元素,包裹起来-->
<transition>
<h3 v-if="flag">这是一个H3</h3>
</transition>
<transition name="my"> <!--区分不同组织间动画-->
<h6 v-if="flag2">这是一个H6</h6>
</transition>
</div>
<script>
var vm = new Vue({
el : '#app',
data : {
flag : false,
flag2 : false,
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue滑动效果</title>
<style>
.d {
position: absolute;
border: 1px solid red;
width: 30px;
height: 30px;
}
@keyframes show {
0% {
opacity: 0;
left: 32px;
}
100% {
opacity: 1;
left: 0;
}
}
@keyframes hide {
0% {
opacity: 1;
left: 0;
}
100% {
opacity: 0;
left: -32px;
}
}
.show-enter-active {
animation: show 1.2s;
}
.show-leave-active {
animation: hide 1.2s;
}
.show-enter, .show-leave-to {
opacity: 0;
}
.wrap {
position: relative;
width: 32px;
height: 32px;
}
</style>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
</head>
<body>
<div id="app">
<p>{{ message }}</p>
<div class="wrap">
<transition name="show">
<div class="d" v-for="item in list" :key="item.id" v-if="count === item.id">
{{ item.text }}
</div>
</transition>
</div>
<button @click="add">add</button>
</div>
<script>
new Vue({
el: '#app',
data () {
return {
message: 'Hello Vue.js!',
count: 0,
list: [
{id: 0, text: 'aaa'},
{id: 1, text: 'bbb'},
{id: 2, text: 'ccc'}
]
}
},
methods: {
add: function () {
if (this.count < this.list.length - 1) {
this.count += 1;
} else {
this.count = 0;
}
}
}
})
</script>
</body>
</html>
# transition实现原理
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。
# 过渡的类名
- 插入元素
v-enter(v2版本)v-enter-from(v3):定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。- v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入 过渡的过程时间,延迟和曲线函数。
- v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
- 移除元素
v-leavev-leave-from: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。- v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
- v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除),在过渡/动画完成之后移除。
注意: vue3 的transition将vue2的transition的 v-enter 改为 v-enter-from , v-leave 改为 v-leave-from ,使用时css样式类名需要修改为vue的类名
提示
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了<transition name="my-transition">,那么 v- 会替换为 my-transition-。
如果transtion使用的class里既有动画又有过渡,如果想统一时间,可以在
<transition type='transition'>(或者type='animation')来决定时长根据谁执行。或者<transition :duration='1000'>(:duration="{enter:1000,leave:4000}")[duration控制过渡动画时间,还可以精细到入场出场动画时间]
过渡写法

动画写法: 当动画步骤相反时注意取反

同时使用过渡和动画
- Vue为了知道过渡的完成,内部是在监听 transitionend 或 animationend;如果只是使用了其中的一个,那么Vue能自动识别类型并设置监听;
- 多个的话,需要设置 type 属性为 animation 或者 transition 来明确的告知Vue监听的类型
- 也可以显示的来指定过渡的时间,通过 duration 属性
- number类型:同时设置进入和离开的过渡时间;
- object类型:分别设置进入和离开的过渡时间
<template>
<div class="app">
<div><button @click="isShow = !isShow">显示/隐藏</button></div>
<transition name="why" type="transition" :duration="{enter: 800, leave: 1000}">
<h2 class="title" v-if="isShow">Hello World</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true
}
}
}
</script>
<style scoped>
.app {
width: 200px;
margin: 0 auto;
}
.title {
display: inline-block;
}
.why-enter-from,
.why-leave-to {
opacity: 0;
}
.why-enter-active,
.why-leave-active {
transition: opacity 1s ease;
}
.why-enter-active {
animation: bounce 1s ease;
}
.why-leave-active {
animation: bounce 1s ease reverse;
}
@keyframes bounce {
0% {
transform: scale(0)
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
</style>
# vue-transition CSS动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
# 自定义过渡类名
enter-classenter-from-class- enter-active-class
- enter-to-class (2.1.8+)
leave-classleave-from-class- leave-active-class
- leave-to-class (2.1.8+) 他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。
# vue-transition 借助Animate.css
cnpm i animate.css -S
import "animate.css";
- 用法一:直接使用animate库中定义的 keyframes 动画;
.why-enter-active {
animation: bounceInUp 1s ease-in;
}
.why-leave-active {
animation: bounceInUp 1s ease-in reverse;
}
- 用法二:直接使用animate库提供给我们的类;
- animate__animated设置动画的时间等,是必须的类,不能缺失
<template>
<div class="app">
<div><button @click="isShow = !isShow">显示/隐藏</button></div>
<transition enter-active-class="animate__animated animate__fadeInDown"
leave-active-class="animate__animated animate__flipInY">
<h2 class="title" v-if="isShow">Hello World</h2>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true
}
}
}
</script>
<style scoped>
.title {
display: inline-block;
transform: translateX(100px)
}
<!-- 调整动画的方向 -->
.animate__flipInY {
animation-direction: reverse;
}
</style>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<!--入场 bounceIn 离场 ounceOut-->
</head>
<body>
<div id="app">
<input type="button" value="toggle" @click="flag=!flag">
<transition enter-active-class="animate__bounceIn"
leave-active-class="animate__bounceOut"
:duration="{ enter: 200, leave: 2000 }">
<h3 v-if="flag" class="animate__animated">react~~~</h3>
</transition>
</div>
<script>
var vm = new Vue({
el : '#app',
data : {
flag : false,
}
});
</script>
</body>
</html>
注意类的写法:引入的animate版本不一致,写的类名也不同
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/> -->
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<!--入场 bounceIn 离场 ounceOut-->
</head>
<body>
<div id="example-3">
<button @click="show = !show">
Toggle render
</button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
<script>
new Vue({
el: '#example-3',
data: {
show: true
}
})
</script>
</body>
</html>
# vue-transition和js钩子
- 当使用js钩子时,已经写了transition部分css动画,可以直接添加:css='false'(不去执行添加减少css逻辑,优化性能)避免干扰,如果不加,则css和js动画都会执行 。
- 当使用JavaScript来执行过渡动画时,需要进行done 回调,否则它们将会被同步调用,过渡会立即完成。
<transition
v-on:before-enter="beforeEnter" // 动画开始前,设置初始状态
v-on:enter="enter" // 执行动画
v-on:after-enter="afterEnter" // 动画结束,清理工作
v-on:enter-cancelled="enterCancelled" // 取消动画
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
></transition>
<script>
methods: {
beforeEnter(el) {
el.style.opacity = 0 // 设置初始状态
},
enter(el, done) {
document.body.offsetHeight; // 触发回流激活动画,要不然不会立刻动画
el.style.opacity = 1 // 设置结束状态
el.addEventListener('transitionend',done)//触发下一步操作,after-enter
}
}
</script>
<transition
:css="false"
@before-enter="handleBeforeEnter"
>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
<script>
// ...
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
},
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {//动画被打断
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {//动画被打断
// ...
}
}
</script>
# vue-transition借助第三方库VelocityJS
<script
src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js">
</script>
<script>
Vue.component('message', {
template: `
<transition name="fade"
:css="false" // 禁用css
@before-enter="beforeEnter"
@enter="enter"
@before-leave="beforeLeave"
@leave="leave">
</transition>
`,
methods: {
beforeEnter(el) {
el.style.opacity = 0
},
enter(el, done) {
Velocity(el, { opacity: 1 }, { duration: 500, complete: done })
},
beforeLeave(el) {
el.style.opacity = 1
},
leave(el, done) {
Velocity(el, { opacity: 0 }, { duration: 500, complete: done })
}
},
})
</script>
# gsap库进行vue-transition动画
cnpm install gsap -S
<template>
<div class="app">
<div><button @click="isShow = !isShow">显示/隐藏</button></div>
<transition @enter="enter"
@leave="leave"
:css="false">
<h2 class="title" v-if="isShow">Hello World</h2>
</transition>
</div>
</template>
<script>
import gsap from 'gsap';
export default {
data() {
return {
isShow: true,
}
},
methods: {
enter(el, done) {
console.log("enter");
gsap.from(el, {
scale: 0,
x: 200,
onComplete: done
})
},
leave(el, done) {
console.log("leave");
gsap.to(el, {
scale: 0,
x: 200,
onComplete: done
})
}
}
}
</script>
<style scoped>
.title {
display: inline-block;
}
</style>
- 数字变化
<template>
<div class="app">
<input type="number" step="100" v-model="counter">
<!-- <h2>当前计数: {{showCounter}}</h2> -->
<h2>当前计数: {{showNumber.toFixed(0)}}</h2>
</div>
</template>
<script>
import gsap from 'gsap';
export default {
data() {
return {
counter: 0,
showNumber: 0
}
},
// computed: {
// showCounter() {
// return this.showNumber.toFixed(0);
// }
// },
watch: {
counter(newValue) {
gsap.to(this, {duration: 1, showNumber: newValue})
}
}
}
</script>
# transition mode 过渡模式
transition中有一个mode:'in-out','out-in'。
因为transition中最多可以存在两个元素,一个即将进入(v-enter),一个即将离开(v-leave)。
- 如果没有设置mode,那么这两者是同时开始执行过渡效果;
- 如果设置mode:'in-out',那么表示先等进入的元素完成过度后,才开始执行离开效果;
- 如果设置mode:'out-in',那么表示先等离开的元素完成过度后,才开始执行进入效果;
- 默认情况下,首次渲染的时候是没有动画的, 设置appear 初次加载也有动画
<transition name="why" mode="out-in" appear>
<h2 class="title" v-if="isShow">Hello World</h2>
<h2 class="title" v-else>你好啊,李银河</h2>
</transition>
应用场景:文本切换,动态组件切换也是适用的。
# vue之transition-group
- 当时列表而不是简单的元素后,则需要使用transition-group
- 需要改变列表中其他元素的变化有一个 v-move 的class属性可以进行添加内容,会在元素的改变定位的过程中应用.
- 列表的进入/离开过渡,对列表直接操作(增、删)的元素,封装
<transition-group>并按常规的CSS或JS过渡即可;但在操作这些元素的位置变化时,由于DOM文档流的变化,会同时引起其它(邻近)节点元素的位置变化,例如在列表插入一个<li>,插入点原本的<li>会下移,删除一个<li>,下面的<li>会上移补充占据这个位置。 对于这些“被动”移动的元素来说,也可以实现过渡,这就用到了v-move 特性。 - 像之前的类名一样,可以通过 name 属性来自定义前缀:v-move {/transition/}。
- 也可以通过 move-class 属性手动设置:
<span move-class='***'> - transition-group会将v-for的多个元素包裹一层,tag就是表示用何种元素进行包裹,默认用span进行包裹
- 默认情况下,它不会渲染一个元素的包裹器,但是你可以指定一个元素并以 tag attribute 进行渲染;
- 过渡模式mode不可用,因为不再相互切换特有的元素;
- 内部元素总是需要提供唯一的 key attribute 值;
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<title>lesson 26</title>
<style>
.v-enter-from {
opacity: 0;
transform: translateY(30px);
}
.v-enter-active {
transition: all .5s ease-in;
}
.v-enter-to {
opacity: 1;
transform: translateY(0);
}
.v-move {
transition: all .5s ease-in;
}
.list-item {
display: inline-block;
margin-right: 10px;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 列表动画的实现
const app = Vue.createApp({
data() {
return { list: [1, 2, 3] }
},
methods: {
handleClick() {
this.list.unshift(this.list.length + 1);
},
},
template: `
<div>
<transition-group>
<span class="list-item" v-for="item in list" :key="item">{{item}}</span>
</transition-group>
<button @click="handleClick">增加</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<link rel="stylesheet" href="css/animate.css">
<style>
li{
border: 1px dashed #999;
margin: 5px;
line-height: 35px;
font-size: 14px;
padding-left: 5px;
width: 100%;
}
li:hover{
background-color: hotpink;
transition: all 0.5s ease;
}
.v-enter,.v-leave-to{
opacity: 0;
transform: translateY(80px);
}
.v-enter-active,.v-leave-active{
transition: all 0.6s ease;
}
/*v-move 和 v-leave-active 配合使用,能够实现列表后续的元素,渐渐地漂上来的效果 */
.v-move{
transition: all 0.6s ease;
}
.v-leave-active{
position: absolute;
}
</style>
</head>
<body>
<div id="app">
<div>
<label>
ID:
<input type="text" v-model="id">
</label>
<label>
Name:
<input type="text" v-model="name">
</label>
<input type="button" value="添加" @click="add">
</div>
<ul>
<!--在实现列表过渡时,如果需要过渡的元素是通过v-for渲染出来的,不能使用
transition 包裹,需要使用 transition-group -->
<!--若需要为 v-for 循环创建的元素设置动画,必须为每一个元素设置 :key 属性-->
<transition-group>
<li v-for="(item,i) in list" :key="item.id" @click="del(i)">
{{ item.id }} --- {{ item.name }}
</li>
</transition-group>
</ul>
</div>
<script>
var vm = new Vue({
el : '#app',
data : {
id:'',
name :'',
list : [
{id:1,name:'项羽'},
{id:2,name:'赵云'},
{id:3,name:'黄忠'},
{id:4,name:'岳飞'},
],
},
methods : {
add(){
this.list.push({ id :this.id,name : this.name});
this.id = this.name = '';
},
del(i){
this.list.splice(i,1);
},
}
});
</script>
</body>
</html>
- ① span作为行内元素,无法设置高度,无法实现translate效果,需要调整
- ② 被删除之前的元素,一直占着文本流,后面的数字的动画就生硬的移动过来,没有过渡了,需要不让他占据文本流
- ③ v-move: 虽然新增的或者删除的节点是有动画的,但是对于哪些其他需要移动的节点是没有动画的,使用v-move可以设置其他元素的动画,不需要具体指明位移多少这些信息
<template>
<div>
<button @click="addNum">添加数字</button>
<button @click="removeNum">删除数字</button>
<button @click="shuffleNum">数字洗牌</button>
<transition-group tag="p" name="why">
<span v-for="item in numbers" :key="item" class="item">
{{item}}
</span>
</transition-group>
</div>
</template>
<script>
import _ from 'lodash';
export default {
data() {
return {
numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
numCounter: 10
}
},
methods: {
addNum() {
// this.numbers.push(this.numCounter++)
this.numbers.splice(this.randomIndex(), 0, this.numCounter++)
},
removeNum() {
this.numbers.splice(this.randomIndex(), 1)
},
shuffleNum() {
this.numbers = _.shuffle(this.numbers);
},
randomIndex() {
return Math.floor(Math.random() * this.numbers.length)
}
},
}
</script>
<style scoped>
.item {
margin-right: 10px;
display: inline-block;
/*①*/
}
.why-enter-from,
.why-leave-to {
opacity: 0;
transform: translateY(30px);
}
.why-enter-active,
.why-leave-active {
transition: all 1s ease;
}
.why-leave-active {
position: absolute;
/*②*/
}
.why-move {
transition: transform 1s ease;
/*③*/
}
</style>
- 搜索内容不被选中挨个消失的写法
<template>
<div>
<input v-model="keyword">
<transition-group tag="ul" name="why" :css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave">
<li v-for="(item, index) in showNames" :key="item" :data-index="index">
{{item}}
</li>
</transition-group>
</div>
</template>
<script>
import gsap from 'gsap';
export default {
data() {
return {
names: ["abc", "cba", "nba", "why", "lilei", "hmm", "kobe", "james"],
keyword: ""
}
},
computed: {
showNames() {
return this.names.filter(item => item.indexOf(this.keyword) !== -1)
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.height = 0;
},
enter(el, done) {
gsap.to(el, {
opacity: 1,
height: "1.5em",
delay: el.dataset.index * 0.5,
onComplete: done
})
},
leave(el, done) {
gsap.to(el, {
opacity: 0,
height: 0,
delay: el.dataset.index * 0.5,
onComplete: done
})
}
}
}
</script>
<style scoped>
/* .why-enter-from,
.why-leave-to {
opacity: 0;
}
.why-enter-active,
.why-leave-active {
transition: opacity 1s ease;
} */
</style>
# vue-transition appear
关于appear的用法和enter的用法相似,它只是在第一次渲染的时候才会起作用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>初始渲染的过渡</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<style>
.custom-appear-active{
color: #2fe26d;
background: #b6b6b6;
transition: all 1s ease;
}
.custom-appear{
font-size: 40px;
color: #e069e2;
background: #7798e2;
}
.custom-appear-to{
color: #e29138;
background: #1c8942;
}
</style>
<body>
<div id="app">
<transition
appear
appear-class="custom-appear"
appear-to-class="custom-appear-to"
appear-active-class="custom-appear-active"
>
<p>2021</p>
</transition>
</div>
<script>
new Vue({
el: "#app"
})
</script>
</body>
</html>
参考1 (opens new window)| 参考2 (opens new window)| 参考3 (opens new window)| 参考4 (opens new window)