如何實現這個動畫?
效果分析
點`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>
複製程式碼