canvas 的基本概念

什麼是 canvas

這裡先簡單說一下總結,對於 2D 的 canvas 在網頁頁面中,可以直接視為透過 JavaScript 動態操作的圖片,3D canvas 不在此文的泛圍中。

維基百科 – Canvas (HTML元素) 中部份內容提到如下。
Canvas 是一個 HTML 元素。可以利用 JavaScript 程式語言在該元素上繪圖,常見的應用包括繪製圖形及文字、影像處理、遊戲及動畫製作。

<canvas> 元素通常也被 WebGL 用來在網頁上顯示使用硬體加速繪製的 3D 圖形。

什麼是 WebGL?
WebGL (Web Graphics Library) 是一個透過瀏覽器渲染3D及2D圖像的 JavaScript API ,且不需要安裝任何插件。 WebGL 透過與OpenGL ES 2.0緊密連結的API,將3D圖像帶入HTML5中,並可透過canvas元素呈現於瀏覽器中。
出處:MDN 技術文件說明

canvas VS SVG

非常強大的 SVG(Scalable Vector Graphics,可伸縮的矢量標記圖)在早期是前端開發配合使用 div + css 來完成繪圖,沒有 canvas 的時候,在瀏覽器繪製圖形是比較複雜的,而在 canvas 出現之後,繪製 2D 圖形相對變得容易了。

canvas 本質上是一個與解析度相關的位圖畫布,也就註定了在不同解析度下,canvas 繪製的內容顯示的時候會有所不同。此外,canvas 繪製的內容不屬於任何 DOM 元素,在瀏覽器的元素查看器中也找不到,那自然無法檢測滑鼠點擊了canvas中的哪個內容,很顯然這兩方面,canvas 都是不如 SVG 的。

如果使用 CSS 設置 canvas 元素的尺寸,那可能會導致繪製出來的圖形變得扭曲,如長方形變正方形,圓形變橢圓等,這是因為畫布尺寸和元素尺寸是不一樣的,畫布會自動適應元素的尺寸,如果二者是成比例的,那麼畫布就會等比例縮放,不會出現扭曲。

SVG 有些問題:

  • SVG 基於 XML,裡面的元素都認為 DOM 元素啟用 DOM 操作,SVG 每個繪製的圖像均被視為對象,若 SVG 對象屬性變化,瀏覽器會自動重現圖形。
  • 過度使用 DOM 的都會變得很慢,複雜的 SVG 會導致渲染速度變慢。像地圖這類的應用首選是 SVG。
  • 瀏覽器因窗口發生變化的重新渲染、元素尺寸位置變化、字體變化等等。
  • 即使可以使用 DOM 操作,但操作的成本是更高的( DOM 和 JS 的實現是分開的)。

canvas 於 HTML 中的預設大小(寬 300 x 高 150 px)

MDN – Canvas 教學文件中部份內容提到如下。
<canvas> 是一個 HTML 元素,我們可以利用程式腳本在這個元素上繪圖(通常是用 JavaScript)。除了繪圖,我們還可以合成圖片或做一些簡單(或是不那麼簡單)的動畫。
<canvas> 元素是 WhatWG Web applications 1.0(也就是 HTML5)規範的一部分,目前所有主流的瀏覽器都已支援。
現今所有主流的瀏覽器都有支援。
預設的畫布大小是 300px * 150px(寬 * 高)。

對於 MDN – Canvas 教學文件 連結,有許多的 API 基本用法與觀念,值得一看。

See the Pen
canvas 於 HTML 中的預設大小(寬 300 x 高 150 px)
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

在 HTML 程式碼中只有單純的一個 <canvas id='myCanvas'> 元素,在 CSS 的部份只單純在 #myCanvas 元素上給了一個黑底背景色塊,在瀏覽器下都只有呈現出一個 300 x 150 大小的區塊,相較於 HTML 中其他相關的區塊與行內元素,嚴格來說可以算是獨立出來的一個元素所給予特定的預設樣式。

為何會提到 <canvas> 元素預設大小呢?這裡後續會提到,某些 canvas API 中的方法使用下,如果沒有給予特定的設定或是參數,會直接段元素預設大小的面積運算處理,而在瀏覽器做渲染畫面動作時,是會和 CSS 分開處理運算。

canvas 的容器特性,canvas 元素尺寸分標籤屬性與 CSS 二種方式定義,CSS 樣式定義尺寸會讓 canvas 產生變形

canvas 元素在前面提到,在瀏覽器中有預設尺寸 300 x 150 xp,這裡先暫提一下後面透過實作了解影響的部份。

以網頁控制元素尺寸,主要可分為二種:

  1. 元素標籤的高寬度屬性。
  2. CSS 的 style 或 class 樣式操作。

