記錄canvas使用及常見問題

前言

在具體項目開發時,咱們常常會使用canvas繪製圖表、豐富的圖形、動畫交互等應用場景。
本篇文章總結概括一下本身在canvas開發的認知和可能遇到的常見問題。
至於基礎知識和常見API使用,你們自行學習,這裏不予複述。javascript

重要概念

HTML5 <canvas> 元素用於圖形的繪製,生成圖形容器,必須經過JS腳原本完成。
canvas繪圖時相對於一根畫筆,每一步驟的繪製是基於當前狀態的(位置、顏色等)
canvas api文檔
canvas.width/height :用來控制Canvas畫布繪製區域的寬高。需根據實際屏幕環境設置,不然繪製的圖形會失真。
canvas.getContext():返回canvas的繪製上下文, 實際繪製依賴於該對象。
canvas.toBlob():能夠對Canvas圖像生成對應的Blob對象。
canvas.toDataURL():返回base64 data圖片數據。css

<canvas> 標籤

HTML5 中的<canvas> 標籤只會生成圖形容器,圖形繪製必須經過JS腳原本完成。html

Canvas寬高與CSS寬高

<canvas width="600" height="300" style="width: 300px; height: 150px"></canvas>
複製代碼
  • style中的width/height表明canvas元素在界面上所佔據的寬/高, 即樣式上的CSS寬高。
  • 屬性中的width/height **則表明canvas實際像素的寬高。**用來控制Canvas畫布繪製區域的寬高。不設置寬高時,會有默認的寬高(300*150),通常建議設置好圖形寬高限度繪製區域。


當使用Canvas API繪製圖形時使用的座標、尺寸大小是基於Canvas寬高屬性的,而與CSS樣式寬高無關。
而CSS寬高則決定canvas圖形的視覺顯示大小,canvas畫布的寬高會等比例縮放成CSS寬高顯示。java

實際使用時,儘可能避免這種因尺寸不一致比例縮放渲染,致使的圖形模糊、鋸齒化等問題。canvas

繪製上下文

canvas.getContext():返回canvas的繪製上下文, 實際繪製依賴於該對象。
本文主要討論二維平面繪製:const context =``canvas.getContext('2d');api

實際畫布繪圖時每一路徑的繪製過程是基於繪製上下文的當前狀態的。
繪製過程至關於只使用一根畫筆做畫,每一次路徑的繪製點、顏色等樣式是基於繪製上下文的當前狀態(位置、顏色、粗細等)。
對於非連續的路徑繪製,繪製過程要用context.beginPath()聲明,這樣可使繪製樣式不會被覆蓋影響。跨域

好比:bash

context.strokeStyle = 'blue';
context.moveTo(10, 10);
context.lineTo(100, 10);
context.stroke();

context.strokeStyle = 'red';
context.moveTo(100, 10);
context.lineTo(10, 50);
context.stroke();
複製代碼

樣式被覆蓋,繪製效果以下:
iphone

image.png

而用 context.beginPath()聲明路徑,代碼:

context.beginPath();
context.strokeStyle = 'blue';
context.moveTo(10, 10);
context.lineTo(100, 10);
context.stroke();

context.beginPath();
context.strokeStyle = 'red';
context.moveTo(100, 10);
context.lineTo(10, 50);
context.stroke();
複製代碼

則兩條線的樣式不受影響,繪製效果以下:
函數

image.png

context.canvas

這個在不少時候須要在繪製時獲取當前畫布信息時很是有用。CanvasRenderingContext2D.canvas是一個只讀屬性,能夠返回當前上下文源自哪一個<canvas>元素,並獲取畫布的width/height等屬性信息。

context.clearRect()

在實際開發時常常會使用到的api,清除制定的繪製區域,不然在同一區域重複繪製會發生圖形疊加。

Canvas與SVG的區別

Canvas和SVG是當前HTML5中主要使用的圖形繪製技術,前者提供畫布標籤和繪製API,後者是一整套獨立的矢量圖形語言,使用 XML 格式定義圖像。

  1. Canvas經過JS繪製圖形,只有當個HTML元素;而SVG使用 XML 格式定義圖形,生成的圖形包含多種圖形元素(Path、Line、Rect)。
  2. Canvas繪製基於像素級控制;SVG則基於內部圖形元素操做控制;
  3. Canvas是像素級渲染,依賴分辨率;SVG則是矢量圖形,縮放時圖形質量不會失真;
  4. 事件交互:Canvas中,事件只能註冊到<canvas>標籤上,但經過事件委託,能夠細化到像素點(x,y)的交互;SVG則能夠爲某個元素附加 單獨的JavaScript 事件處理器,但也只能控制細化在圖形元素上。
  5. Canvas適合小面積、大數據應用場景;SVG適合大面積、小數量應用場景(圖像元素少)。

Canvas適用場景:適合像素處理,動態渲染和大數據量繪製; 適合圖像密集型的遊戲;
SVG適用場景:適合靜態圖片展現,高保真文檔查看和打印的應用場景。

常見問題

移動端繪製canvas模糊問題

現象

以下圖:

image.png

