前端(vue)入門到精通課程:進入學習
在開發 ToC 專案中,需要用到很多動畫,比如常見的路由動畫,彈出框動畫以及一些業務動畫等,本文對學過的動畫做了一個總結,方便日後複習,如果對您有所幫助不甚榮幸。
首先我們來回顧下 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 值。後面我們會通過一個例子演示如何使用<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 動畫到此就結束了,動畫其實是一個比較難的功能,特別是複雜動畫。通過上面兩個複雜動畫可以給我們做一個借鑑,相信你也能寫出自己想要的動畫效果。
(學習視訊分享:、)
以上就是範例詳解,帶你玩轉 Vue 動畫的詳細內容,更多請關注TW511.COM其它相關文章!