mingyunyuziyou

Vue中使用create-keyframe-animation、动画钩子完成复杂动画

作者: 秒速五厘米     
 


如何实现这个动画?

--------------------------------------------------------------------------------------

效果分析
点`start`的时候,我们把整个动画拆分为两种效果(过渡和动画)。

1. 中间cd消失,下方播放条显示,这是属于`过渡` 
2. `过渡`开始的同时,cd同时移动、放大、缩小到左下方播放条  ,这属于`动画`

上面的效果是【过渡】加【动画】同时使用完成的
  • 对于第一种【过渡】,我们用vue中transition标签,加设置v-enter、v-leave-to、v-enter-active、v-leave-enter即可完成

  • 对于第二种【动画】,我们就要用keyframe来完成了。

这里我们先完成第一种过渡
  • vue中模板节点

<template>
  <div class="index">
    <transition>
      <div class="cd-box" ref="cdWrapper" v-show="fullScreen">
    // CD图片 (动画的时候图片初始位置)
        <img src="../assets/bj.png" alt="" class="bg">
      </div>
    </transition>
    <button @click="switchMode" style="position:absolute;top:0;left:10px;">start</button>
    <transition>
    // 下面播放状态框
      <div class="mini-player-box" v-show="!fullScreen">
   // 状态看里面的图片 (动画的时候图片结束位置)
        <div class="mini-img">
          <img src="../assets/bj.png" alt="" >
        </div>
      </div>
    </transition>
  </div>
</template>


结构很简单,基本就是两个大div,然后把div的布局按效果图那些布置

------------------------------------------------------------------------------------------------------------


css部分(省略布局部分)
.cd-box
    &.v-enter-active, &.v-leave-active
      transition: all 0.4s
    &.v-enter, &.v-leave-to
      opacity: 0
.mini-player-box
    &.v-enter-active, &.v-leave-active
      transition: all 0.4s 
    &.v-enter, &.v-leave-to
      transform: translate3d(0, 40px, 0)
      opacity: 0


这样在fullScreen变量改变的时候,就会触发【过渡】

------------------------------------------------------------------------------------------------------------


这里我们完成第二种动画

1.首先安装插件npm i create-keyframe-animation 这个插件是用js写css的keyframe动画用的,至于为什么keyframe不在css里面写呢?那是因为屏幕大小不一样,会导致需要移动的px不一样,所以要动态计算。

2.给添加动画钩子

<transition 
      @enter="enter"
      @after-enter="afterEnter"
      @leave="leave"
      @after-leave="afterLeave"
    >  
      <div class="cd-box" ref="cdWrapper" v-show="fullScreen">
        <img src="../assets/bj.png" alt="" class="bg">
      </div>
</transition>


3.计算偏移量(中心点到中心的偏移,图中红线距离)

// 获得偏移量,以及scale
_getPosAndScale() {
   // 左下角图片的宽度
     const targetWidth = 40
   // cd宽度
     const width = 300
     const scale = targetWidth / width
     // 这里的 x,y要算,过程省略,无非就是加加减减,这的x,y都是算出来了的
     const x = -167.5
     const y = 497
     return {x ,y , scale}
   },


x,y的数值代表什么?见图



这里x为什么是负的,y是正的呢?

因为浏览器的坐标系的中心点是在左上角的,如图


