canvas基礎及圖片縮放的實現

基礎部分

座標系

畫布座標、螢幕座標的概念

螢幕座標,絕對座標,類似於css中的絕對定位

畫布座標,類似於css中的相對定位,還要考慮縮放

幾何變換:平移、縮放、旋轉

平移,位置移動,形狀、相對位置不變

縮放,位置(相對螢幕座標)和大小都發生變化

旋轉,位置旋轉,形狀、相對位置不變

Canvas 中的所有幾何變換針對的不是繪製的圖形,而是針對畫布本身,也就是說,當移動、縮放、旋轉畫布之後,新的座標系只對新的操作生效

canvas基礎及圖片縮放的實現

參考:

Canvas 幾何變換 - Canvas 基礎教程 - 簡單教程,簡單程式設計

Canvas 平移 translate() - Canvas 基礎教程 - 簡單教程,簡單程式設計

Canvas 縮放 scale() - Canvas 基礎教程 - 簡單教程,簡單程式設計

Canvas 旋轉 rotate() - Canvas 基礎教程 - 簡單教程,簡單程式設計

繪圖步驟

基礎繪圖三步法

獲取canvas物件

獲取上下文環境物件context

開始繪製圖形。

示例程式碼

const canvas = document。getElementById(“canvas”);const context = convas。getContext(“2d”);context。fillRect(100, 100, 50, 50);

通用繪圖步驟

儲存畫布(狀態),context。save();

畫布操作,context。transform(疊加) 或者 context。setTransform(不疊加)

設定樣式,繪製圖形

恢復畫布(狀態),context。restore();

canvas基礎及圖片縮放的實現

圖形變換基本操作

清空畫布,context。clearRect

儲存狀態,context。save

畫布操作,doTransform

getShapeList and forEach

恢復狀態,context。restore

Canvas效能

Canvas的繪製和html的繪製是不一樣的,html的繪製是增量的,當變化時,只會重新繪製變化的部分,沒有變化的部分是不會重新繪製的,但是canvas不一樣,每次都是全量繪製的,如果一個canvas裡有很多圖形,當改變一個圖形時,需要重新繪製所有圖形才可以(當然,可以用clearRect擦除部分割槽域,但一般很少這麼用)。

瞭解canvas的繪製規則之後,就很容易發現效能問題,如果canvas上繪製了大量的圖形(成千上萬個),每次重繪就需要很長的時間,如果重繪的頻率很高,那麼就會有效能問題

canvas基礎及圖片縮放的實現

那麼如何解決這個問題呢,目前有以下幾種方案

使用圖層

使用臨時圖層

使用webworker或wasm

使用webgl

使用圖層

圖層的概念來自於PS,每一個圖層都是一個canvas,既然在一個canvas上繪製太多圖形會有效能問題,那麼就分幾個圖層,每次僅重新繪製其中一個圖層,每個圖層的圖形都不會很多,那麼即使重繪的頻率很高,也不會有效能問題。圖層的概念圖如下:

canvas基礎及圖片縮放的實現

這裡用背景顏色只是示意,實際上圖層都是透明

程式碼實現

用一個父元素作為容器,把所有的元素設定成一樣的寬高並放在裡面重疊。

使用臨時圖層

繪製是很耗效能的,如果每次都清空畫布然後重新畫一次,那麼效能會消耗很大(即使分了幾個圖層),我們應區分“變”與“不變”的部分,只對“變”的部分重新渲染,“不變”的部分不渲染,將經常變化的部分抽離到臨時圖層,這樣僅需要渲染臨時圖層,臨時圖層有幾種實現思路,一種是使用操作圖層(俗稱高效能圖層),一種是使用隱藏圖層(不繪製到介面上的)

高效能圖層

一般高頻(實時響應滑鼠、鍵盤等事件)的操作會放在高效能圖層,等操作完成之後,再將最終結果儲存到其它圖層,比如繪製、拖拽、縮放一個(或一批)shape

canvas基礎及圖片縮放的實現

隱藏圖層

有些圖層是不用給使用者看的,這些canvas僅存在於記憶體中,不會插入html的dom中,用完就銷燬,比如常見的canvas to image。

還有一種實現方式是離屏渲染(OffscreenCanvas),先在一個offCanvas操作,然後再將結果渲染到介面上(有點像虛擬dom操作),一般會結合webworker或webassembly

const canvas = document。createElement(“canvas”);const context = canvas。getContext(“2d”);// 繪製圖片,或其它操作context。drawImage();// 轉成base64圖片convas。toDataUrl();

canvas基礎及圖片縮放的實現

使用webworker或wasm

影響canvas效能的除了繪製頻率,還有一個重要的是畫素點操作,一般影象處理會涉及到大量的畫素點操作,如果放在主執行緒計算,那麼會卡住其它操作,造成頁面卡頓,特別影響使用者體驗,這些涉及大量計算的一般會單獨開個執行緒來操作,而在瀏覽器中有這個能力的就只有webworker了。

有了webworker可能還不夠,因為始終是在js上執行,js執行效率天生就比其它語言慢,所以一般的會使用webassembly,執行效率比js快很多,而且還能用到更豐富的影象處理庫

canvas基礎及圖片縮放的實現

使用webgl

如果還有更高的效能要求,那麼普通的2d canvas可能就無法滿足了,這個時候可以使用webgl,效能更高(當然學習成本也更高),再結合wasm,就可以有無限想象力了,鼎鼎大名的figma就是用webgl + wasm(rust)實現的,另外google doc線上文件也使用了webgl,飛書文件將來也會替換成wegbl,基於瀏覽器的渲染始終有諸多限制,一般有能力的都會實現自己的渲染引擎。

業務中圖片縮放的實現設計

假設canvas大小為(867,350)

圖片的大小為(768,576)

將上面這張圖片放到canvas中,圖片貼邊處理,也即圖片太大就縮小,圖片太小就放大。那麼我們如何實現這種效果呢?

canvas基礎及圖片縮放的實現

總結一下,總共分為幾步:

計算畫布大小和圖片大小

計算如果將圖片以原圖大小放入畫布中心,左上角的座標

將畫布座標移動到圖片中心點

將畫布放大或縮小(scale=Math。min(畫布寬度/圖片寬度,畫布高度/圖片高度))

重新移動畫布座標到左上角

繪製圖片(或圖形)

canvas的執行細節如下:

canvas基礎及圖片縮放的實現