展开收起动画(JavaScript 钩子)
前面使用了vue的css类名实现基础的动画效果,这里要做一个更酷炫的效果。 如上图,从迷你播放器进入全屏的时候,迷你播放器中的图片飞入全屏的唱片中(逐渐放大飞入),或则说更大,然后再缩小到正常状态,和顶部的贝塞尔曲线效果相呼应
先来看一下示意图,到底是怎么个飞?原理如下图
1. 飞入原理
简单一句话:在全屏播放器打开的时候,让cd元素先移动到mini播放器的小cd处,也就是 大圆中心点,移动到小圆中心点
移动的时候是 基于大圆为基准,所以这里是 x轴 往左移动(负数),y轴往下移动(正数)
那么就要计算出往左移动多少,和往下移动多少。
x轴偏移量 = -(屏幕的宽度/2 - 小圆的半径 - 小圆距离屏幕左边的距离) y轴的偏移量 = 屏幕的高度 - cd距离顶部高度 - 小圆中心点距离屏幕底部的距离 - 大圆的半径
上面的高度半径什么的,都是以容器的css样式计算的,其他的高度宽度都是 css 写死的,padding值,图片容器宽高等,只有大圆的半径是屏幕宽度的百分之80
所以就计算出来了 x,y轴的偏移量。再加上缩放的比例(小圆占大圆的比例)
在效果上看起来就是从迷你播放器cd上慢慢变大并且飞入到大cd中
2. 飞入动画代码
这里使用一个js创建动画css代码的库 create-keyframe-animation
和 vue的动画钩子
<transition name="normal"
@enter="enter"
@after-enter="afterEnter"
@leave="leave"
@after-leave="afterLeave"
>
methods: {
enter (el, done) {
// 进入的时候,让小cd飞入大cd位置,过程了先慢慢放大最后再缩小成大cd效果
const {x, y, scale} = this._getPosAndScale()
let animation = {
0: {
// 动画开始,让大cd缩小成小cd一致,且把自己移动到小cd位置处
transform: `translate3d(${x}px,${y}px,0) scale(${scale})`
},
60: {
// 移动回大cd原来的位置,且cd放大一点点
transform: `translate3d(0,0,0) scale(1.1)`
},
100: {
// 最后大cd还原成原来的大小
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 () {
this.$refs.cdWrapper.style.animation = ''
},
leave (el, done) {
// 离开的收,让大cd缩小移动到小cd处
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.addEventListener('transitionend', done)
},
afterLeave () {
this.$refs.cdWrapper.style.transition = ''
this.$refs.cdWrapper.style[transform] = ''
},
// 获取大cd圆心点移动和缩放到小cd圆心点的位置和缩小比例
_getPosAndScale () {
let miniLeftPadding = 40 / 2 + 20 // 迷你播放器的图形40px,paddingLeft=20,小图片中心点距离左边就是40
let miniBottomPadding = 60 / 2 // mini-player 元素高60,图片是居中的,所以中心点距离底部30
let normalTopPadding = 80 // middle 元素距离顶部80
let normalWidth = window.innerWidth * 0.8 // cd-wrapper 的宽是容器的百分之80,容器是圆形(正方形)所以宽高一致
let miniWidth = 40 // 迷你播放器图形宽度(因为是圆形所以宽高一致)
let scale = miniWidth / normalWidth // 小图形占比大图形的比例
let x = -(window.innerWidth / 2 - miniLeftPadding)
let y = window.innerHeight - normalTopPadding - miniBottomPadding - normalWidth / 2
return {
x, y, scale
}
}
}