上圖中,在未作兼容移動端處理時,繪製的canvas刻度組件在iphone 6s機型上,canvas圖形和文字模糊失真。兼容處理後圖形質量得以保證。
以上刻度組件參考個人另外一篇文章,地址:「 採用Canvas繪製一個可配置的刻度(尺)組件

緣由

關於移動端高清屏DPR、圖片模糊、移動端適配等問題,不清楚的童鞋能夠參考「關於移動端適配,你必需要知道的」這篇文章,講的比較詳細。這裏再也不贅述,本文章只處理移動端Canvas模糊問題。
在移動端高清屏幕上,常常會遇到Canvas圖形模糊的問題。本質上跟移動端圖片模糊問題是同樣的。canvas繪製成的圖像跟也是位圖,在dpr > 1的屏幕上,位圖的一個像素可能由多個物理像素來渲染,然而這些物理像素點並不能被準確的分配上對應位圖像素的顏色,只能取近似值,因此在dpr > 1的屏幕上就會模糊。
在PC端繪製canvas圖形,咱們都直接把1個canvas像素直接等於1px的css像素處理,這沒有問題,應該目前PC端屏幕dpr都是1。而在dpr > 1的移動端屏幕上就不能直接這樣處理。

解決

解決方案固然仍是從dpr入手。

  1. 經過window.devicePixelRatio獲取當前設備屏幕的dpr;
  2. 首先獲取或設置Canvas容器的寬高;
  3. 根據dpr,設置canvas元素的寬高屬性;在dpr = 2時至關於擴大畫布2倍;
  4. 經過context.scale(dpr, dpr)縮放Canvas畫布的座標系。在dpr = 2時至關於把canvas座標系也擴大了兩倍,這樣繪製比例放大了2倍,以後canvas的實際繪製像素就能夠按原先的像素值處理。

在渲染到屏幕時,擴大的畫布圖形又等比例縮放渲染到canvas容器中。從而保證canvas圖形的質量。

// 獲取dpr
const dpr = window.devicePixelRatio; 
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 獲取Canvas容器的寬高
const { width: cssWidth, height: cssHeight } = canvas.getBoundingClientRect();
// 根據dpr,設置Canvas的寬高,使1個canvas像素和1個物理像素相等
canvas.width = dpr * cssWidth;
canvas.height = dpr * cssHeight;
// 根據dpr,設置canvas元素的寬高屬性
ctx.scale(dpr,dpr);
複製代碼

canvas drawImage()參數問題,移動端圖片模糊問題

canvas的drawImage() 函數有個特別容易混淆搞錯的地方。它的5參數和9參數用法的參數位置是不一樣的。實際開發中沒注意到這一點,會讓本身特別困惑問題出在哪!汗!

drawImage()方法有一個很是怪異的地方,你們必定要注意,那就是5參數和9參數用法的參數位置是不同的,這個和通常的API有所不一樣。通常API可選參數是放在後面。可是,這裏的drawImage()使用9個參數時候,可選參數sx,sy,sWidth和sHeight是在前面的。若是不注意這一點,有些表現會讓你沒法理解。

且drawImage()函數插入的圖形在移動端dpr >1屏幕一樣會有圖片模糊的問題。
在移動端經過drawImage()載入另外一個已繪製的Canvas元素時,也要注意對另外一個canvas元素作兼容處理,還須要注意二者座標系的不一樣。

// 設置canvas_bg寬高
canvas_bg.width = (config.unit * (scale_len - 1) + config.width) * dpr;
canvas_bg.height = config.height * dpr;
ctx_bg.scale(dpr, dpr);

...

// 初始化開始位置
point_x = (config.def - config.start) / config.capacity * config.unit;
//在主畫布ctx上,經過drawImage()插入另外一個canvas_bg畫布;
ctx.drawImage(canvas_bg, point_x * dpr, 0, config.width * dpr, config.height * dpr, 0, 0, config.width, config.height);

複製代碼

上面的代碼中, canvas_bg畫布一樣須要處理上面提到的canvas模糊問題;在主畫布ctx上,經過drawImage()插入另外一個canvas_bg畫布圖形時,須要注意此時二者座標系比例的不一樣,此時canvas_bg的座標系是根據dpr縮放後的。

當canvas繪製尺寸或drawImage插入圖像、getImageDate獲取圖形資源等尺寸大於某個閾值時,可能會出現繪製空白問題。

在實際開發中遇到,canvas繪製尺寸或drawImage插入圖像、getImageDate獲取圖形資源等尺寸大於某個閾值時,渲染出來的圖片整個都是空白。這個具體的閾值不肯定,跟運行環境有關。但這應該也是drawImage繪製的一個不知什麼時候爆發的隱患。
好比下圖,繪製的刻度尺畫布尺寸過大,截取後渲染到主畫布上,整個刻度空白,但不影響交互。

image.png

canvas getImageData 跨域問題

只要可以在網頁中正常顯示出來的跨域圖片,就可使用canvas的drawImage() API繪製出來。可是若是想經過getImageData()方法獲取圖片的完整的像素信息,轉換成本地輸出時,則多半會出現跨域問題。

解決:

1. 是頁面與服務端開啓容許跨域;

2. 給圖片設置容許跨域,`img.setAttribute('crossOrigin', 'anonymous');`
複製代碼
相關文章
相關標籤/搜索