Vue中使用transition或transition-group标签实现动画效果

transition和transition-group是Vue官方提供的两个内置组件,他们会检测所有子节点的插入和移除,依次在这些属性作用的各个阶段抛出钩子函数接受我们定义的动画或者第三方库里的动画然后再把每个阶段的动画重新拼接成一个完整的动画效果。利用这两个内置组件可以实现一些简单的动画效果。本文针对transition和transition-group实现动画效果以例子的形式做简要介绍。

1、transition动画:
例子效果图:
在这里插入图片描述

点击“开“或”收“三个圆会自动展开或收起。
核心代码:

<button @click="showMenu" class="btn">{{text}}</button>
      <transition name="move">
        <div class="menu" v-show="show">
          <div class="inner inner-1"></div>
          <div class="inner inner-2"></div>
          <div class="inner inner-3"></div>
        </div>
      </transition>
data () {
    return {
      show: false
    }
  },
  methods: {
    showMenu () {
      this.show = !this.show
    }
  },
  computed: {
    text () {
      return this.show ? '收' : '开'
    }
  }
.menu{
   position: fixed;
   bottom: 50px;
   right: 10px;
   width: 50px;
   height: 50px;
   border-radius: 50%;
   transition: all .7s ease-in;
   &.move-enter-active{
    .inner{
     transform: translate3d(0, 0, 0);
     transition-timing-function: cubic-bezier(0, .57, .44, 1.97)}
    .inner-1{
     transition-delay: .1s}
    .inner-2{
     transition-delay: .2s}
    .inner-3{
     transition-delay: .3s}
   }
   &.move-enter, &.move-leave-active{
    .inner{
     transition-timing-function: ease-in-out}
    .inner-1{
     transform: translate3d(0, 60px, 0);
     transition-delay: .3s}
    .inner-2{
     transform: translate3d(40px, 40px, 0);
     transition-delay: .2s}
    .inner-3{
     transform: translate3d(60px, 0, 0);
     transition-delay: .1s}
   }
   .inner{
    display: inline-block;
    position: absolute;
    width: 30px;
    height: 30px;
    line-height: 30px;
    border-radius: 50%;
    background: #1d1d1b;
    z-index: -1;
    text-align: center;
    color: #fff;
    transition: all .4s}
   .inner-1{
    top: -40px;
    left: 10px}
   .inner-2{
    left: -30px;
    top: -30px}
   .inner-3{
    left: -40px;
    top: 10px}
  }

主思想:
在进入/离开的过渡中,vue提供了6个class切换,通过点击事件,类名会对应变化,我们修改对应类名的样式即可达到我们想要的动画效果。值得注意的是如果在中没有定义name,则v-是这些类名的默认前缀,若定义了name,则name-是这些类名的默认前缀。

2、transition-group动画:
在某些场景下我们希望在v-for的循环中使用动画效果,这时transition-group标签就排上了用场。transition-group的用法和transition基本一致,不同的是如果用到v-for则需要使用transition-group,并且每个 transition-group 的子节点必须有独立的 key,动画才能正常工作。
例子效果图:
在这里插入图片描述

在刷新页面后页面会依次显示1-4,当点击加号时,会在下方逐渐出现后续的数字和方块。
核心代码:

<div class="app">
      <button @click="add" class="add-btn">+</button>
      <transition-group name="slide" tag="ul" class="list-wrapper" appear>
      <li class="list" v-for="(item) in lists" :key="item">
        <span class="text">{{item}}</span>
      </li>
      </transition-group>
    </div>
data () {
    return {
      lists: []
    }
  },
  methods: {
    add () {
      this.lists.push(this.lists.length + 1)
    },
    test () {
      const temp = setInterval(() => {
        this.lists.push(this.lists.length + 1)
        if (this.lists.length > 3) {
          clearInterval(temp)
        }
      }, 100)
}
	}
mounted () {
    this.test()
 }
.slide-enter-active{
    transition: all 3s ease;
    opacity:0
  }
  .slide-enter-to{
    transition: all 3s ease;
    opacity:1
  }

主思想:
在里加了appear属性,他会让初次加载的时候自动加载动画。这里加了一个定时器控制数组的变化,否则整个列表都会一起显示出来,而不是逐条显示。通过上面这个例子希望可以给大家在后续做一些动画的时候提供新的思路。
3、小结:
对于transition或transition-group标签实现动画除了本文中提到的6个过渡类名,还有一些事件比如enter、leave、appear等来绑定函数从而来实现更加精细的动画效果。transition 实现动画的思路是通过某种手段比如v-if来触发不同的过渡效果来显示不同的动画。而transition-group实现动画思路是将一个完整的动画拆分成在每个阶段,然后在编译阶段重新拼接为一个完整的动画。transition和transition-group实现简单动画效果时比较方便,但是在实现复杂动画时,比如循环嵌套循环时会出现比如抖动等奇怪的效果,所以如果面对比较复杂的动画效果时可以另辟蹊径选择其他的实现方式。

