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

这里我们完成第二种动画

首先安装插件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" alt="" 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" 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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 解决vue v-for 遍历循环时key值报错的问题

    一 .问题如下: [Vue warn] Avoid using non-primitive value as key, use string/number value instead. non-primitive表示的是对象 这里的[Vue warn]是指不要用对象或是数组作为key,用string或number作为key. :key相当于是索引的作用,提高循环性能,如果循环量较小,不写也可以的. 以上这篇解决vue v-for 遍历循环时key值报错的问题就是小编分享给大家的全部内容了,希望能

  • Vue中的v-for循环key属性注意事项小结

    当Vue用 v-for 正在更新已渲染过的元素列表是,它默认用"就地复用"策略.如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素. 为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性.key属性的类型只能为 string或者number类型. 在下面这个例子中,如果不给 p 元素绑定key,我先选中第一个, 然后输入ID和Nam

  • 浅谈Vue2.0中v-for迭代语法的变化(key、index)

    今天,在写关于Vue2.0的代码中发现 $key这个值并不能渲染成功,问题如下: 结果这个对象的key值并不能够显示: 后来查阅了文档才知道,这是因为在Vue2.0中,v-for迭代语法已经发生了变化: 丢弃了: 新数组语法 value in arr (value, index) in arr 新对象语法 value in obj (value, key) in obj (value, key, index) in obj 解决后: 以上这篇浅谈Vue2.0中v-for迭代语法的变化(key.i

  • vue2.0移除或更改的一些东西(移除index key)

    一.vue2.0移除了$index和$key 虽然说现在很多文章说他们的代码是vue2.0版本的,但是有一些仔细一看,发现并不全是2.0版本,有些语法还是1.0的版本,比如这个$index,$key,这两个压根就不是2.0的写法,2.0早就把这两个给删除了,我们先来看看之前的列表渲染是怎么写的 <template> <div class="hello"> <ul> <li v-for="item in list">{{

  • 详解key在Vue列表渲染时究竟起到了什么作用

    Vue2+采用diff算法来进行新旧vnode的对比从而更新DOM节点.而通常在我们使用v-for这个指令的时候,Vue会要求你给循环列表的每一项添加唯一的key,那么这个key在渲染列表时究竟起到了什么作用呢? 在解释这一点之前,你最好已经了解Vue的diff算法的具体原理是什么. Vue2更新真实DOM的操作主要是两种:创建新DOM节点并移除旧DOM节点和更新已存在的DOM节点,这两种方式里创建新DOM节点的开销肯定是远大于更新或移动已有的DOM节点,所以在diff中逻辑都是为了减少新的创建

  • vue 监听键盘回车事件详解 @keyup.enter || @keyup.enter.native

    vue运行为v-on在监听键盘事件时,添加了特殊的键盘修饰符: <input v-on:keyup.13="submit"> vue还非常贴心地给出了常用按键的别名,这样就不必去记keyCode ~ ~ 上面代码,还可以在这样写: <input v-on:keyup.enter="submit"> <input @keyup.enter="submit"> 全部的键盘别名: .enter .tab .delet

  • vue element-ui 绑定@keyup事件无效的解决方法

    解决办法: <el-input v-model="searchParmas.gameName" placeholder="游戏名称" class="w120" @keyup.native="getGameList(searchParmas.gameName)"></el-input> 加上.native覆盖原有封装的keyup事件即可. 以上这篇vue element-ui 绑定@keyup事件无效的解

  • Vue中 key keep-alive的实现原理

    vue2.0提供了一个keep-alive组件用来缓存组件,避免多次加载相应的组件,减少性能消耗 keep-aliv是Vue.js的一个内置组件.它能够不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中. 它有两个生命周期: activated: keep-alive组件激活时调用 deactivated: keep-alive组件停用时调用 它提供了include与exclude两个属性,允许组件有条件地进行缓存. keep-a

  • 如何正确理解vue中的key详解

    就目前所了解的情况,key的作用有以下这些. v-for遍历时,用id,uuid之类作为key,唯一标识节点加速虚拟DOM渲染 响应式系统没有监听到的数据,用+new Date()生成的时间戳作为key,手动强制触发重新渲染 场景一大同小异司空见惯,场景二是下面这样的: <div :key="rerender"> <span>Hello Vue.js !</span> <complexComponent :propObj="propO

  • 详解如何理解vue的key属性

    如果没有这个属性的时候vue应用 in-place patch(就地复用)策略.列表里的顺序发生改变的时候比如shuffle(列表打乱)的时候,vue为了提升性能,不会移动dom元素,只是更新相应元素的内容节点. 就地复用的弊端 这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出. 如上引用自官网,这个模式就是上面的"就地复用"策略.那么是不是依赖子组件状态的列表渲染采用上面的模式就出问题了呢.如下测试代码: <ul&g

随机推荐