目录
- 前言
- Transition 内置组件
- 触发条件
- 再分类
- 六个过渡时机
- Transition 组件 & CSS transition 属性
- 核心原理
- 实现 Transition 组件
- 原生 DOM 如何实现过渡?
- 原生 DOM 元素移动示例
- 进场动效
- 离场动效
- 实现 Transition 组件
- 最后
前言
<Transition>作为一个Vue中的内置组件,它可以将进入动画和离开动画应用到通过默认插槽传递给目标元素或组件上。
也许你有在使用,但是一直不清楚它的原理或具体实现,甚至不清楚其内部提供的各个class到底怎么配合使用,想看源码又被其中各种引入搞得七荤八素…
本篇文章就以Transition组件为核心,探讨其核心原理的实现,文中不会对其各个属性再做额外解释,毕竟这些看文档就够了,希望能够给你带来帮助!!!
Transition 内置组件
触发条件
<Transition>组件的进入动画或离开动画可通过以下的条件之一触发:
- 由v-if所触发的切换
- 由v-show所触发的切换
- 由特殊元素<component name="x">切换的动态组件
- 改变特殊的key属性
再分类
其实我们可以将以上情况进行再分类:
-
组件挂载和销毁
- v-if的变化
- <component name="x">的变化
- key的变化
-
组件样式属性display: none | x设置
- v-show的变化
【扩展】v-if和v-for一起使用时,在Vue2和Vue3中的不同
- 在Vue2中,当它们处于同一节点时,v-for的优先级比v-if更高,即v-if将分别重复运行于每个v-for循环中,也就是v-if可以正常访问v-for中的数据
- 在Vue3中,当它们处于同一节点时,v-if的优先级比v-for更高,即此时只要v-if的值为false则v-for的列表就不会被渲染,也就是v-if不能访问到v-for中的数据
六个过渡时机
总结起来就分为进入和离开动画的初始状态、生效状态、结束状态,具体如下:
-
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被移除的同时),在过渡或动画完成之后移除
其中的v前缀是允许修改的,可以<Transition>组件传一个name的prop来声明一个过渡效果名,如下就是将v前缀修改为 **`modal `** 前缀:
<Transition name=\”modal\”> … </Transition>
Transition 组件 & CSS transition 属性
以上这个简单的效果,核心就是两个时机:
- v-enter-active进入动画的生效状态
- v-leave-active离开动画的生效状态
再配合简单的CSS过渡属性就可以达到效果,代码如下:
<template>
<div class=\”home\”>
<transition name=\”golden\”>
<!– 金子列表 –>
<div class=\”golden-box\” v-show=\”show\”>
<img
class=\”golden\”
:key=\”idx\”
v-for=\”idx in 3\”
src=\”../assets/golden.jpg\”
/>
</div>
</transition>
</div>
<!– 钱袋子 –>
<img class=\”purse\” @click=\”show = !show\” src=\”../assets/purse.png\” alt=\”\” />
</template>
<script setup lang=\”ts\”>
import { ref, computed } from \’vue\’
const show = ref(true)
</script>
<style lang=\”less\” scoped>
.home {
min-height: 66px;
}
.golden-box {
transition: all 1s ease-in;
.golden {
width: 100px;
position: fixed;
transform: translate3d(0, 0, 0);
transition: all .4s;
&:nth-of-type(1) {
left: 45%;
top: 100px;
}
&:nth-of-type(2) {
left: 54%;
top: 50px;
}
&:nth-of-type(3) {
right: 30%;
top: 100px;
}
}
&.golden-enter-active {
.golden {
transform: translate3d(0, 0, 0);
transition-timing-function: cubic-bezier(0, 0.57, 0.44, 1.97);
}
.golden:nth-of-type(1) {
transition-delay: 0.1s;
}
.golden:nth-of-type(2) {
transition-delay: 0.2s;
}
.golden:nth-of-type(3) {
transition-delay: 0.3s;
}
}
&.golden-leave-active {
.golden:nth-of-type(1) {
transform: translate3d(150px, 140px, 0);
transition-delay: 0.3s;
}
.golden:nth-of-type(2) {
transform: translate3d(0, 140px, 0);
transition-delay: 0.2s;
}
.golden:nth-of-type(3) {
transform: translate3d(-100px, 140px, 0);
transition-delay: 0.1s;
}
}
}
.purse {
position: fixed;
width: 200px;
margin-top: 100px;
cursor: pointer;
}
</style>
当然动画的效果是多种多样的,不仅只是局限于这一种,例如可以配合:
- CSS的transition过渡属性(上述例子使用的方案)
- CSS的animation动画属性
gsap 库
核心原理
通过上述内容其实不难发现其核心原理就是:
- 当组件(DOM)被挂载时,将过渡动效添加到该DOM元素上
- 当组件(DOM)被卸载时,不是直接卸载,而是等待附加到DOM元素上的动效执行完成,然后在真正执行卸载操作,即延迟卸载时机
在上述的过程中,<Transition>组件会为目标组件/元素通过添加不同的class来定义初始、生效、结束三个状态,当进入下一个状态时会把上一个状态对应的class移除。
那么你可能会问了,v-show的形式也不符合挂载/卸载的形式呀,毕竟它只是在修改DOM元素的display: none | x的样式!
让源码中的注释来回答:
v-if、<component name="x">、key控制组件显示/隐藏的方式是挂载/卸载组件,而v-show控制组件显示/隐藏的方式是修改/重置display: none | x属性值,从本质上看方式不同,但从结果上看都属于控制组件的显示/隐藏,即功能是一致的,而这里所说的挂载/卸载是针对大部分情况来说的,毕竟四种触发方式中就有三种符合此情况。
实现 Transition 组件
所谓Transition组件毕竟是 Vue 的内置组件,换句话说,组件的编写要符合 Vue 的规范(即声明式写法),但为了更好的理解核心原理,我们应该从原生 DOM的过渡开始(即命令式写法)探讨。
原生 DOM 如何实现过渡?
所谓的过渡动效本质上就是一个 DOM 元素在两种状态间的转换,浏览器会根据我们设置的过渡效果自行完成 DOM 元素的过渡。
而状态的转换指的就是初始化状态和结束状态的转换,并且配合 CSS 中的transition属性就可以实现两个状态间的过渡,即运动过程。
原生 DOM 元素移动示例
假设要为一个元素在垂直方向上添加进场动效:从原始位置向上移动200px的位置,然后在1s内运动回原始位置。
进场动效
用 CSS 描述
// 描述物体
.box {
width: 100px;
height: 100px;
background-color: red;
box-shadow: 0 0 8px;
border-radius: 50%;
}
// 初始状态
.enter-from {
transform: translateY(-200px);
}
// 运动过程
.enter-active {
transition: transform 1s ease-in-out;
}
// 结束状态
.enter-to {
transform: translateY(0);
}
用 JavaScript 描述
// 创建元素
const div = document.createElement(\’div\’)
div.classList.add(\’box\’)
// 添加 初始状态 和 运动过程
div.classList.add(\’enter-from\’)
div.classList.add(\’enter-active\’)
// 将元素添加到页面上
document.body.appendChild(div)
// 切换元素状态
div.classList.remove(\’enter-from\’)
div.classList.add(\’enter-to\’)
从命令式编程的步骤上来看,似乎每一步都没有问题,但实际的过渡动画是不会生效的,虽然在代码中我们有状态的切换,但这个切换的操作对于浏览器来讲是在同一帧中进行的,所以只会渲染最终状态,即enter-to类所指向的状态。
requestAnimationFrame 实现下一帧的变化
window.requestAnimationFrame(callback)会在浏览器在下次重绘之前调用指定的回调函数用于更新动画。
也就是说,单个的requestAnimationFrame()方法是在当前帧中执行的,也就是如果想要在下一帧中执行就需要使用两个requestAnimationFrame()方法嵌套的方式来实现,如下:
// 嵌套的 requestAnimationFrame 实现在下一帧中,切换元素状态
requestAnimationFrame(() => {
requestAnimationFrame(() => {
div.classList.remove(\”enter-from\”);
div.classList.add(\”enter-to\”);
});
});
transitionend 事件监听动效结束
以上就完成元素的进入动效,那么在动效结束之后,别忘了将原本和进入动效相关的类移除掉,可以通过transitionend 事件监听动效是否结束,如下
// 嵌套的 requestAnimationFrame 实现在下一帧中,切换元素状态
requestAnimationFrame(() => {
requestAnimationFrame(() => {
div.classList.remove(\”enter-from\”);
div.classList.add(\”enter-to\”);
// 动效结束后,移除和动效相关的类
div.addEventListener(\”transitionend\”, () => {
div.classList.remove(\”enter-to\”);
div.classList.remove(\”enter-active\”);
});
});
});
以上就是进场动效的实现,如下:
离场动效
有了进场动效的实现过程,在定义离场动效时就可以选择和进场动效相对应的形式,即初始状态、过渡过程、结束状态。
用 CSS 描述
// 初始状态
.leave-from {
transform: translateY(0);
}
// 过渡状态
.leave-active {
transition: transform 2s ease-out;
}
// 结束状态
.leave-to {
transform: translateY(-300px);
}
用 JavaScript 描述
所谓的离场就是指DOM 元素的卸载,但因为要有离场动效要展示,所以不能直接卸载对应的元素,而是要等待离场动效结束之后在进行卸载。
为了直观一些,我们可以添加一个离场的按钮,用于触发离场动效。
// 创建离场按钮
const btn = document.createElement(\”button\”);
btn.innerText = \”离场\”;
document.body.appendChild(btn);
// 绑定事件
btn.addEventListener(\”click\”, () => {
// 设置离场 初始状态 和 运动过程
div.classList.add(\”leave-from\”);
div.classList.add(\”leave-active\”);
// 嵌套的 requestAnimationFrame 实现在下一帧中,切换元素状态
requestAnimationFrame(() => {
requestAnimationFrame(() => {
div.classList.remove(\”leave-from\”);
div.classList.add(\”leave-to\”);
// 动效结束后,移除和动效相关的类
div.addEventListener(\”transitionend\”, () => {
div.classList.remove(\”leave-to\”);
div.classList.remove(\”leave-active\”);
// 离场动效结束,移除目标元素
div.remove();
});
});
});
});
离场动效,如下:
实现 Transition 组件
以上的实现过程,可以将其进行抽象化为三个阶段:
- beforeEnter
- enter
- leave
现在要从命令式编程转向声明式编程了,因为我们要去编写Vue 组件了,即基于VNode节点来实现,为了和普通的VNode作为区分,Vue中会为目标元素的VNode节点上添加transition属性:
- Transition 组件本身不会渲染任何额外的内容,它只是通过默认插槽读取过渡元素,并渲染需要过渡的元素
- Transition 组件作用,是在过渡元素的VNode节点上添加和transition相关的钩子函数
<script lang=\”ts\”>
import { defineComponent } from \’vue\’;
const nextFrame = (callback: () => unknown) => {
requestAnimationFrame(() => {
requestAnimationFrame(callback)
})
}
export default defineComponent({
name: \’Transition\’,
setup(props, { slots }) {
// 返回 render 函数
return () => {
// 通过默认插槽,获取目标元素
const innerVNode = (slots as any).default()
// 为目标元素添加 transition 相关钩子
innerVNode.transition = {
beforeEnter(el: any) {
console.log(111)
// 设置 初始状态 和 运动过程
el.classList.add(\”enter-from\”);
el.classList.add(\”enter-active\”);
},
enter(el: any) {
// 在下一帧切换状态
nextFrame(() => {
// 切换状态
el.classList.remove(\”enter-from\”);
el.classList.add(\”enter-to\”);
// 动效结束后,移除和动效相关的类
el.addEventListener(\”transitionend\”, () => {
el.classList.remove(\”enter-to\”);
el.classList.remove(\”enter-active\”);
});
})
},
leave(el: any) {
// 设置离场 初始状态 和 运动过程
el.classList.add(\”leave-from\”);
el.classList.add(\”leave-active\”);
// 在下一帧中,切换元素状态
nextFrame(() => {
// 切换元素状态
el.classList.remove(\”leave-from\”);
el.classList.add(\”leave-to\”);
// 动效结束后,移除和动效相关的类
el.addEventListener(\”transitionend\”, () => {
el.classList.remove(\”leave-to\”);
el.classList.remove(\”leave-active\”);
// 离场动效结束,移除目标元素
el.remove();
});
})
}
}
// 返回修改过的 VNode
return innerVNode
}
}
})
</script>
最后
从整体来看,Transition 组件的核心并不算复杂,特别是以命令式编程实现之后,但话说回来在Vue源码中实现的还是很全面的,比如:
- 提供props实现用户自定义类名
- 提供内置模式,即先进后出(in-out)、后进先出(enter-to)
- 支持v-show方式触发过渡效果
以上就是彻底搞懂Transition内置组件的详细内容,更多关于Transition内置组件的资料请关注悠久资源网其它相关文章!
您可能感兴趣的文章:
- Vue transition组件简单实现数字滚动
- Vuetransition过渡组件详解
- Vue中transition标签的基本使用教程
- Flutter利用SizeTransition实现组件飞入效果
- Vue中transition单个节点过渡与transition-group列表过渡全过程
- vue中transition组件在项目中运用小结
- Vue transition实现点赞动画效果的示例