宝玛科技网
您的当前位置:首页Vue中使用create-keyframe-animation与动画钩子完成复杂动画

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

来源:宝玛科技网


本篇文章主要介绍了 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" " 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" " >
     </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变量改变的时候,就会触发【过渡】

    这里我们完成第二种动画

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

    给 <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" " class="bg">
     </div>
     </transition>

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

     

    // 获得偏移量,以及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偏移为正

    然后用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 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" " 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" " >
     </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>
    显示全文