共计 7227 个字符,预计需要花费 19 分钟才能阅读完成。
前言
欢送关注同名公众号《熊的猫》,文章会同步更新!
<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 形式触发过渡成果
- …
心愿以上内容对你有所帮忙!!!