這篇“Vue動畫實例代碼分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Vue動畫實例代碼分析”文章吧。
以顯示和隱藏動畫為例:
<div id="demo"> <button v-on:click="show = !show"> Toggle </button> <transition name="fade"> <p v-if="show">hello</p> </transition> </div>
當動畫開始的時候,P 標簽還是隱藏的,此時 Vue 會給 P 加上兩個class:
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 0.5s;
}
當動畫開始后,會移除.fade-enter(在元素被插入之前生效,在元素被插入之后的下一幀移除),這個時候 P 標簽的 opacity就恢復到 1,即顯示,這個時候就會觸發transition , 檢測到opacity的變化,就會產生動畫。當動畫結束后,會移除 Vue 加上的 class(v-enter-to, v-enter-active)。

上面這個過程是怎么實現的呢?它主要用到了requestAnimationFrame這個api,我們自己可以實現一個簡易版的動畫,當生成下一幀的時候添加或刪除某個類,從而形成動畫效果。
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
<style type="text/css">
.box {
width: 100px;
height: 100px;
background-color: red;
}
.enter {
opacity: 0;
}
.mov {
transition: opacity 5s linear;
}
</style>
</head>
<body>
<div id="box" class="box mov enter"></div>
<script>
var box = document.getElementById('box')
// 第一幀之后執行
requestAnimationFrame(function() {
box.setAttribute('class', 'box mov')
})
</script>
</body>
</html>當生成下一幀的時候,會移除enter這個class,那么 div 就會顯示出來,就會觸發transition產生動畫效果。
當隱藏的時候也產生動畫,如下圖:

.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: opacity 0.5s;
}剛開始 P 標簽是顯示的,因為此時fade-leave (在離開過渡被觸發時立刻生效,下一幀被移除) 的樣式是 opacity 是 1,執行到第二幀的時候加上fade-leave-to(在離開過渡被觸發之后下一幀生效 ,與此同時 fade-leave 被刪除),此時opacity 是 0,既然發生了屬性的變化,transition就會監聽到,從而形成動畫。
這樣顯示和隱藏就形成了一個完整的動畫。
原理是當你通過點擊事件改變css屬性,比如opacity時,transition會檢測到這個變化,從而形成動畫。
CSS 動畫原理同 CSS 過渡類似,區別是在動畫中 v-enter 類名在節點插入 DOM 后不會立即刪除,而是在 animationend 事件觸發時刪除。
<style>
.bounce-enter-active {
animation: bounce-in 3s;
}
.bounce-leave-active {
animation: bounce-in 3s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
</style>
<div id="demo">
<button @click="show = !show">Toggle show</button>
<transition name="bounce">
<p v-if="show">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
facilisis enim libero, at lacinia diam fermentum id. Pellentesque
habitant morbi tristique senectus et netus.
</p>
</transition>
</div>當我們點擊改變show為false時,會在 P 標簽上添加.bounce-leave-active這個class,這個類就會執行animation 動畫,當動畫執行完成后刪除.bounce-leave-active。
<!DOCTYPE html> <html> <head> ... <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script> </head> <body> <div id="demo"> <button @click="show = !show"> Toggle </button> <transition v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:leave="leave" v-bind:css="false" > <p v-if="show"> Demo </p> </transition> </div> <script> new Vue({ el: '#demo', data: { show: true }, methods: { beforeEnter: function(el) { el.style.opacity = 0 el.style.transformOrigin = 'left' }, enter: function(el, done) { Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 1000 }) Velocity(el, { fontSize: '1em' }, { complete: done }) }, leave: function(el, done) { Velocity( el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 } ) Velocity( el, { rotateZ: '45deg', translateY: '30px', translateX: '30px', opacity: 0 }, { complete: done } ) } } }) </script> </body> </html>
before-enter: 是指在動畫之前執行的函數;
enter: 就是整個動畫的過程, 執行完后要加一個done(),來告訴vue已經執行完畢;當只用 JavaScript 過渡的時候,在 enter 和 leave 中必須使用 done 進行回調。否則,它們將被同步調用,過渡會立即完成。
after-enter: 動畫結束之后執行;
Javascript 鉤子動畫一般用在比較復雜的動畫上,而不是簡單的過渡動畫。后面我們會用很大的篇幅通過幾個例子來說明用 Javascript 鉤子動畫是如何完成復雜動畫的。
因為CSS過渡動畫需要有個觸發條件,比如opacity必須有一個變化,如果沒有變化就不會觸發。那么,可以通過 appear attribute 設置節點在初始渲染的過渡:
<transition appear> <!-- ... --> </transition>
CSS 動畫(animation)則不需要觸發條件。
一旦涉及到多個元素的過渡,那么就會出現舊元素和新元素進出的先后問題。<transition> 的默認行為是進入和離開同時發生,但是這樣就會產生一些不協調的效果,所以 Vue 提供了過渡模式:
in-out:新元素先進行過渡,完成之后當前元素過渡離開(in-out 模式不是經常用到,但對于一些稍微不同的過渡效果還是有用的)。
out-in:當前元素先進行過渡,完成之后新元素過渡進入(這個用的比較多)。
<transition name="fade" mode="out-in"> <!-- ... the buttons ... --> </transition>
但是這兩個模式并不能完全滿足實際需要,實際上我們可以定制我們要想的先后效果,比如后臺管理系統中有一個面包屑導航欄,當改變路由的時候需要更改面包屑里面的內容,那么這個更改的動畫可以讓舊的元素向左滑出,新的元素從右邊滑入。
<div id="demo">
<div>
<button @click="show = !show">
Toggle
</button>
</div>
<transition name="fade">
// 一定要設置key
<div class="cls" v-if="show" key="1">
if Demo
</div>
<div class="cls" v-else key="2">else demo</div>
</transition>
</div>
<style>
.cls {
display: inline-block;
}
.fade-enter-active,
.fade-leave-active {
transition: all 1s;
// 這個定位設置很關鍵
position: absolute;
}
.fade-enter {
opacity: 0;
transform: translateX(30px);
}
.fade-leave-to {
opacity: 0;
transform: translateX(-30px);
}
</style>當有相同標簽名的元素切換時,需要通過 key attribute 設置唯一的值來標記以讓 Vue 區分它們,否則 Vue 為了效率只會替換相同標簽內部的內容。即使在技術上沒有必要,給在 transition 組件中的多個元素設置 key 是一個更好的實踐。
多個組件的過渡簡單很多 - 我們不需要使用 key attribute。相反,我們只需要使用動態組件:
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
}
})
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to {
opacity: 0;
}上面講的動畫都是針對單個節點,或者同一時間渲染多個節點中的一個,那么怎么同時渲染整個列表,比如使用 v-for?
在這種場景中,使用 <transition-group> 組件,這個組件的幾個特點:
不同于 <transition>,它會以一個真實元素呈現:默認為一個 <span>。你也可以通過 tag attribute 更換為其他元素。
過渡模式不可用,因為我們不再相互切換特有的元素。
內部元素總是需要提供唯一的 key attribute 值。
CSS 過渡的類將會應用在內部的元素中,而不是這個組/容器本身。
后面我們會通過一個例子演示如何使用<transition-group> 。
在后臺管理系統中,當路由變化時,對應的組件內容也會發生變化,當在變化時加上一個動畫,讓整個頁面效果更加自然。
<transition name="fade-transform" mode="out-in">
// 這里加了key
<router-view :key="key">
</transition>
computed: {
key() {
return this.$route.path
}
}
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px)
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px)
}在 H5 頁面開發中,一個常用的功能是點擊一個按鈕,隱藏的內容從屏幕底部彈出,同時彈出的時候有個動畫效果,一般是緩慢上升。
<div id="list-demo">
<transition name="list-fade">
// 外面一層遮罩
<div class="playlist" v-show="showFlag" @click="hide">
// 這里面才是內容
<div class="list-wrapper"></div>
</div>
</transition>
<div @click="show" class="add">
Add
</div>
</div>
<style>
.add {
position: fixed;
bottom: 0;
left: 0;
text-align: center;
line-height: 40px;
}
.playlist {
position: fixed;
z-index: 200;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.3);
}
.list-wrapper {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 400px;
background-color: #333;
}
// 針對最外面一層的遮罩
.list-fade-enter-active,
.list-fade-leave-active {
transition: opacity 0.3s;
}
// 針對類為list-wrapper的內容
.list-fade-enter-active .list-wrapper,
.list-fade-leave-active .list-wrapper {
transition: all 0.3s;
}
.list-fade-enter,
.list-fade-leave-to {
opacity: 0;
}
// 最開始內容是隱藏的,所以translate3d(0, 100%, 0)
.list-fade-enter .list-wrapper,
.list-fade-leave-to .list-wrapper {
transform: translate3d(0, 100%, 0);
}
</style>這個動畫有兩層,一層是最外層的內容,另一層是最里面部分的內容
在 H5 頁面開發中,如果在一個列表頁中刪除其中一個子項,要求有一個刪除動畫效果。
<div id="list-demo">
<transition-group ref="list" name="list" tag="ul">
<li :key="item.id" class="item" v-for="(item, index) in arr">
<span class="text" v-html="item.name"></span>
<span class="delete" @click="deleteOne(item, index)">
delete
</span>
</li>
</transition-group>
</div>
.item {
height: 40px;
}
.list-enter-active,
.list-leave-active {
transition: all 0.1s
}
.list-enter,
.list-leave-to {
height: 0
}一般復雜的動畫,并不能用簡單的 css 過渡或者 css 動畫能實現的,需要使用 javascript鉤子動畫實現。
針對上面圖片的動畫進行拆解:
當從底部談起的時候,頁面頂部(向下的箭頭和歌曲名稱部分)從上往下滑入,同時有個回彈的效果,同時,底部(播放進度條的部分)從下往上滑入,也有一個回彈的效果。
左下角旋轉的圓有兩個動畫效果,一個是從小變大,一個是從左下角滑動到中心部分
圓的旋轉動畫
代碼結構:
<div> <transition name="normal" @enter="enter" @after-enter="afterEnter" @leave="leave" @after-leave="afterLeave" > <div class="normal-player" v-show="fullScreen"> <div class="top"> // 頂部區域... </div> <div class="middle" @touchstart.prevent="middleTouchStart" @touchmove.prevent="middleTouchMove" @touchend.prevent="middleTouchEnd" > // 中間區域... </div> <div class="bottom"> // 底部區域... </div> </div> </transition> <transition name="mini"> <div class="mini-player" v-show="!fullScreen" @click="open"> // 內容區域... </div> </transition> </div>
實現第一個動畫效果
// 這是stylus的寫法 .normal-enter-active, .normal-leave-active transition: all 0.4s .top, .bottom // 通過這個白塞爾曲線,使得動畫有個回彈的效果 transition: all 0.4s cubic-bezier(0.86, 0.18, 0.82, 1.32) .normal-enter, .normal-leave-to opacity: 0 .top // 從上往下滑入 transform: translate3d(0, -100px, 0) .bottom // 從下往上滑入 transform: translate3d(0, 100px, 0)
通過第一章節部分的學習,看懂這段動畫代碼應該不難。
實現第二個動畫效果
要實現這個動畫效果,必須要計算左下角的圓到中心部分的圓的 x 軸和 y 軸方向上的距離,因為H5頁面在不同的手機屏幕下,這個距離是不同的,所以一開始就不能寫死,只能通過 javascript 去動態的獲取。
// 計算從小圓中心到大圓中心的距離以及縮放比例
_getPosAndScale() {
const targetWidth = 40
const paddingLeft = 40
const paddingBottom = 30
const paddingTop = 80
const width = window.innerWidth * 0.8
const scale = targetWidth / width
const x = -(window.innerWidth / 2 - paddingLeft)
const y = window.innerHeight - paddingTop - width / 2 - paddingBottom
return {
x,
y,
scale
}
}這段代碼細節可以不用看,只需要知道它是計算左下角小圓的原心到中心部分圓的原心的距離(x, y),以及根據圓的直徑獲取放大縮小倍數(scale)。
// 這個庫可以讓我們使用js來創建一個keyframe的動畫,為什么要用js來生成呢?這是因為有些變化的屬性需要動態的計算,而不是一開始就定好了
import animations from 'create-keyframe-animation'
// 動畫鉤子
// done:當動畫執行完后執行done函數,然后跳到afterEnter鉤子函數
enter(el, done) {
const { x, y, scale } = this._getPosAndScale()
// 對于大圓來說,進入的時機就是從小圓到小圓
let animation = {
0: {
// 一開始大圓相對于小圓的位置,所以x為負數,y為整數
transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`
},
// scale: 1.1 這樣圓就有個放大后變回原樣的效果
60: {
transform: 'translate3d(0, 0, 0) scale(1.1)'
},
100: {
transform: 'translate3d(0, 0, 0) scale(1)'
}
}
// 設置animation
animations.registerAnimation({
name: 'move',
animation,
presets: {
duration: 400,
easing: 'linear'
}
})
// 往dom上加上這個animation,并執行動畫
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})`
// 這樣寫的目的是如果沒有監聽到動畫結束的事件,那么我們自己就寫一個定時器,400ms后執行done函數
const timer = setTimeout(done, 400)
// 監聽動畫結束
this.$refs.cdWrapper.addEventListener('transitionend', () => {
clearTimeout(timer)
done()
})
}這段代碼的效果就是大圓的動畫效果。當點擊小圓的時候,大圓開始進入,進入的過程就是動畫的過程。當點擊向下的箭頭,大圓將消失,消失的過程就是大圓退出的動畫過程。
雖然有點復雜,但是也不難看懂,以后我們對于復雜動畫可以模仿上面的代碼。
那小圓的動畫呢?它非常簡單,就是一個顯示隱藏的動畫:
.mini-enter-active, .mini-leave-active transition: all 0.4s .mini-enter, .mini-leave-to opacity: 0
至此,就完成小圓和大圓的聯動動畫,整體效果還是很驚艷的。
實現第三個動畫
// 模板部分
<div class="cd" ref="imageWrapper">
<img
ref="image"
:class="cdCls"
class="image"
:src="currentSong.image"
/>
</div>
// 邏輯部分
// 通過事件來控制playing的值,然后改變img標簽的class,從而是動畫停止和展示
cdCls() {
return this.playing ? 'play' : 'play pause'
}
// css部分
.play
animation: rotate 20s linear infinite
.pause
animation-play-state: paused
@keyframes rotate
0%
transform: rotate(0)
100%
transform: rotate(360deg)首先對動畫進行拆解:
當點擊 + 的時候,有一個小圓從右側向xiang左側滾動出來到指定的位置,既有滾動的效果,也有從右往左移動的效果。同時,當點擊 - 的時候,小圓從左側滾動到右側并消失。
當點擊 + 的時候,會出現一個小球,這個小球會從點擊的位置做一個拋物線運動軌跡到左下角的購物車中。同時,當我連續點擊的時候,會出現多個小球同時做拋物線運行出現在屏幕中。
實現第一個動畫
通過上面的學習,對這一個動畫的實現應該不難。代碼如下:
<div class="cartcontrol">
// 小圓 -
<transition name="move">
<div class="cart-decrease" v-show="food.count>0" @click.stop="decrease">
<span class="inner"> - </span>
</div>
</transition>
<div class="cart-count" v-show="food.count>0">{{food.count}}</div>
// 小圓 +
<div class="cart-add" @click.stop="add"> + </div>
</div>
.move-enter-active, &.move-leave-active
transition: all 0.4s linear
.move-enter, &.move-leave-active
// 外層動畫是從右往左運動
opacity: 0
transform: translate3d(24px, 0, 0)
.inner
// 內層動畫是旋轉180°
transform: rotate(180deg)實現第二個動畫
創建小球,因為這個動畫就是對小球的動畫
<div class="ball-container">
<div v-for="(ball,index) in balls" :key="index">
<transition
@before-enter="beforeDrop"
@enter="dropping"
@after-enter="afterDrop">
<div class="ball" v-show="ball.show">
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
</div>
<script>
function createBalls() {
let balls = []
for (let i = 0; i < BALL_LEN; i++) {
balls.push({ show: false })
}
return balls
}
data() {
return {
balls: createBalls()
}
},
</script>這里創建是10個小球,小球開始的狀態都是隱藏的。
點擊 + 按鈕的時候,觸發一個小球彈出
// 點擊加號調用這個函數,同時把加號的dom傳遞,這樣就能知道小球運動的起點位置
onAdd(target) {
// shopCart就是圖中底部組件,執行drop函數
this.$refs.shopCart.drop(target)
},
// 把加號對應的dom傳入,并綁定到小球el屬性上
drop(el) {
for (let i = 0; i < this.balls.length; i++) {
const ball = this.balls[i]
if (!ball.show) {
ball.show = true
ball.el = el
// dropBalls表示正在下落的小球,因為當快速點擊時,會觸發多個小球下落
this.dropBalls.push(ball)
return
}
}
},因為小球的ball.show為true,那么就會觸發對應的動畫鉤子函數,首先觸發beforeDrop:
beforeDrop(el) {
// 取出最后一個小球
const ball = this.dropBalls[this.dropBalls.length - 1]
// 獲取小球的起點位置,就是在哪個地方點擊的加號按鈕
const rect = ball.el.getBoundingClientRect()
const x = rect.left - 32
const y = -(window.innerHeight - rect.top - 22)
// 設置小球的位置,把小球設置到點擊加號按鈕的那個地方
el.style.display = ''
// 外層動畫,向下
el.style.transform = el.style.webkitTransform = `translate3d(0,${y}px,0)`
const inner = el.getElementsByClassName(innerClsHook)[0]
// 內層動畫向左
inner.style.transform = inner.style.webkitTransform = `translate3d(${x}px,0,0)`
}接著執行enter事件函數dropping:
dropping(el, done) {
// 觸發瀏覽器重繪,把beforeDrop事件中設置的小球位置從底部位置移動到點擊加號的位置,這樣小球就會從上面往下面落下
this._reflow = document.body.offsetHeight
// 設置小球落下的終點位置
el.style.transform = el.style.webkitTransform = `translate3d(0,0,0)`
const inner = el.getElementsByClassName(innerClsHook)[0]
inner.style.transform = inner.style.webkitTransform = `translate3d(0,0,0)`
// 監聽動畫結束
el.addEventListener('transitionend', done)
}最后執行after-enter事件函數afterDrop:
afterDrop(el) {
// 取出第一個小球,設置屬性show為false,同時要設置el.style.display = 'none'
const ball = this.dropBalls.shift()
if (ball) {
ball.show = false
el.style.display = 'none'
}
}我們來梳理下流程:
點擊加號位置,觸發drop函數,然后把一個隱藏的小球設置為顯示狀態,存儲在dropBalls中,因為用戶可以快速點擊,所以dropBalls里面可能有多個小球。
當把小球狀態設置為顯示狀態,就會觸發動畫鉤子before-enter,enter,after-enter這三個鉤子。
before-enter的作用是把小球的位置設置到點擊的位置,同時利用offsetHeight觸發瀏覽器重繪,這樣就會把小球的位置放在點擊的位置。
enter的作用就是讓小球從點擊的位置落下,動畫分為兩層,一層是向下,另一層是向左,當兩者結合就構成了一個從右上角到左下角斜線的動畫軌跡,但是一個斜的直線動畫軌跡比較丑,這里就使用了時間函數cubic-bezier來改變動畫軌跡,使其有一個先向上運動,最后向下運動的拋物線軌跡動畫。
.ball position: fixed left: 32px bottom: 22px z-index: 200 transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41) .inner width: 16px height: 16px border-radius: 50% background: $color-blue transition: all 0.4s linear
after-enter當小球到達目的后,需要把小球隱藏起來,所以取出第一個小球,然后設置show的屬性為false,這樣小球就隱藏起來,等待下一次動畫執行。
所以函數的執行順序是:drop -> before-enter -> enter -> after-enter -> drop -> before-enter -> enter -> after-enter...,這樣就形成了在頁面中同時出現多個小球的動畫。
注意:當我們設置
show的屬性為false就可以了,但是代碼中同時也設置了el.style.display = 'none',如果不設置這個小球消失有一個延遲。
以上就是關于“Vue動畫實例代碼分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。