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的布局按效果图那些布置。javascript


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变量改变的时候,就会触发【过渡】css


这里咱们完成第二种动画
  1. 首先安装插件npm i create-keyframe-animation 这个插件是用js写css的keyframe动画用的,至于为何keyframe不在css里面写呢?那是由于屏幕大小不同,会致使须要移动的px不同,因此要动态计算。
  2. <transition>添加动画钩子
<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>
复制代码
  1. 计算偏移量(中心点到中心的偏移,图中红线距离)
// 得到偏移量,以及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的数值表明什么?见图 html

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

由于浏览器的坐标系的中心点是在左上角的,如图vue

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

  1. 而后用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'] = ''
    }
复制代码

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

但在写js的keyframe的时候浏览器

咱们还能够加上rotate,让动画效果有一个回弹效果app

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 class="index">
    <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>
    <button @click="switchMode" style="position:absolute;top:0;left:10px;">start</button>
    <transition>
      <div class="mini-box" v-show="!fullScreen">
        <div class="mini-img">
          <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 lang="stylus" 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>
复制代码
相关文章
相关标签/搜索