懂的都懂

你还不会?

作者:张浔

阅读本篇文章大概耗时3分钟

前言

小伙伴们好啊,我是张浔。

首先感谢来阅读我的文章,今天呢,给大家聊一聊列表过渡和状态过渡,看文字好像理解了又不是很理解,到底是什么意思呢?别急,我慢慢介绍给你认识。

如果你是对 Vue2 一点都不懂的,建议先阅读完我前两篇文章 《教你用Vue2如何做过渡&动画效果+著名动画库》、《学一学在Vue2中实现过渡&动画的初始渲染和多个元素and组件过渡的效果》之后再来看此篇,不然你会很懵。

列表过渡

1. 介绍

大家对列表应该都熟悉吧,我举个例子,有三位好朋友,分别是小明、小红、小刘,他们三个经常在一起玩,组在了一起,这就是一个列表,可能还会有新的朋友加入他们一起玩,也可能哪一天就不跟其中的一个朋友不玩了, 术语中表达就是一个列表中可以有很多元素,可以进行添加也可以进行删除。

既然理解什么是列表了,那么怎么给他们加过渡效果呢?

看看 Vue2—列表过渡 官网中是怎么说的

image-20220514170440144

之前我们给元素加过渡动画时,都是放在了 transition 组件中,这次发现不一样了,官网中介绍告诉我们使用 transition-group 组件,还有如下特点:

  • 它默认会以一个 span 元素显示在 DOM 中,可以通过 tag 属性更换为其他元素,比如:divp ….等等
  • 它不可以使用 过渡模式 了,就是咱们前面文章介绍的 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>

效果

image-20220514172056910

发现 transition 组件确实没有元素显示,直接是显示的里面的 button 元素,之前一直没有注意到这个小细节,这次记住它了!

我没有放这段代码的动图演示效果,因为此我前面的文章中有过动图了,就是一个滑动过渡的效果,就不过多演示了。

2. 案例

那么我们来写一个小案例来增加小伙伴对于 transition-group 组件的认识吧

要写一个什么案例呢?其实官网中有示例,给你们看看

GIF 2022-5-14 17-28-51

感觉介绍这个小案例是最容易理解的了,咱们就模仿着写一个吧,不过我们的内容可以变化一下,数字太生硬了,嘿嘿,我给你们带来一个西游篇。

代码

<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>

效果

GIF 2022-5-14 17-42-21

嘿嘿,很轻松的就实现了,聪明的小伙伴一定知道他们喊了几遍师父吧😄。

好了,我们来复盘一下,首先我们如果想给列表加过渡效果,我们需要使用 transition-group 组件,它默认为显示 成span 元素,所以为了让他独自占一行,使用 tag 属性更换成了 div 元素,之后下面的渲染列表元素和一些处理方法我就不介绍了,基础知识,不过有一点我们需要注意,我们必须将 span 标签设置为行内块元素,原因是官网内部实现是使用了 FLIP 动画队列,使用 FLIP 过渡的元素不能设置为行内元素,这一点官网文档中也有介绍说明,看下图。

image-20220514175356924

有想详细了解 FLIP 的小伙伴可以点击链接进去查看,博主还没整明白,你等我整明白了再分享看我的博客也可以哈哈,或者有大佬分享一下自己的理解也可。

image-20220514175422983

小伙伴们仔细的看上面动图的效果,你会发现当添加或删除一个元素时,元素的位置移动很生硬,一点也不丝滑对不对,我们有没有办法让它变得丝滑一些呢?这个问题也是官网文档中引出来的,我们一起看看是怎么说明和解决的。

image-20220514180621516

问题描述我就不再说了,说一下解决的方式,读了一遍文档,了解到 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;
}

效果

GIF 2022-5-14 18-16-06

看动图,你发现添加元素的时候,周围元素移动的时候比较丝滑,但是删除元素时,周围的元素移动还是生硬,这是怎么回事呢?