對於二種方式用於 canvas 元素會產不同影響,可先理解成 canvas 是較特別的 <img> 標籤,畫布大小是由 JS 定義,若由 class 或是 style 定義尺寸的話,會產生變形

以下以四個部份,呈現出 canvas 元素在 JS 與 CSS 定義尺寸後的差別。

See the Pen
Untitled
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

canvas 元素透過 CSS 定義大小,如果沒有先透過標籤屬性的 width="" 或是 height="" 定義的話,會先以 canvas 的預設尺寸進行畫布內的繪製,也就是以 300 x 150 的尺寸來將置入的圖片繪製,而透過 CSS 再操作 canvas 元素尺寸,會以 canvas 所預設繪制出來的尺寸來進行變形處理,CSS 若有定義背景色,會於 canvas 元素以底色呈現,canvas 畫布本身預設為全透,除非有定義畫布的色彩。


canvas 的文字繪製

fillText() 填滿文字

  • textToDraw 文字內容
  • x, y 指在 (x,y) 繪製填滿指定文字
  • maxWidth 可繪製最大寬度

See the Pen
canvas 於 HTML 中的預設大小(寬 300 x 高 150 px)
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

以上例來說,透過 canvas 所繪的預設文字為黑色,級數大小非常的小,需要再進行設定。
.fillText() 方法,主要是針對宣告為 canvas 物件內入文字內容與定位。

ctx.font 與 ctx.fillStyle 自訂字型大小與色彩

See the Pen
canvas 文字繪製-宣告與基本的文字繪製-1
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

透過 ctx.font 屬性以賦值的方式 "30px sans-serif" 指定字級與字體,屬性值以字串設定於字體大小與字體,中間以空白字串隔開表示。

網頁預設字型與安全字體的基本知識可見 跨平台安全網頁字體 整理一文。

預載字型與圖片 (線上 webFont 字體實作與使用記錄)

使用 canvas 如果要引入線上的字體,因為繁體中文的字體需要較高的流量,又或是原本 google webFont 預設 link 引入方式,所使用的 rel="preconnect" 也會影像 JS 處理渲染畫面,需要先將字體資源完整引入後才執行 JS,才不會產生只以網頁安全字體呈現的情形產生。

Google webFont 預載字體:

其他參考來源:
IT 邦幫忙 -【Day06】Canvas-繪製文字
MDN – 使用canvas繪製文字 (API 的方法相關參數)。

繪製文字時因預設基線置底文字會偏上,透過 textBaseline 設定

textBaseline 預設值為 alphabetic (基線在文字底部),改成 top (基線在文字上部) 近一般文書排版

canvas 的文字預設為左上角開始計算,基線的部份預設為 alphabetic (中/英:字母的),基本上預設設定看到字母大寫的部份,都會底部開啟計算,也因為這預設 alphabetic 的關係,加上 canvas 座標開始計算也是由左至右 (正值向右疊加)、由上至下 (正值向下疊加) 的方式,如果一開始使用 .fillText() 繪製出文字第 2 和 3 的參數設定都為 0 的話,除了看不到到基線外,也只有幾個像是小寫的 g 有向下超過基線的比畫可以看的到外,大寫的字實基本上是看不到的。

See the Pen
canvas-textBaseline–預設文字基線為 alphabetic
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

修正方式,將 ctx.textBaseline 的預設值改成 "top",基線調整到上方後也比較相近於一般的文書排版,此時印出在畫面的就會是 top

See the Pen
canvas-textBaseline()–預設文字基線為 alphabetic
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

textBaseline 文字基線的 6 個設定

以 MDN 官方文件加以修改,配合 canvas 基線與文字的定位產生繪製,透過 for 迴圈呈現出每個 textBaseline 設定,針對預設設定刻意多加垂直 10px 的座標值。

See the Pen
canvas-textBaseline()–基線的預設與 6 個設定
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

其他參考來源:
MDN – CanvasRenderingContext2D.textBaseline (CanvasRenderingContext2D.textBaseline 原理與說明)
Canvas.drawText繪製文字為什麼會偏上?

canvas 的文字換行與自動換行

特殊字元 \n 將字串換行

canvas 的繪製文字中的字串本質上是透過 JavaScript 的字串字面值 (String literals) 進型操作,在 JS 中的字串字串要換行的話,就需要以特殊字元 \n 的方式進行換行。

在使用 html 表單元素 input 或是 textarea 元素進行輸入字串,如果在 textarea 元素中給使用者輸入文字並透過 enter 字串換行,此時表單在字串換行的地方會以特殊字元 \n 做為字符,當再透過 JS 或是 HTML 解析後,呈現在畫面上就會產生換行。

特殊字元 \n 的換行的方式也不只限於 JS 中使用,包刮在終端機下指令所印出的字串內容,都可用特殊字元這樣的方式來將字串換行。

