css技巧:逐幀動畫抖動如何解決?
一個抖動的例子
做一個8幀的逐幀動畫,每幀的尺寸為:360x540。
image.png
觀察在主流(手機)解析度下的播放情況:
|iPhone 6(375x667)
iPhone 6+ (414x736)
iPhone 5 (320x568)
Android (360x640)
四種解析度下,可以看到除了iP6 其它的三種解析度都發生了抖動。 (iP6 不抖動的原因是適配方案是基本於 iP6 的解析度訂製的。)
分析抖動
影象由終端(螢幕)顯示,而終端則是一個個光點(物理畫素)組成的矩陣,換句話說圖片也一組光點矩陣。為了方便描述,筆者假設終端上的一個光點代表css中的1px。
以下是一張 9px * 3px
的sprite:
9px * 3px
每幀的尺寸為 3px * 3px
,逐幀的取位過程如下:
9px * 3px
把 sprite 的 background-size 的寬度取一半,那麼終端會怎麼處理?
9 / 2 = 4.5
終端的光點都是以自然數的形式出現的,這裡需要做取整處理。取整一般是三種方式: round/ceil/floor
。假設是 round ,那麼 background-size: 5px
,sprite 會是以下三種的一個:
image.png
理論上,5 / 3 = 1.666...。但實際上光點取整後,三個幀的寬度都不可能等於 1.666...,而是有一個幀的寬度降級為 1px(虧),另外兩個寬度升級為 2px(盈),筆者把這個現象稱作「盈虧互補」。
再看一下盈虧互補後,逐幀的取位過程:
image.png
。
個人總結抖動的原因是: sprite在尺寸縮放後,幀與幀之間的盈虧互補現象導致動畫抖動
附註:1px 由幾個光點表示是由以終端的 dpr 決定
解決方案
「盈虧互補」也可以說是「盈虧不一致」,如果尺寸在縮放後「盈虧一致」那麼抖動現象可以解決。
解決構想一
根據「盈虧一致」設計了「解決構想一」:
image.png
根據上圖,其實很容易就聯想到一個簡單的方案: 不用雪碧圖(即一幀對應一張圖片) 。
這個方案確實是可以解決抖問題,不過筆者並不推薦使用它,因為它有兩個負面的東西:
- KB變大與請求數增多
- 多餘的 animation 程式碼
這個方案很簡單,這裡就不贅述了。
解決構想二
把逐幀取位與影象縮放拆分成兩個獨立的過程,就是我的「解決構想二」:
image.png
實現「構想二」,我首先想到的是使用 transform: scale(),於是整理了一個實現方案A:
image.png
這個實現方案A存在明顯的缺陷:scale 的值需要寫很多斷點程式碼。於是筆者結全一段 js 程式碼來改善這個實現方案B:
css:
image.png
javascript:
image.png
通過改善後的方案 CSS 的斷點沒了,感覺是不錯了,不過我覺得這個方案不是個純粹的構建方案。
我們知道 <img> 是可以根據指定的尺寸自適應縮放尺寸的,如果逐幀動畫也能與 <img> 自適應縮放,那就可以從純構建角度實現「構想二」。
SVG剛好可以解決難題!!!SVG 的表現與 <img> 類似同時可以做動畫。以下是我的實現方案C。
html:
image.png
css:
image.png
方案C的改良
實現方案C很好地解決了方案A和方案B的缺陷,不過方案C也有它的問題: 不利於自動化工具去處理圖片 。
自動化工具一般是怎麼處理圖片的?
自動化工具一般是掃描 CSS 檔案找出所有的 url(...)
語句,然後再處理這些語句指向的圖片檔案。
如果 <image>
可以改用 CSS 的 background-image
就可以解決這個問題,不過 SVG
不支援 CSS 的 background-image
。但是, SVG
有一個擴充套件標籤: foreignObject
,它允許向 <svg></svg>
插入 html
程式碼。在使用它前,先看一下它的相容情況:
caniuse
iOS 與 Android 4.3 一片草綠相容情況算是良好,實機測試騰訊 X5 核心的瀏覽器相容仍舊良好。以下是改良後的方案。
html:
image.png
css:
image.png
感謝閱讀完本文章的讀者。本文是個人觀點,希望能幫助到有相關問題的朋友,如果本文有不妥之處請不吝賜教。
我是一名前端工程師,自己整理了一份2019最全面前端學習資料,從最基礎的HTML+CSS+JS到移動端HTML5到各種框架都有整理,送給每一位前端小夥伴,這裡是小白聚集地,歡迎初學和進階中的小夥伴從最基礎的HTML+CSS+JS到移動端HTML5到各種框架都有整理,送給每一位前端小夥伴,這裡是小白聚集地,歡迎初學和進階中的小夥伴web前端資料分享圈