第一次在segmentfault上發文章 :),歡迎評論指正。原文最初發表在 https://github.com/WarpPrism/...javascript
從控制角度分,前端動畫分爲兩種:css
JS動畫的原理是經過setTimeout
setInterval
或requestAnimationFrame
方法繪製動畫幀(render),從而動態地改變網頁中圖形的顯示屬性(如DOM樣式,canvas位圖數據,SVG對象屬性等),進而達到動畫的目的。前端
多數狀況下,應 首先選用 requestAnimationFrame
方法(RAF),由於RAF的原理是會在瀏覽器下一次重繪以前更新動畫,即它的刷新頻率和瀏覽器自身的刷新頻率保持一致(通常爲60Hz),從而確保了性能。另外RAF在瀏覽器切入後臺時會暫停執行,也能夠提高性能和電池壽命。(來自MDN)java
// requestAnimationFrame Demo let i = 0 let render = () { if (i >= frame.length) i = 0 let currentFrame = frame[i] drawFrame(currentFrame) i++ requestAnimationFrame(render) } requestAnimationFrame(render)
下面代碼是一個用js + canvas 實現幀動畫的一個例子,能夠幫你更好的理解js動畫原理:git
/** * 基於canvas的幀動畫庫 * 最近修改日期:2018-06-22 */ import { IsArray } from 'Utils' class FrameAnim { constructor ({ frames, canvas, fps, useRAF }) { this._init({ frames, canvas, fps, useRAF }) } /** * 實例初始化 * @param options -> * @param {Array} frames image對象數組 * @param {Object} canvas canvas dom 對象 * @param {Number} fps 幀率 * @param {Boolean} useRAF 是否使用requestAnimationFrame方法 */ _init ({ frames, canvas, fps, useRAF }) { this.frames = [] if (IsArray(frames)) { this.frames = frames } this.canvas = canvas this.fps = fps || 60 this.useRAF = useRAF || false this.ctx = this.canvas.getContext('2d') // 繪圖上下文 this.cwidth = this.canvas.width this.cheight = this.canvas.height this.animTimer = null // 動畫定時器 this.currentIndex = 0 // 當前幀 this.stopLoop = false // 中止循環播放 } _play (frameSections, fromIndex = 0) { return new Promise((resolve, reject) => { this.currentIndex = fromIndex || 0 if (this.useRAF) { let render = () => { this.ctx.clearRect(0, 0, this.cwidth, this.cheight) let currentFrame = frameSections[this.currentIndex] this.ctx.drawImage(currentFrame, 0, 0, currentFrame.width, currentFrame.height) this.currentIndex++ if (this.currentIndex <= frameSections.length - 1) { requestAnimationFrame(render) } else { this._stopPlay() resolve({finish: true}) } } this.animTimer = requestAnimationFrame(render) } else { this.animTimer = setInterval(() => { if (this.currentIndex > frameSections.length - 1) { this._stopPlay() resolve({finish: true}) return } this.ctx.clearRect(0, 0, this.cwidth, this.cheight) let currentFrame = frameSections[this.currentIndex] this.ctx.drawImage(currentFrame, 0, 0, currentFrame.width, currentFrame.height) this.currentIndex++ }, 1000 / this.fps) } }) } _stopPlay () { if (this.useRAF) { cancelAnimationFrame(this.animTimer) this.animTimer = null } else { clearInterval(this.animTimer) this.animTimer = null } } stopAllFrameAnimation () { this.stopLoop = true this._stopPlay() } /** * 順序播放 * @param {Array} frameSections 動畫幀片斷 */ linearPlay (frameSections = this.frames) { return this._play(frameSections, this.currentIndex) } /** * 順序循環播放 * @param {Array} frameSections 動畫幀片斷 */ loopPlay (frameSections = this.frames) { this._play(frameSections, this.currentIndex).then((res) => { if (!this.stopLoop) { this.currentIndex = 0 this.loopPlay(frameSections, this.currentIndex) } }) } // 倒序播放 reversePlay (frameSections = this.frames) { frameSections.reverse() return this.linearPlay(frameSections) } // 倒序循環播放 reverseLoopPlay (frameSections = this.frames) { frameSections.reverse() this.loopPlay(frameSections) } // 鞦韆式(單擺式)循環播放:即從第一幀播放到最後一幀,再由最後一幀播放到第一幀,如此循環 swingLoopPlay (frameSections = this.frames) { this._play(frameSections, this.currentIndex).then((res) => { if (!this.stopLoop) { this.currentIndex = 0 frameSections.reverse() this.swingLoopPlay(frameSections) } }) } /** * 銷燬資源,需謹慎使用 */ disposeResource () { this.stopAllFrameAnimation() for (let i = 0; i < this.frames.length; i++) { this.frames[i] = null } this.frames = null this.canvas = this.ctx = null } } export default FrameAnim
css動畫的原理是經過transition
屬性或@keyframes/animation
定義元素在動畫中的關鍵幀,以實現漸變式的過渡。github
css動畫有如下特色:web
優勢canvas
缺點segmentfault
<!-- 能夠經過以下代碼強制開啓硬件加速:數組
.anim-cube { z-index: 9999; // 指定z-index,儘量高 } .anim-cube { transform: translateZ(0); /* 或 */ transform: translate3d(0, 0, 0); }
丟幀:瀏覽器繪製某一幀的時長超過了平均時長(幀超時),爲了完成整個動畫不得不丟棄後面的動畫幀,形成丟幀現象。畫面就出現了所謂的閃爍,卡頓。
致使幀超時的緣由有不少,最主要的緣由是layout、paint帶來的性能開銷:
不管是JS動畫,仍是CSS動畫,在操做元素的某些樣式(如height,width,margin,padding),會觸發layout和paint,這樣每一幀就會產生巨大的性能開銷,相反,使用transform屬性則不會,具體哪些屬性能觸發能夠參考CSS Trigers,總之,咱們應儘量使用影響小的屬性(transform,opacity)來作動畫。
若是採用的是基於圖片切換的幀動畫技術,請確保全部圖片預加載完畢,且用cacheImgs數組緩存全部圖片資源到內存中,不然也會出現卡頓現象。
layout: 瀏覽器會對這些元素進行定位和佈局,這一步也叫作reflow或者layout。paint: 瀏覽器繪製這些元素的樣式,顏色,背景,大小及邊框等,這一步也叫作repaint。
composite: 而後瀏覽器會將各層的信息發送給GPU,GPU會將各層合成;顯示在屏幕上。
隨着現代web技術的發展,不管是CSS動畫仍是JS動畫,性能瓶頸愈來愈小,咱們只要選擇適合業務須要的技術,同樣能創做出絲滑般順暢的web動畫。若是實在沒法選擇,看下圖(僅供參考):
通常來講,動畫性能優劣以下所示:
JS+Canvas > CSS + DOM > JS + DOM
這裏是一個動畫技術比較的Codepen Demo
緩動函數的實現可參考Tween.js
前端繪圖技術一般指以HTML5爲表明的(canvas,svg,webgl等)2D、3D圖形繪製技術。它和前端動畫之間沒有包含與被包含的關係,更不能將它們 混爲一談,只有二者的有機結合才能建立出炫酷的UI界面。