原文連接: Smooth as Butter: Achieving 60 FPS Animations with CSS3css
在移動端使用動畫元素是很容易的.html
若是你能遵循咱們的這裏的提示, 在移動端適當的使用動畫元素, 能夠變得更加容易.css3
在這些天裏, 每一個人都不會適當的使用CSS3動畫. 有些最佳的實踐方法, 一直被忽視. 被忽視的主要緣由是人們不能真正的理解,這些實踐存在的真正緣由, 以及爲什麼能獲得大力支持.web
如今設備的規格很是多, 若是你不能經過仔細思考優化你的代碼, 使用順暢的動畫, 你會給大部分的人帶來很差的體驗.chrome
記住: 儘管一些高端, 旗艦設備不斷推進發展, 但這世界上大部分的設備, 和高端機相比差太多, 就像一個帶着LCD的算盤.瀏覽器
咱們會告訴你如何使用CSS3, 發揮它的最大功效. 爲了達成這一點, 咱們首先須要學習一點東西.app
渲染和使用元素的時候, 瀏覽器會作些什麼? 這是很是簡單的一條時間線, 被稱之爲關鍵渲染路徑(Critical Rendering Path)chrome-devtools
咱們應該專一於改變影響合成步驟的屬性, 而不是增長上一次佈局的壓力.函數
瀏覽器開始計算樣式, 以便應用到元素上 - 從新計算樣式佈局
下一個步驟中, 瀏覽器開始計算模型和各個元素的位置 - 佈局. 這是瀏覽器設置在頁面中設置例如寬和高屬性的地方, 也包括了外邊距, 或者是實例的左/高/右/下.
瀏覽器開始將每個元素在像素級別填充到圖層中. 咱們使用的這些屬性包括: box-shadow, border-radius, color, background-color, 等等.
這是你須要操做的地方, 由於這是瀏覽器開始在屏幕繪製全部的圖層.
如今瀏覽器可以產生動畫的有四個屬性, ,最好使用tansform, 和opacity屬性.
經過上面這些思考, 讓咱們開始幹吧.
咱們經過一個HTML開始. 咱們會建立一個很是簡單的模型, 而後在.layout
中放置一個app-menu
.
<div class="layout"> <div class="app-menu"></div> <div class="header"> <div class="menu-icon"></div> </div> </div>
.app-menu { transition: left 300ms linear; left: -60%; } .open .app-menu { left: 0; }
看到被咱們改變的屬性了嗎? 你應該避免使用transition
上面的top/right/bottom/left
屬性. 那並不會產生一個流暢的動畫. 由於他們讓瀏覽器一直在建立layout
s, 這會影響到他全部的子組件.
他的結果就像這樣:
這個動畫徹底不暢. 咱們經過使用DevTools Timeline
來看看底層發生了什麼, 這是他的結果:
這清楚的展示了FPS的不規則, 而且性能不好.
"綠條表示FPS. 他上面的條表示動畫在60FPS如何渲染. 下面的條表示低於60FPS. 因此, 理想狀況下, 你但願綠條能在整個時間線保持較高的水平. 紅條也能表示出閃避
jank
(避開了渲染時間?), 因此, 還能夠經過消除紅條, 來表示你性能的進步." 感謝Kayce Basques指出.
Dev Tools
官方文檔: Timeline
已經再也不使用, 下面是使用Performance
這是一個疑問句? 如今我發現, 官方文檔上所說的CRP和這篇文章說的不太同樣, 有空翻譯下官方文檔.
.app-menu { transition: transform 300ms linear; transform: translateX(-100%); } .open .app-menu{ transform: none; }
transform
屬性做用到Composite
合成階段. 這裏告訴咱們, 只要動畫開始, 瀏覽器全部的圖層都渲染完成並準備好了, 因此動畫渲染的時候, 間隔很是小.
在實際中的時間線中展現:
如今的結果變得好一些了, FPS可以進行更多渲染, 而後動畫更加流暢.
和上一次我的測試相比, 省去了layout階段, 就是佈局的時間.
那麼, 讓我把他提升一個等級. 爲了保證動畫運行的順暢, 咱們是用GPU開始渲染動畫.
.app-menu { transition: transform 300ms linear; transform: translateX(-100%); /* transform: translate3d(-100%, 0, 0); */ will-change: transform; } .open .app-menu{ transform: none; }
儘管一些瀏覽器依舊須要使用translateZ()
和translate3d()
做爲備選方案, will-change
CSS will-change
- how to use it, how it works纔是之後的趨勢. 這樣作, 能夠把元素提高到另外一層上, 因此, 瀏覽器不須要考慮佈局的渲染和繪製.
可以看出他的順暢嗎? 渲染路徑會證明這一點.
動畫的FPS是很是連續的, 而且動畫的渲染是很是快速的. 可是依舊有一個紅框在渲染的時候. 那只是在開始的時候, 一個小瓶頸.
記住剛開始時建立的HTML的結構. 讓咱們使用JavaScript在結構中控制一個app-menu
div.
function toggleClassMenu() { var layout = document.querySelector('.layout') if (!layout.classList.contains('app-menu-open')) { layout.classList.add('app-menu-open') } else { layout.classList.remove('app-menu-open') } }
這個問題是: 咱們給layout
這個div添加了類名, 讓瀏覽器再一次計算了樣式, 影響了渲染性能.
首先是, 使用translate3d的效果:
這是使用了will-change
的效果:
一樣, 我也是使用這種控制類名的方式實現的動畫.
若是咱們從視圖層外邊建立一個區域替代以前的作法呢? 一個隔離的區域, 能夠確保影響到的元素, 就是想要進行動畫的.
因此, 咱們使用下面這種HTML結構.
<div class="menu"> <div class="app-menu"></div> </div> <div class="layout"> <div class="header"> <div class="menu-icon"></div> </div> <a href="www.baidu.com">baidu</a> </div>
如今咱們可使用稍微不一樣的方式控制menu的狀態了. 當動畫結束的時候, 咱們使用JavaScript中的transitionend
函數, 刪除還有動畫的類名.
function toggleClassMenu() { myMenu.classList.add("menu--animatable"); if (!myMenu.classList.contains("menu--visible")) { myMenu.classList.add("menu--visible"); } else { myMenu.classList.remove('menu--visible'); } } function OnTransitionEnd() { myMenu.classList.remove("menu--animatable"); } var myMenu = document.querySelector(".menu"); var oppMenu = document.querySelector(".menu-icon"); myMenu.addEventListener("transitionend", OnTransitionEnd, false); // 只在動畫期間添加動畫函數 oppMenu.addEventListener("click", toggleClassMenu, false); myMenu.addEventListener("click", toggleClassMenu, false);
讓咱們所有結合起來, 而後檢查結果.
下面是完整, 可使用CSS3的例子, 每一處都使用了最正確的方式.
body { margin: 0; padding: 0; } .menu { position: fixed; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden; pointer-events: none; /* 這個屬性表示, 即便是上面有一層, 也不影響, 下面元素的使用 */ z-index: 150; } .menu--visible { pointer-events: auto; /* 遮蓋了, 也就不讓用了 */ } .app-menu { background-color: #fff; color: #fff; position: relative; max-width: 400px; width: 90%; height: 100%; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); -webkit-transform: translateX(-103%); transform: translateX(-103%); display: flex; flex-direction: column; will-change: transform; z-index: 160; pointer-events: auto; /* 這是咱們的側邊欄, 打開的時候, 不讓用下面的元素 */ } .menu--visible .app-menu { -webkit-transform: none; transform: none; } .menu--animatable .app-menu { /* 消失的時候, 先慢後快 */ transition: all 130ms ease-in; } .menu--visible.menu--animatable .app-menu { /* 出現的時候, 先快, 後慢 */ transition: all 330ms ease-out; } .menu:after { content: ''; display: block; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.4); opacity: 0; will-change: opacity; pointer-events: none; transition: opacity 0.3s cubic-bezier(0, 0, 0.3, 1); } .menu--visible.menu:after { opacity: 1; pointer-events: auto; } /* aux */ body { margin: 0; } .layout { width: 375px; height: 667px; background-color: #f5f5f5; position: relative; } .header { background-color: #ccc; } .menu-icon { content: "Menu"; color: #fff; background-color: #666; width: 40px; height: 40px; } .app-menu { width: 300px; height: 667px; box-shadow: none; background-color: #ddd; } .menu:after { width: 375px; height: 667px; }
讓咱們看下Timeline展現給咱們的?
看到了嗎? 很是流暢.
發現, 性能提高主要在Event中, 其餘未能看出提高, 並進行了一個名爲Fire Idle Callback
.還須要深刻了解下. 下圖爲實操圖片:
關鍵渲染路徑
究竟是什麼. 文章和官網說的不同.will-change
和pointer-events