那么动画从cd中心到左下角,X偏移为负,y偏移为正

    4.然后用animations插件执行动画钩子

        // enter是指当 cd从隐藏到显示的动画,
        enter(el, done) {
          const {x, y, scale} = this._getPosAndScale()
    
          let animation = {
          // 第0帧的时候,先让图片缩小,显示在右下角
            0: {
              transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`
            },
          // 60%的时候,让图片回到cd中心,变大
            60: {
              transform: `translate3d(0 ,0 , 0) scale(1.1)`
            },
         // 变回原来的尺寸,会有一个回弹的效果
            100: {
              transform: `translate3d(0 ,0 , 0) scale(1)`
            }
          }
          // 动画的一些配置
          animations.registerAnimation({
            name: 'move',
            animation,
            presets: {
              duration: 400,
              easing: 'linear'
            }
          })
    //运行动画
        animations.runAnimation(this.$refs.cdWrapper, 'move', done)
        },
        afterEnter(){
        //运行完动画之后,注销掉动画
         animations.unregisterAnimation('move')
         this.$refs.cdWrapper.style.animation = ''
        },
        // leave是指 cd从显示到隐藏的动画
        leave(el, done) {
          this.$refs.cdWrapper.style.transition = 'all 0.4s'
          const {x, y, scale} = this._getPosAndScale()
          // 这里我们只要直接移动变小就可以了
          this.$refs.cdWrapper.style['transform'] = `translate3d(${x}px,${y}px,0) scale(${scale})`
             
     // 监听transitionend 事件在 CSS 完成过渡后触发done回调     
     this.$refs.cdWrapper.addEventListener('transitionend', () => {
            done()
          })
        },
        afterLeave() {
          this.$refs.cdWrapper.style.transition = ''
          this.$refs.cdWrapper.style['transform'] = ''
        }



    ------------------------------------------------------------------------------------------------------------


    写到这里,我们就把刚开始的效果给写完啦!

    但在写js的keyframe的时候

    我们还可以加上rotate,让动画效果有一个回弹效果

      let animation = {
            0: {
              transform: `translate3d(${x}px, ${y}px, 0) scale(${scale}) rotate(0deg)`
            },
            60: {
              transform: `translate3d(0 ,0 , 0) scale(1.1) rotate(365deg)`
            },
            100: {
              transform: `translate3d(0 ,0 , 0) scale(1) rotate(360deg)`
            }
          }

    所有源码

    <template>
      <div>
        <transition 
          @enter="enter"
          @after-enter="afterEnter"
          @leave="leave"
          @after-leave="afterLeave"
        >  
          <div ref="cdWrapper" v-show="fullScreen">
            <img src="../assets/bj.png" alt="">
          </div>
        </transition>
        <button @click="switchMode" style="position:absolute;top:0;left:10px;">start</button>
        <transition>
          <div v-show="!fullScreen">
            <div>
              <img src="../assets/bj.png" alt="" >
            </div>
          </div>
        </transition>
      </div>
    </template>
    
    <script>
    /* eslint-disable */
    import animations from 'create-keyframe-animation'
    export default {
      components: {},
      props: {},
      data() {
        return {
          fullScreen: true
        }
      },
      computed: {},
      watch: {},
      created() {},
      mounted() {
        // const {x, y, scale} = this._getPosAndScale()
        console.log(this._getPosAndScale())
        console.log(animations)
      },
      methods: {
        switchMode() {
          this.fullScreen = !this.fullScreen
        },
        _getPosAndScale() {
          const targetWidth = 40
          const paddingLeft = 20
          const paddingBottom = 20
          const paddingTop = 0
          const width = 300
          const scale = targetWidth / width
          const x = -(window.innerWidth / 2 - paddingLeft)
          const y = window.innerHeight - paddingTop - paddingBottom - width / 2
          return {x ,y , scale}
        },
        enter(el, done) {
          const {x, y, scale} = this._getPosAndScale()
    
          let animation = {
            0: {
              transform: `translate3d(${x}px, ${y}px, 0) scale(${scale}) rotate(0deg)`
            },
            60: {
              transform: `translate3d(0 ,0 , 0) scale(1.1) rotate(365deg)`
            },
            100: {
              transform: `translate3d(0 ,0 , 0) scale(1) rotate(360deg)`
            }
          }
          animations.registerAnimation({
            name: 'move',
            animation,
            presets: {
              duration: 400,
              easing: 'linear'
            }
          })
          animations.runAnimation(this.$refs.cdWrapper, 'move', done)
        },
        afterEnter(){
          animations.unregisterAnimation('move')
          this.$refs.cdWrapper.style.animation = ''
        },
        leave(el, done) {
          this.$refs.cdWrapper.style.transition = 'all 0.4s'
          const {x, y, scale} = this._getPosAndScale()
          this.$refs.cdWrapper.style['transform'] = `translate3d(${x}px,${y}px,0) scale(${scale})`
          // this.$refs.cdWrapper.style['transform'] = 'rotate(360deg)'
          // transitionend 事件在 CSS 完成过渡后触发
          this.$refs.cdWrapper.addEventListener('transitionend', () => {
            done()
          })
        },
        afterLeave() {
          this.$refs.cdWrapper.style.transition = ''
          this.$refs.cdWrapper.style['transform'] = ''
        }
      }
    }
    </script>
    <style scoped>
    .index
      background: #eee
      width: 100%
      height: 100%
      display : flex
      flex-direction: column
      justify-content : space-between
      align-items: center
      .cd-box
        display : flex
        justify-content : center
        align-items : center
        width: 300px
        height: 300px
        background: #eee
        border-radius: 50%
        &.v-enter-active, &.v-leave-active
          transition: all 0.4s
        &.v-enter, &.v-leave-to
          opacity: 0
        .bg
          width: 300px
          height: 300px
          border-radius: 50%
      .mini-box
        position: absolute
        bottom: 0
        right: 0
        left: 0
        display : flex 
        align-items center 
        border: 1px solid #555
        width: 100%
        height: 40px
        box-sizing : border-box
        &.v-enter-active, &.v-leave-active
          transition: all 0.4s 
        &.v-enter, &.v-leave-to
          transform: translate3d(0, 40px, 0)
          opacity: 0
        .mini-img
          height: 40px
          width: 40px
          box-sizing : border-box
          img
            height: 100%
            width: 100%
    </style>


    链接:https://juejin.im/post/5caaf85fe51d452b4d56182a