MDN – 字串裡的特殊字元文件連結 查看 \n 換行 (New line)。

canvas 的繪製文字使用 JavaScript 原型方式,自訂加入自動換行方法

在 canvas 的預設文字繪製,是沒有自動換行的功能 (或 API),簡單的理解就是字串除了透過 JS 操作特殊字元 \n 方式進行換行外,基本上就是無限的在 canvas 容器元素內,以字串的長度向右或向下進行增長的動作。

原先 canvas 沒有 API 的話,不表示就不能處理,這裡是使用 JS 的原型方式,在 CanvasRenderingContext2D 原型中掛上使用的方式,讓所有使用 canvas 的物件都能透過字串自動換行的方法進行自動判斷換行的長度,對應的條件除了 \n (特殊字元) 外,也與 ' ' (單一空白字元) 來做換行的動作。

例中故意以沒有空白字元的方式 加入wrapText方法,達到自動換行的排版方式, 進行操作,可見到字串在沒有 \n (特殊字元) 與 ' ' (單一空白字元) 的情形下,字串會穿出所繪製文字所定義的寬度,這樣的方式會相近於網頁排版的字串自動換行特性。

註:在沒有定義出所使用的字型,會因為瀏覽器預設安全字型的不同,對於自動換行所呈現的排版也會有些不同。

See the Pen
canvas 繪製文字以 JavaScript 原型方式,自訂加入自動換行方法
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

canvas 文字繪製相關自動換行的文章:


canvas 的圖片繪製與 base64 轉換處理

圖片處理 .drawImage() 置入圖片到 canvas 中繪製 → .toDataURL() 方法轉出 canvas 成 base64 編碼的圖指定格式 (EX: "data:image/png;base64,iVBxxxxx" ) → 使用者下載出 base64 的圖檔,透過 file API 餺存下格式圖片檔 (EX: XXX.png 圖檔) 存於硬體等儲存空間中。

將來源圖片檔 (xxx.jpg…等格式檔案),以 .drawImage() 方法加入 canvas 繪製圖片

MDN – CanvasRenderingContext2D.drawImage()

使用 ctx.drawImage(image, dx, dy); 三個參數的方法,進行程式碼規劃,分別以動態操作 <img><canvas> 元素。。

See the Pen
Untitled
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

canvas 繪製圖片於元素後,若圖片為跨域 (CORS) 連結,透過 crossorigin 設定讓之後的 .toDataURL() 方法順利轉出

較詳細可見 HTML5標簽的crossorigin屬性,提供元素CORS跨域設定 文章,針對非同網域 (CORS) 需在圖片上使用 crossorigin 的屬性設定,但瀏覽器所接收的網頁檔頭要是 access-control-allow-origin: * 再接 crossorigin="" 標籤設定才可以讓取下來的圖片給 canvas 轉換處理。

輸出成指定格式圖片 (jpeg 或 png 等…),以 HTMLCanvasElement.toDataURL() 方法,將 canvas 轉成 base64 編碼

這裡先簡單小結一下,將圖片以 canvas 轉成 base64 格式後,就可透過瀏覽器取得圖片檔案。

data URIs 可理解成檔案以一堆字串置於記憶體中用來使用的媒體檔

data URIs 是以 base64 格式編碼的方式,透過編碼將檔案以資料的型式來使用。
這樣說有點抽象,但可以先理解成編碼出來的一推字串,可不用見到檔案的實體資料 (不會在硬碟中存成檔案,直接是以字串置於記憶體中使用),就直接使用的方式。

canvas.toDataURL() 方法轉成 data URI 的 base64 編碼

type 選擇性 – 圖像格式的 DOMString. 預設為 image/png.
encoderOptions 選擇性 – 表示 image/jpeg 或是 image/webp 的圖像品質, 為 0 到 1 之間的 Number. 如果值不在上述範圍中, 將會使用預設值. 其他的會忽略.

MDN – HTMLCanvasElement.toDataURL() 文件提到方法內容如下:
HTMLCanvasElement.toDataURL() 方法回傳含有圖像和參數設置特定格式的 data URIs (預設 PNG). 回傳的圖像解析度為 96 dpi.

  • 如果 canvas 的高度或是寬度為 0, 將會回傳字串 "data:,".
  • 如果要求的圖像類型並非 image/png, 但是回傳的類型卻是 data:image/png, 表示要求的圖像類型並不支援.
  • Chrome 也支援 image/webp 類型.

瀏覽器使用者端 canvas 或是 JavaScript 所繪製圖案,透過 canvas 的 HTMLCanvasElement.toDataURL() 方法,把 canvas 畫布中的圖案轉變成 base64 編碼格式的 png 圖檔以 Data URL 數據型式使用。

