Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:
1. 在 CSS 过渡和动画中自动应用 class
2. 可以配合使用第三方 CSS 动画库,如 Animate.css
3. 在过渡钩子函数中使用 JavaScript 直接操作 DOM
4. 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
1️⃣ 单元素/组件的过渡
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
1. 条件渲染 (使用 v-if)
2. 条件展示 (使用 v-show)
3. 动态组件
4. 组件根节点
2️⃣ 过渡的类名
3️⃣ 进入
v-enter:定义进入过渡的开始状态。
1. 在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active :定义进入过渡生效时的状态。
1. 在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。
1. 在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
3️⃣ 离开
v-leave:定义离开过渡的开始状态。
1. 在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active:定义离开过渡生效时的状态。
1. 在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。
1. 在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
v-enter-active 和 v-leave-active 可以控制进入/离开过渡的不同的缓和曲线
2️⃣ 类名前缀
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v-
是这些类名的默认前缀。如果你使用了 <transition name="animation">
,那么 **v-enter**
会替换为 .animation-enter
2️⃣ CSS 过渡
2️⃣ CSS 动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
<style scoped>
.v-enter-active {
animation: enter 3s;
}
.v-leave-active {
animation: enter 3s reverse;
}
@keyframes enter {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
2️⃣ 自定义过渡的类名
我们可以通过以下 attribute 来自定义过渡类名:
1. enter-class
2. enter-active-class
3. enter-to-class (2.1.8+)
4. leave-class
5. leave-active-class
6. leave-to-class (2.1.8+)
<!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
<template>
<div class="co">
<button @click="show = !show">点击</button>
<!-- 自定义过渡的类名 -->
<transition enter-active-class="eac" leave-active-class="lac">
<h1 v-show="show">过度&动画</h1>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: false,
};
},
};
</script>
<style scoped>
.eac {
animation: enter 3s;
}
.lac {
animation: enter 3s reverse;
}
@keyframes enter {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。
1. `yarn add animate.css`
2️⃣ 同时使用过渡和动画
Vue 为了知道过渡的完成,必须设置相应的事件监听器。它可以是 transitionend 或 animationend,这取决于给元素应用的 CSS 规则。如果你使用其中任何一种,Vue 能自动识别类型并设置监听。
但是,在一些场景中,你需要给同一个元素同时设置两种过渡动效,比如 animation 很快的被触发并完成了,而 transition 效果还没结束。在这种情况中,你就需要使用 type attribute 并设置 animation 或 transition 来明确声明你需要 Vue 监听的类型。
2️⃣ 显性的过渡持续时间
在很多情况下,Vue 可以自动得出过渡效果的完成时机。默认情况下,Vue 会等待其在过渡效果的根元素的第一个 transitionend 或 animationend 事件。然而也可以不这样设定——比如,我们可以拥有一个精心编排的一系列过渡效果,其中一些嵌套的内部元素相比于过渡效果的根元素有延迟的或更长的过渡效果。
在这种情况下你可以用 <transition>
组件上的 duration prop 定制一个显性的过渡持续时间 (以毫秒计):
<transition :duration="1000">...</transition>
你也可以定制进入和移出的持续时间:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
1️⃣ 初始渲染的过渡
可以通过 appear attribute 设置节点在初始渲染的过渡
// 设置节点在初始渲染的过渡 - 单独指定 appear 的话初始过度和默认的进去过度是一样的
<transition appear>
<!-- ... -->
</transition>
这里默认和进入/离开过渡一样,同样也可以自定义 CSS 类名。
1. 通过指定不一样的类名来让初始过度有一个不一样的过度或者动画
<transition
appear
appear-class="appear-class"
appear-active-class="appear-active-class"
appear-to-class="appear-to-class" (2.1.8+)
>
<!-- ... -->
</transition>
自定义 JavaScript 钩子:
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
<!-- ... -->
</transition>
在上面的例子中,无论是 appear attribute 还是 v-on:appear 钩子都会生成初始渲染过渡。
1️⃣ JavaScript 钩子
可以在 attribute 中声明 JavaScript 钩子
1. **before-enter:**动画入场前,可以在其中设置元素开始动画之前的起始样式
2. **enter:**动画入场中,可以在其中写动画
3. **after-enter:**动画完成后
4. **enter-cancelled:**取消动画
5. **before-leave:**动画出场前,可以在其中设置元素开始动画之前的起始样式
6. **leave:**动画出场中,可以在其中写动画
7. **after-leave:**动画出场完成
8. **leave-cancelled:**取消动画
<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>
这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。
当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调, enter 和 active-enter 中也必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。 推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css=”false”,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
<!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
<template>
<div class="demo">
<button @click="show = !show">Click</button>
<transition
@before-enter="be"
@enter="e"
@after-enter="ae"
@enter-cancelled="ec"
>
<div v-if="show">JavaScipt 钩子</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
};
},
methods: {
be(el) {
console.log(el);
console.log("before-enter");
},
e(el, done) {
console.log(el);
console.log("enter");
// 在 enter 结束后在显式的通过 done() 调用 after-enter 否则两者会同时执行过渡会立即结束
setInterval(() => {
done();
}, 1000);
},
ae(el, done) {
console.log(el);
console.log("after-enter");
// 取消动画 - 设置 done.canceled = true 的话就会执行 enter-cancelled 方法
done.canceled = true
},
ec(el) {
console.log(el);
console.log("enter-cancelled");
},
},
};
</script>
设置了 appear 特性的 transition 组件,也存在自定义 JavaScript 钩子:
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
<!-- ... -->
</transition>
1️⃣ 多个元素的过渡
对于原生标签可以使用 v-if/v-else。
1. 注意:在使用多元素过渡时,如果标签相同 vue 会复用标签,而只是改变了标签内的内容,所以下面的例子不会出现过渡效果
<!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
<template>
<div class="demo">
<button @click="show = !show">Click</button>
<transition>
<div v-if="show">多元素过度 - 显示</div>
<div v-else>多元素过度 - 隐藏</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
};
},
};
</script>
<style scoped>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: 1s;
}
.v-enter-to,
.v-leave {
opacity: 1;
}
</style>
当有相同标签名的元素切换时,需要通过 key attribute 设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在
组件中的多个元素设置 key 是一个更好的实践。
设置不同的 key 后就会有过度效果
<!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
<template>
<div class="demo">
<button @click="show = !show">Click</button>
<transition>
<div v-if="show" key="xs">多元素过度 - 显示</div>
<div v-else key="yc">多元素过度 - 隐藏</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
};
},
};
</script>
<style scoped>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: 1s;
}
.v-enter-to,
.v-leave {
opacity: 1;
}
</style>
在一些场景中,也可以通过给同一个元素的 key attribute 设置不同的状态来代替 v-if 和 v-else,上面的例子可以重写为:
<template>
<div class="demo">
<button @click="show=!show">Click</button>
<transition>
<div class="box"
v-bind:key="show">
{{ show ? 'Save' : 'Edit' }}
</div>
</transition>
</div>
</template>
<script>
export default {
/* eslint-disable no-undef */
data () {
return {
show: true,
}
},
}
</script>
<style scoped>
.box {
width: 50px;
height: 50px;
background-color: orangered;
color: aliceblue;
line-height: 50px;
text-align: center;
}
.v-leave-to,
.v-enter {
opacity: 0;
transform: translateX(200px);
}
.v-leave-active,
.v-enter-active {
transition: all 1s;
}
.v-leave,
.v-enter-to {
opacity: 1;
transform: translateX(0px);
}
</style>
2️⃣ 过渡模式
一个离开过渡的时候另一个开始进入过渡。这是 <transition>
的默认行为 - 进入和离开同时发生。
同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式
1. **in-out**:新元素先进行过渡,完成之后当前元素过渡离开。
2. **out-in**:当前元素先进行过渡,完成之后新元素过渡进入。
<!-- >>>>>>>>>> ComponentOne.vue >>>>>>>>>> -->
<template>
<div class="demo">
<button @click="show = !show">Click</button>
<transition mode="out-in">
<div v-if="show" key="xs">多元素过度 - 显示</div>
<div v-else key="yc">多元素过度 - 隐藏</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
};
},
};
</script>
<style scoped>
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: 1s;
}
.v-enter-to,
.v-leave {
opacity: 1;
}
</style>
多个组件的过渡
1. 多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件:
<transition mode="out-in">
<component v-bind:is="view"></component>
</transition>
1️⃣ 列表过渡
过度如何使用 v-for?在这种场景中,使用 <transition-group>
组 件。在我们深入例子之前,先了解关于这个组件的几个特点:
1. 不同于 `<transition>`,它会以一个真实元素呈现:默认为一个 `<span>`。你也可以通过 tag attribute 更换为其他元素。
2. 过渡模式不可用,因为我们不再相互切换特有的元素。
3. 内部元素总是需要提供唯一的 key attribute 值。
4. CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
2️⃣ 列表的进入/离开过渡
<template>
<div class="demo">
<button @click="add">添加</button>
<button @click="min">移除</button>
<button @click="dis">打乱</button>
<transition-group mode="out-in" tag="ul">
<li v-for="list in lists" :key="list">
{{ list }}
</li>
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
lists: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNUm: 10,
};
},
methods: {
add() {
const num = Math.floor(Math.random() * this.lists.length);
this.lists.splice(num, 0, this.nextNUm++);
},
min() {
if (this.nextNUm > 0) {
const num = Math.floor(Math.random() * this.lists.length);
this.lists.splice(num, 1);
this.nextNUm--;
}
},
dis() {
this.lists.sort(() => Math.random() - 0.5);
},
},
};
</script>
<style scoped>
li {
list-style: none;
display: inline-block;
margin: 10px;
font-weight: 600;
}
.v-leave-to,
.v-enter {
opacity: 0;
transform: translateY(20px);
}
.v-leave-active,
.v-enter-active {
transition: all 0.5s;
}
.v-leave,
.v-enter-to {
opacity: 1;
transform: translateX(0px);
}
</style>
2️⃣ 列表的排序过渡
<transition-group>
组件提供了一个新的特性:v-move,它会在元素改变定位的过程中应用。
如何使用?通过类名即可设置:.v-move {}。
1. 当给 `<transition-group>` 设置 name 特性时,例如 name 为 fade,则 v-move 在使用时,需要替换为 fade-move。
1. 注意:当移除一个列表元素时,需要将移除的元素脱离文档流,否则,要溢出的元素在移除过渡中一直处于文档流中,会影响到后面元素的 move 过渡效果。
2. 内部的实现:Vue 使用了一个叫 FLIP 简单的动画队列,使用 transforms 将元素从之前的位置平滑过渡新的位置。
3. 需要注意的是使用 FLIP 过渡的元素不能设置为 `display: inline` 。作为替代方案,可以设置为 `display: inline-block` 或者放置于 flex 中。
<transition-group>
默认会在外围包裹一层 span 标签,可以根据自己的意愿通过 tag 来设置想要包裹的标签
1. 如:`<transition-group tag="ul">`
<template>
<div class="demo">
<button @click="add">添加</button>
<button @click="min">移除</button>
<button @click="dis">打乱</button>
<transition-group mode="out-in"
tag="ul">
<li v-for="list in lists"
:key="list">
{{list}}</li>
</transition-group>
</div>
</template>
<script>
export default {
/* eslint-disable no-undef */
data () {
return {
lists: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNUm: 10
}
},
methods: {
add () {
const num = Math.floor(Math.random() * this.lists.length)
this.lists.splice(num, 0, this.nextNUm++)
},
min () {
if (this.nextNUm > 0) {
const num = Math.floor(Math.random() * this.lists.length)
this.lists.splice(num, 1)
this.nextNUm--
}
},
dis () {
this.lists.sort(() => Math.random() - 0.5)
}
}
}
</script>
<style scoped>
li {
list-style: none;
display: inline-block;
margin: 10px;
font-weight: 600;
}
.v-leave-to,
.v-enter {
opacity: 0;
transform: translateY(20px);
}
.v-leave-active,
.v-enter-active {
transition: all 0.5s;
}
.v-leave,
.v-enter-to {
opacity: 1;
transform: translateX(0px);
}
.v-move {
transition: transform 1s;
}
</style>
2️⃣ 列表的交错过渡
如果要给列表中的元素,应用更丰富的过渡效果,可以配合 JavaScript 钩子。
1️⃣ 可复用的过渡
过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,你需要做的就是将 <transition>
或者 <transition-group>
作为根组件,然后将任何子组件放置在其中就可以了。
函数式组件更适合完成这个任务,不过需要注意的是,当使用函数式组件复用过渡时,不要设置 css 作用域。可以为 transition 加上 name 属性让组件属性可以生效又不会影响其他组件