ps:有大佬可以帮忙看下为什么循环嵌套transition-group的循环会出现奇怪的抖动吗?
在这里插入图片描述
附上主要代码:

<div v-for="(areaCell) in areaArr" :key="areaCell.regionName" class="pie_canvas_box">
            <p class="numBar">
              <span class="current">{{parseInt(areaCell.current * animateTime)}}</span><span class="total">/{{parseInt(areaCell.total * animateTime)}}</span>
            </p>
            <transition-group
              name="animation"
              appear>
                <div  class="cell_box" v-for="(defultCell) in areaCell.cells" :key="defultCell.num">
                  <div :style="'width:' + defultCell.classWidth + ';' + 'background:' + defultCell.color + ';' + 'opacity:' + defultCell.opacity" :class="defultCell.className"></div>
                </div>
            </transition-group>
            <p class="cell_name">{{areaCell.regionName}}</p>
          </div>
mounted () {
    this.initArea()
  },
  methods: {
    initArea () {
    // 这一大堆都是用来生成小方块,可以省略不看----------------------------start
      this.areaArr.map((item, index) => {
        let vm = this
        vm.cells = []
        let allPart = parseFloat(item.occupancyRatio) * 200
        let integerPart = (allPart + '').indexOf('.') > -1
          ? (allPart + '').split('.')[0]
          : allPart
        this.integerPart = integerPart
        let decimalPart = (allPart + '').indexOf('.') > -1
          ? (allPart + '').split('.')[1]
          : 0
        for (let i = 1; i < 201; i++) {
          if (i <= integerPart) {
            if (i <= 40) {
              vm.cells.push({
                num: i,
                color: '#0F6D48',
                className: 'cell_box_twenty',
                classWidth: '100%',
                opacity: 1
              })
            } else if (i > 40 && i <= 80) {
              vm.cells.push({
                num: i,
                color: '#1E9D47',
                className: 'cell_box_forty',
                classWidth: '100%',
                opacity: 1
              })
            } else if (i > 80 && i <= 120) {
              vm.cells.push({
                num: i,
                color: '#1BBD6C',
                className: 'cell_box_sixty',
                classWidth: '100%',
                opacity: 1
              })
            } else if (i > 120 && i <= 160) {
              vm.cells.push({
                num: i,
                color: '#24F485',
                className: 'cell_box_eighty',
                classWidth: '100%',
                opacity: 1
              })
            } else if (i > 160 && i <= 200) {
              vm.cells.push({
                num: i,
                color: '#FFCA26',
                className: 'cell_box_hundred',
                classWidth: '100%',
                opacity: 1
              })
            }
          } else {
            vm.cells.push({
              num: i,
              color: '#AECCBF',
              className: 'cell_box_inner',
              classWidth: '100%',
              opacity: 0.1
            })
          }
        }
        if (vm.cells[integerPart]) {
          if (parseFloat('0.' + decimalPart) * 100 === 0) { // 当小数位为0时,展示背景色为灰色
            vm.cells[integerPart].color = '#FFFFFF'
            vm.cells[integerPart].opacity = 0.1
          } else {
            vm.cells[integerPart].classWidth = parseFloat('0.' + decimalPart) * 100 + '%'
          }
        } else {
          vm.cells[integerPart - 1].classWidth = '100%'
        }
        // vm.cells[integerPart] ? vm.cells[integerPart].classWidth = parseFloat('0.' + decimalPart) * 100 + '%' : vm.cells[integerPart - 1].classWidth = '100%'
        if (allPart <= 40) {
          vm.cells[integerPart].className = 'cell_box_twenty'
        } else if (allPart > 40 && allPart <= 80) {
          vm.cells[integerPart].className = 'cell_box_forty'
        } else if (allPart > 80 && allPart <= 120) {
          vm.cells[integerPart].className = 'cell_box_sixty'
        } else if (allPart > 120 && allPart <= 160) {
          vm.cells[integerPart].className = 'cell_box_eighty'
        } else if (allPart > 160 && allPart < 200) {
          vm.cells[integerPart].className = 'cell_box_hundred'
        } else if (allPart === 200) {
          vm.cells[integerPart - 1].className = 'cell_box_hundred'
        }
        // item.cells = vm.cells
        // 这一大堆都是用来生成小方块,可以省略不看-------------------------------end
        this.timer(index, vm.cells)
      })
    },
    timer (index, cells) {
      let i = 0
      let temp = setInterval(() => {
        this.areaArr[index].cells.push(cells[i])
        i = i + 1
        if (i >= cells.length) {
          clearInterval(temp)
        }
      }, 10)
    }
  }
::v-deep.animation-enter-active {
  transition: all 1s ease;
  opacity:0;
}

::v-deep.animation-enter-to {
  opacity: 1;
}

参考文章:
vue官网transition&transition-group内置组件
vue官网过渡
知乎某大佬的文章
前端开发者网站某大佬的文章
也许可以解决奇怪的抖动

Logo

前往低代码交流专区

更多推荐