这是因为当你删除元素时,那个元素其实还在占着位置,并没有脱离文档流,周围的元素根本移动不过去,我们需要在元素离开(隐藏)时设置为绝对定位就可以了,因为我们知道当给元素设置定位时,元素就会脱离文档流了。

代码

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;
}

效果

这时候我们再看效果

GIF 2022-5-14 18-22-30

是不是很棒,多么的丝滑

官网上还有一个对列表排序的动画效果,我们可不可以也扩展一下呢?

咱们能不能把文字换成图片呢?一直是文字不是很好看,太单一。

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>

效果

GIF 2022-5-14 19-46-48

你看,一个简单的效果就实现了,看完代码后,有小伙伴可能对 img 标签的 src 属性的值有些不理解,为什么要使用 require() 包裹起来?详情可以看我这篇文章 《自己解决在Vue中动态渲染图片不显示的问题 》 ,通俗易懂。

在这里,推荐大家使用一个第三方库 Lodash ,里面封装了很多 JS 的方法,其中就有对数组随机排序的方法,这是一个很出名的库,业内人士几乎都在用,大家自己可以尝试一下使用。

状态过渡

1. 介绍

首先带大家先看看官网 Vue2—状态过渡 中是怎么介绍的

image-20220514195910700

什么意思呢?我理解的就是咱们之前写的那些过渡动画效果,都是元素进入(显示)和离开(隐藏)的效果,没有针对元素本身的动画效果,比如从1到100之间的效果,修改一个元素颜色之后的效果,咱们只说概念可能有小伙伴脑补不出来,我们先看一看官网上的示例效果是什么样子。

GIF 2022-5-14 20-10-05

看完上面的动图效果,心里什么感受,是不是直呼 “nb”,其实你看完代码就知道,它是结合了第三方动画库实现的,说白了这个动画效果并不是 Vue 内部实现的,只是咱们使用第三方动画库结合 Vue 的组件系统,一些指令和方法去实现这种效果。

有小伙伴心想为什么叫 状态过渡 呢?我自己理解的是一个元素本身有一种状态,拿第一个动图示例的效果举例吧,一开始数字为 0,这是当前数字的状态 ,当数字修改为 100 时,当前数字的状态被改变了,那么当状态一被改变就会执行动画效果,所以叫 状态过渡

2. 案例

看完上完的介绍,对于 状态过渡 基本的概念应该有所了解了吧,我们来实现动图示例中的第一个效果怎么样?我认为很经典。

我们看官网中结合的 GreenSock 第三方库实现的,下图

image-20220514202237288

用到了侦听器,看示例效果,其实自己琢磨一下,这个第三方动画库是怎么实现的呢?我们分析一下,我把动图效果贴上来,方便阅读

GIF 2022-5-14 20-36-13

  • 首先是侦听输入框 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>

效果

GIF 2022-5-14 21-00-29

就是上图效果,使用定时器自己实现数字变化的效果,我们发现输入 -+ 号并没有输出,这是因为 input 设置为了 number 类型,只可以是数字,所以输入符号无效,不过是有问题的,因为没有考虑防抖和节流,所以你看右侧控制台的输出,只要你一修改就会立马响应,这样是很耗性能的。

你看自己写的话,需要很多很多的代码,这就体现了一致性、模块化、高性能的工具库重要性,这句话引用的是 Lodash 官方的介绍,还都是开源的,这些人太伟大了,其实想一想,它们能够写出来,自己何尝又不行呢,先模仿着人家写出来一个也是很不错的,关键就是不能懒惰,要做一个勤奋的人。

总结

感谢大家的阅读与支持,今天给大家分享了 列表过渡状态过渡 一小点知识,希望本篇文章帮助到了你,如果读完此篇文章有对其中内容不解的地方可以下方评论留言,有不对的地方也请大家帮忙指出,今天就到这里咯。