近期在開發小程序中,接觸最多的就是 canvas 了,期間又因為兼容性的問題,又經歷了底層 API 的新舊版的替換,踩的坑可謂令人印象深刻。小程序(微信)的 canvas 與 HTML 標準的 canvas 有較大區別,就連小程序本身的 canvas 底層 API 都有兩個大版本的區別(其實遠古時期還有一個版本,但年代過于久遠就不做考究了)。目前現存的兩個版本的區別在于是否支持「同層渲染」。
小程序的內容大多是渲染在 WebView 上的,如果把 WebView 看成單獨的一層,那么由系統自帶的這些原生組件則位于另一個更高的層級(如 canvas、video)。兩個層級是完全獨立的,因此無法簡單地通過使用 z-index 控制原生組件和非原生組件之間的相對層級。想要在原生組件之上只能用 cover-view 和 cover-image 來實現。但 cover-view 和 cover-image 支持的 css 樣式是在很有限,而且經過實踐來看,cover-view 在安卓部分機器上性能真的很差。
「同層渲染」則是將原生組件直接渲染到 WebView 層級上,就可以通過簡單的 z-index 來控制層級,而且支持的 css 非常豐富,麻麻再也不用擔心我碰到的層級問題了!是不是看起來很美好?然而現實非常殘酷。
首先,根據小程序官方的文章來看,幾乎是重構了整個「原生組件」,使用方式和支持的特性與之前的區別都非常大,非常類似標準的 canvas API,甚至官方聲稱「支持標準 canvas 的大部分屬性方法」。但是根據我的實際項目經驗來看,新版 canvas API 僅僅只是在 iOS 上表現良好,在部分安卓機器上會出現許多怪異行為。一個簡單的例子是繪制多個相同的形狀時,畫筆似乎會出現在「飄忽不定」的位置上,導致繪制最終結果無法預測。另外很讓人頭疼的一個地方在于 drawImage 方法上。舊版 API drawImage 第一個參數是圖片路徑,本地路徑或網絡路徑皆可,但新版 API drawImage 第一個參數必須是圖片實例,由于小程序無法獲取 DOM 元素,只能用官方提供的 createImage 方法創建圖片實例,在其 onLoad 回調中再次調用 drawImage,才能實現原先簡單的方法。諸如此類。但這些都是可以克服的,最終導致我們放棄的原因是其在部分安卓機器上的「不確定性」,如果在「新特性」和「兼容性」上做選擇,我想我還是堅持選擇「兼容性」吧。就好像「優雅降級」和「漸進增強」,我更傾向于后者。
我相信大多數做過小程序 canvas 相關都有層級的煩惱。既然無法使用新版 API 來實現,那問題總要解決,最終我們想出了一套在舊版 API 也可以實現類似「同層渲染」的效果。目前需要「同層渲染」的場景基本上都是需要在 canvas 上彈層,所以在覆蓋 canvas 的時候不會同時操作 canvas,因為可以利用canvasToTempFilePath 可以臨時將 canvas 轉成圖片,然后隱藏 canvas,顯示 tempImage 即可。
新版 canvas API 并不是一無是處,有一個很大的變化在于它不再使用物理尺寸來繪制,使用的是實際尺寸。這就會使得使用新版 API 繪制的結果比原來高清許多,這算是為數不多的優點吧。另外新版 canvas API 在 iOS 上表現還是很不錯的。希望未來官方可以讓新版 canvas API 兼容性更優秀,讓開發者早日擺脫這些臨時方案。