懂的都懂
作者:张浔
阅读本篇文章大概耗时3分钟
前言
小伙伴们好啊,我是张浔。
首先感谢来阅读我的文章,今天呢,给大家聊一聊列表过渡和状态过渡,看文字好像理解了又不是很理解,到底是什么意思呢?别急,我慢慢介绍给你认识。
如果你是对 Vue2 一点都不懂的,建议先阅读完我前两篇文章 《教你用Vue2如何做过渡&动画效果+著名动画库》、《学一学在Vue2中实现过渡&动画的初始渲染和多个元素and组件过渡的效果》之后再来看此篇,不然你会很懵。
列表过渡
1. 介绍
大家对列表应该都熟悉吧,我举个例子,有三位好朋友,分别是小明、小红、小刘,他们三个经常在一起玩,组在了一起,这就是一个列表,可能还会有新的朋友加入他们一起玩,也可能哪一天就不跟其中的一个朋友不玩了, 术语中表达就是一个列表中可以有很多元素,可以进行添加也可以进行删除。
既然理解什么是列表了,那么怎么给他们加过渡效果呢?
看看 Vue2—列表过渡 官网中是怎么说的
之前我们给元素加过渡动画时,都是放在了 transition
组件中,这次发现不一样了,官网中介绍告诉我们使用 transition-group
组件,还有如下特点:
- 它默认会以一个
span
元素显示在 DOM 中,可以通过tag
属性更换为其他元素,比如:div
、p
….等等 - 它不可以使用 过渡模式 了,就是咱们前面文章介绍的
mode
属性中的 in-out 和 out-in ,详情请阅读 《学一学在Vue2中实现过渡&动画的初始渲染和多个元素and组件过渡的效果》 - 在
transition-group
组件里面的元素必须有唯一的key
属性值 - 我们自己定义的 CSS 过渡效果的类会应用在里面的元素,不会应用在
transition-group
组件上
我是按我自己理解的意思介绍的它的特点,我个人觉得这样表达自己更容易理解一些,小伙伴一定可以看懂的对吧
介绍完特点,其实发现一个小问题,它说 transition-group
组件默认会以一个 span
元素显示在 DOM 中,之前使用的 transition
组件没有显示吗??? 自己并没有注意这个细节,我去试一试,截图给你们啊。
代码
<template>
<div class="TransitionsView">
<!-- 过渡&动画组件 -->
<transition>
<button v-if="isShow" @click="isShow = !isShow" key="open">开启</button>
<button v-else @click="isShow = !isShow" key="close">关闭</button>
</transition>
</div>
</template>
<script>
export default {
name: 'TransitionsView',
data () {
return {
// 控制盒子显示与隐藏,false为隐藏,true为显示, 默认隐藏
isShow: true
}
}
}
</script>
<style lang="less" scoped>
// 进入(显示)开始时效果
.v-enter {
opacity: 0;
transform: translateX(50px);
}
// 离开(隐藏)结束时效果
.v-leave-to {
opacity: 0;
transform: translateX(-50px);
}
// 进入(显示)和离开(隐藏)过程中效果
.v-enter-active,
.v-leave-active {
transition: all 1s;
}
// 按钮样式
button {
position: absolute;
width: 50px;
margin: 0px -25px;
}
</style>
效果
发现 transition
组件确实没有元素显示,直接是显示的里面的 button
元素,之前一直没有注意到这个小细节,这次记住它了!
我没有放这段代码的动图演示效果,因为此我前面的文章中有过动图了,就是一个滑动过渡的效果,就不过多演示了。
2. 案例
那么我们来写一个小案例来增加小伙伴对于 transition-group
组件的认识吧
要写一个什么案例呢?其实官网中有示例,给你们看看
感觉介绍这个小案例是最容易理解的了,咱们就模仿着写一个吧,不过我们的内容可以变化一下,数字太生硬了,嘿嘿,我给你们带来一个西游篇。
代码
<template>
<div class="ListView">
<button @click="add">添加</button>
<button @click="remove">删除</button>
<!-- 列表过渡组件 -->
<transition-group tag="div">
<span v-for="item in list" :key="item">{{ item }}</span>
</transition-group>
</div>
</template>
<script>
export default {
name: 'ListView',
data () {
return {
// 稀有人物
list: ['唐僧', '悟空', '八戒', '沙师弟', '白龙马'],
// 计数
num: 0
}
},
methods: {
randomIndex () {
// 向下取整
return Math.floor(Math.random() * this.list.length)
},
// 添加
add () {
/**
* splice() 方法
* 第一个参数表示开始位置
* 第二个参数表示要删除的元素数量
* 第三个参数表示要插入的元素
* 返回值是一个空数组, 使用此方法是为了修改数组中的内容
*/
this.list.splice(this.randomIndex(), 0, '师父' + this.num++)
},
// 删除
remove () {
this.list.splice(this.randomIndex(), 1)
}
}
}
</script>
<style lang="less" scoped>
span {
display: inline-block; // 重要
margin: 0 5px;
}
// 进入(显示)开始时和离开(隐藏)结束时效果
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateY(30px);
}
// 进入(显示)和离开(隐藏)过程中的效果
.v-enter-active,
.v-leave-active {
transition: all 1s;
}
</style>
效果
嘿嘿,很轻松的就实现了,聪明的小伙伴一定知道他们喊了几遍师父吧😄。
好了,我们来复盘一下,首先我们如果想给列表加过渡效果,我们需要使用 transition-group
组件,它默认为显示 成span
元素,所以为了让他独自占一行,使用 tag
属性更换成了 div
元素,之后下面的渲染列表元素和一些处理方法我就不介绍了,基础知识,不过有一点我们需要注意,我们必须将 span
标签设置为行内块元素,原因是官网内部实现是使用了 FLIP 动画队列,使用 FLIP 过渡的元素不能设置为行内元素,这一点官网文档中也有介绍说明,看下图。
有想详细了解 FLIP 的小伙伴可以点击链接进去查看,博主还没整明白,你等我整明白了再分享看我的博客也可以哈哈,或者有大佬分享一下自己的理解也可。
小伙伴们仔细的看上面动图的效果,你会发现当添加或删除一个元素时,元素的位置移动很生硬,一点也不丝滑对不对,我们有没有办法让它变得丝滑一些呢?这个问题也是官网文档中引出来的,我们一起看看是怎么说明和解决的。
问题描述我就不再说了,说一下解决的方式,读了一遍文档,了解到 transition-group
组件有一个特殊的功能,它可以改变定位,使用 v-move
类,可以通过 name 属性自定义前缀(不懂这个什么意思到的小伙伴可以看博主前面的文章,里面有详细介绍),还可以通过 move-class
属性设置。
我们分析一下啊,我们的问题是当添加和删除元素的时候,周围的元素移动的时候不丝滑,移动的太快了,我们无非就是想让它们移动的慢一点,诶,这不就好解决了吗,我们给 v-move
类添加 transition: all 1s;
过渡效果不就好了,尝试一下。
代码
我就直接放 CSS 部分的代码了
span {
display: inline-block;
margin: 0 5px;
}
// 进入(显示)开始时和离开(隐藏)结束时效果
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateY(30px);
}
// 进入(显示)和离开(隐藏)过程中的效果, 还有改变定位的 v-move 类
.v-enter-active,
.v-leave-active,
.v-move {
transition: all 1s;
}
效果
看动图,你发现添加元素的时候,周围元素移动的时候比较丝滑,但是删除元素时,周围的元素移动还是生硬,这是怎么回事呢?
这是因为当你删除元素时,那个元素其实还在占着位置,并没有脱离文档流,周围的元素根本移动不过去,我们需要在元素离开(隐藏)时设置为绝对定位就可以了,因为我们知道当给元素设置定位时,元素就会脱离文档流了。
代码
span {
display: inline-block;
margin: 0 5px;
}
// 进入(显示)开始时和离开(隐藏)结束时效果
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateY(30px);
}
// 进入(显示)和离开(隐藏)过程中的效果, 还有改变定位的 v-move 类
.v-enter-active,
.v-leave-active,
.v-move {
transition: all 1s;
}
// 当元素离开(隐藏)过程中的效果
.v-leave-active {
// 设置绝对定位
position: absolute;
}
效果
这时候我们再看效果
是不是很棒,多么的丝滑
官网上还有一个对列表排序的动画效果,我们可不可以也扩展一下呢?
咱们能不能把文字换成图片呢?一直是文字不是很好看,太单一。
3. 扩展案例
我们先思考一下想要的效果,需求都有哪些
- 把文字换成图片
- 对它们进行随机排序
- 最近9宫格图片很火,我们做成9宫格的布局好不好
既然需求提出了,我们需要去找图片,找图片比较好找,度娘一大堆,9宫格布局对于我们来说也不困难,但是第二个需求,我们如何对一个数组进行随机排序呢?
我们都知道数组有一个 sort()
排序方法,我详细介绍一下用法,怕有小伙伴不了解
-
没有传参数时,默认按照字母升序排序
-
它的参数必须是一个函数
-
假设参数的函数是如下这样写的
// x 和 y 表示数组中的元素, 必须是数字 function(x, y) { return x - y }
sort()
方法就会根据函数返回的 x - y 差的值进行排序,如果这个值等于0,x 和 y 的位置保持不变,如果值小于 0,x 会排在 y 的前面,如果大于 0,x 会排在 y 的后面。
介绍完了 sort()
方法,聪明的小伙伴肯定想这个方法也不能随机排序啊,是的,我们还需要用到一个 Math.random()
随机数方法,
这个方***返回一个 0 ~ 1(不包含1) 之间的一个随机数,可能是 0.23333,也可能是 0.844332,反正随机的,我们让它减 0.5 或者随便写一个小数,然后返回它俩的差,再根据这个值进行排序不就可以了,因为是随机数,所以不就是成了随机排序了吗。
好了,说了这么多,下面我们把案例实现一下吧,不理解的小伙伴多看几遍或者自己实践一下,还是不明白的话下方留言评论我再帮你解答。
代码
<template>
<div class="ShuffleImg">
<button @click="shuffle">随机排序</button>
<!-- 列表过渡组件 -->
<transition-group tag="div" class="list-num">
<!-- 图片 -->
<img
:src="require(`../assets/images/${item}`)"
v-for="item in list"
:key="item"
alt=""
/>
</transition-group>
</div>
</template>
<script>
export default {
name: 'ShuffleImg',
data () {
return {
// 存储图片的数组
list: [
'10001.jpg',
'10002.jpg',
'10003.jpg',
'10004.png',
'10005.jpg',
'10006.jpg',
'10007.jpg',
'10008.jpg',
'10009.jpg'
],
newList: []
}
},
methods: {
shuffle () {
// 对数组随机排序
this.list.sort(() => {
return Math.random() - 0.5
})
}
}
}
</script>
<style lang="less" scoped>
// 9宫格布局
.list-num {
// border: 1px solid red;
width: 360px;
margin: 10px auto;
// 图片
img {
display: inline-block;
width: 120px;
height: 120px;
text-align: center;
line-height: 30px;
}
}
// 改变元素定位时效果
.v-move {
transition: all 0.5s;
}
</style>
效果
你看,一个简单的效果就实现了,看完代码后,有小伙伴可能对 img
标签的 src 属性的值有些不理解,为什么要使用 require()
包裹起来?详情可以看我这篇文章 《自己解决在Vue中动态渲染图片不显示的问题 》 ,通俗易懂。
在这里,推荐大家使用一个第三方库 Lodash ,里面封装了很多 JS 的方法,其中就有对数组随机排序的方法,这是一个很出名的库,业内人士几乎都在用,大家自己可以尝试一下使用。
状态过渡
1. 介绍
首先带大家先看看官网 Vue2—状态过渡 中是怎么介绍的
什么意思呢?我理解的就是咱们之前写的那些过渡动画效果,都是元素进入(显示)和离开(隐藏)的效果,没有针对元素本身的动画效果,比如从1到100之间的效果,修改一个元素颜色之后的效果,咱们只说概念可能有小伙伴脑补不出来,我们先看一看官网上的示例效果是什么样子。
看完上面的动图效果,心里什么感受,是不是直呼 “nb”,其实你看完代码就知道,它是结合了第三方动画库实现的,说白了这个动画效果并不是 Vue 内部实现的,只是咱们使用第三方动画库结合 Vue 的组件系统,一些指令和方法去实现这种效果。
有小伙伴心想为什么叫 状态过渡 呢?我自己理解的是一个元素本身有一种状态,拿第一个动图示例的效果举例吧,一开始数字为 0,这是当前数字的状态 ,当数字修改为 100 时,当前数字的状态被改变了,那么当状态一被改变就会执行动画效果,所以叫 状态过渡 。
2. 案例
看完上完的介绍,对于 状态过渡 基本的概念应该有所了解了吧,我们来实现动图示例中的第一个效果怎么样?我认为很经典。
我们看官网中结合的 GreenSock 第三方库实现的,下图
用到了侦听器,看示例效果,其实自己琢磨一下,这个第三方动画库是怎么实现的呢?我们分析一下,我把动图效果贴上来,方便阅读
- 首先是侦听输入框
input
绑定的属性,默认值为 0 - 当数字被修改时,假设修改为了 10,效果为从 0 慢慢加到了 10
- 此时显示的数字为 10,当接着修改时,如果显示的数字大于输入框的数字,就慢慢减,直到减到与输入框的值一样为止
- 如果输入框的值为空,显示的数字又变为了默认值 0
- 如果输入框的值为负数,显示的数字如果大于输入框的数字,就慢慢减,直到减到与输入框的值一样为止
- 发现当输入框为
-
号 或为+
号,显示的数字也是变为了默认值 0
以上分析完毕,发现逻辑可以分为三种
- 当输入为空或为
-
号 、+
号时让显示的数字为 0 - 当显示的数字小于输入框中的数字时,累加,加到相等时停止
- 当显示的数字大于输入框中的数字时,累减,减到相等时停止
思路逻辑想出来了,该用什么实现呢?聪明的小伙伴看到累加和累减时,一定想到了定时器,对,没有错,我们使用定时器写一写。
代码
<template>
<div class="StatusView">
<!-- 将输入框类型设置为 number 类型 -->
<input type="number" v-model="number" />
<!-- 显示的数字 -->
<p>{{ resultNumber }}</p>
</div>
</template>
<script>
export default {
name: 'StatusView',
data () {
return {
// 输入框中的数字
number: 0,
// 要显示的数字
tweenedNumber: 0
}
},
computed: {
resultNumber () {
// toFixed 方法表示根据传递的参数保留几位小数,0表示不保留小数位,1表示保留1位小数....以此类推
return this.tweenedNumber.toFixed(0)
}
},
watch: {
number (newVal) {
console.log('当前数字为' + newVal)
// 当输入框的值为空时等于 0
newVal = newVal || 0
// 定义定时器, 实现数字变化的动画效果
const res = setInterval(() => {
// 如果输入框为0时
if (!newVal) {
// 定时器
const res1 = setInterval(() => {
/**
* 三元运算符
* 如果显示的数字大于0时, 就让显示的数字一直减
* 减到等于 0 时关闭定时器
*/
this.tweenedNumber > 0 ? this.tweenedNumber-- : clearInterval(res1)
}, 10)
}
// 如果输入框中的数字为负数时
if (newVal < this.tweenedNumber) {
// 定时器
const res2 = setInterval(() => {
/**
* 三元运算符
* 如果显示的数字大于输入框中的数字就让显示的数字一直减
* 减到等于输入框中的数字时关闭定时器
*/
this.tweenedNumber > newVal
? this.tweenedNumber--
: clearInterval(res2)
}, 10)
}
// 如果显示的数字大于等于输入框中的数字,关闭定时器
if (this.tweenedNumber >= newVal) {
// 关闭定时器
clearInterval(res)
// 停止运行后面的代码
return
}
// 如果上面条件不满足,让数字一直累加
this.tweenedNumber++
}, 10)
}
}
}
</script>
<style lang="less" scoped>
.StatusView {
color: red;
// 输入框
input {
width: 100px;
height: 20px;
border: 1px solid #0a77e3;
outline: none;
}
}
</style>
效果
就是上图效果,使用定时器自己实现数字变化的效果,我们发现输入 -
和 +
号并没有输出,这是因为 input
设置为了 number
类型,只可以是数字,所以输入符号无效,不过是有问题的,因为没有考虑防抖和节流,所以你看右侧控制台的输出,只要你一修改就会立马响应,这样是很耗性能的。
你看自己写的话,需要很多很多的代码,这就体现了一致性、模块化、高性能的工具库重要性,这句话引用的是 Lodash 官方的介绍,还都是开源的,这些人太伟大了,其实想一想,它们能够写出来,自己何尝又不行呢,先模仿着人家写出来一个也是很不错的,关键就是不能懒惰,要做一个勤奋的人。
总结
感谢大家的阅读与支持,今天给大家分享了 列表过渡 和 状态过渡 一小点知识,希望本篇文章帮助到了你,如果读完此篇文章有对其中内容不解的地方可以下方评论留言,有不对的地方也请大家帮忙指出,今天就到这里咯。