如果 canvas.toDataURL(); 不帶入參數直接使用方法,會是以 data:image/png 的格式轉出成 base64 圖片。

HTMLCanvasElement.toDataURL() 方法 Deom 如下,使用 console.log() 查看轉成 base64 的結果。

See the Pen
canvas 與 html img 使用 JavaScript 渲染
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

將 canvas 透過 .toDataURL() 方法轉出的 base64 做為路徑,指給網頁沒 src 的 img 標籤在瀏覽器呈現圖片

如果帶參數使用 .toDataURL(); 方法,可指定輸出的影像格式與影像品質等參數設定。

See the Pen
canvas 以 HTMLCanvasElement.toDataURL() 方法轉 base64 編碼
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

用JavaScript將Canvas內容轉化成圖片的方法 文章中介紹 (實作範例)

  1. 透過 <img id="ringoImage" alt="" src=" /wordpress/wp-content/uploads/2014/03/hacker.jpg " />.jpg 格式圖檔以 img 元素加入 DOM 結構。
  2. 再透過 JavaScript 以 #canvasHolder 動態產生 canvas 元素中介處理繪制圖片。
  3. 最後由 #pngHolder 將圖片由 .toDataURL() 方法,指定為 png 圖片格式的 base64 方式置於頁面連結 <img src="data:image/png;base64,iVBORw0KGgoA..."> 以 img 元素呈現畫頁面。

將畫布(canvas)圖像保存成本地圖片的方法 文章中介紹,整合 canvas2image.js, base64.js 二支插件 為主,並配合 .toDataURL() 的用法 (並帶有 canvas 轉出存成與轉換 png、bmp、jpeg 格式圖片檔實作範例可操作)。

如果要處理 base64 轉出的字串,要開頭的檔案類型等訊息移除,可以使用 .replace() 方法取出後方的二進位編碼。

目前處理 File API map 位置 binary-to-String

fileAPI轉換操作流程圖
File API map 流程圖連結 (上方圖片為連結轉存的圖片)

整理一下目前處理的流程,以附圖中的紅線就是處理的流程,已透過 base64 編碼成了是一組相似的二進制到文本(binary-to-text or binaryString)的編碼規則。

補充知識點 – base64 可處理的編碼 (Encode) 及解碼 (Decode) 方式:
MDN – Base64的編碼與解碼
JavaScript Base64 編碼(Encode)及解碼(Decode)


下載 base64 圖片檔案

以二個方式用來下載圖檔,透過點按行為的方式給使用者取得圖片。

概念與原理

二個方式的原理都差不多,canvas 由 base64 轉出成字串的型式 (binary-to-String),實際上就可以在瀏覽器上直接開啟圖檔,在頁面中產生出一個 html 的 a 標籤,以 .clcik (點按) 與 .download (下載) 行為,就可讓使用者點按或相關操作下,以下載的方式取得圖片檔案。

base64 圖片經由 .download (下載) 行為的過程,可先預設出存檔名稱,方便於瀏覽器下載方式直接以預設檔名的方式存檔,但如果有使用另存檔案的方式,會有介面呈現於瀏覽器的上,若使用者想在定義其他名稱,讓使用者另命名後才存下圖片到本機的磁碟中,成為一個圖片的實體檔案。

base64 圖片,在瀏覽器中可單純開啟圖片檔,另外也可以指定 <img> 元素,透過 src='' 做為來源指入於元素中呈現圖片。

下載方式一:手刻 base64 圖片下載

透過前面提的概念,將產生的實體圖檔存放於儲存空間, .download 設定下載圖檔的預設名稱並帶時間戳,圖檔的副檔名透過 base64 以截字串二次的方式,取得圖片格式做為使用。

See the Pen
canvas 以 HTMLCanvasElement.toDataURL() 方法轉 base64 編碼,將 png 印出於網頁上
by Jimmy_Wu (@Jimmy_Wu)
on CodePen.light

其他參考文件:MDN – Uint8Array

下載方式二:download.js 套件,簡化處理 base64 圖片輸出成圖檔

透過 download.js 實作的按鈕下載圖片。

簡單以語法對應設定 base64 圖片的下載名稱與副檔名。

See the Pen
canvas 轉 base64 編碼 png 圖片,在網頁上透過按鈕點按觸發下載
by Jimmy_Wu (@Jimmy_Wu) on CodePen.light

其他參考文章:

  • Canvas2image.js GitHub
  • Web開發之用canvas2image.js將canvas保存為圖片(實現頁面截圖下載功能)說明文章gitHub-pages
    整合 canvas2image.js、html2canvas.js、convertImgToBase64.js 插件,透過 canvas 原型製出繪畫功能。

其他相關實作資料