Canvas 從入門到勸朋友放棄(圖解版)

來自:掘金,作者:德育處主任

鏈接:https://juejin.cn/post/7116784455561248775

本文簡介

在前端領域,如果只是懂 Vue 或者 React ,未來在職場的競爭力可能會比較弱。

根據我多年在家待業經驗來看,前端未來在 數據可視化 和 AI 這兩個領域會比較香,而 Canvas 是數據可視化在前端方面的基礎技術。

本文就用光的速度將 canvas 給入門了。

要入門一個技術,前期最重要是快!所以本文只講入門內容,能應付簡單項目。深入的知識點會在其他文章講解。

Canvas 是什麼?

Canvas 和 SVG 的區別

DMrv0K

就上面的描述而言可能有點難懂,你可以打開 AntV 旗下的圖形編輯引擎做對比。G6[1] 是使用 canvas 開發的,X6[2] 是使用 svg 開發的。

我的建議是:如果要展示的數據量比較大,比如一條數據就是一個元素節點,那使用 canvas 會比較合適;如果用戶操作的交互比較多,而且對清晰度有要求(矢量圖),那麼使用 svg 會比較合適。

起步

學習前端一定要動手敲代碼,然後看效果展示。

起步階段會用幾句代碼說明 canvas 如何使用,本例會畫一條直線。

畫條直線

  1. 在 HTML 中創建 canvas 元素

  2. 通過 js 獲取 canvas 標籤

  3. 從 canvas 標籤中獲取到繪圖工具

  4. 通過繪圖工具,在 canvas 標籤上繪製圖形

<!-- 1、創建 canvas 元素 -->
<canvas
  id="c"
  width="300"
  height="200"
  style="border: 1px solid #ccc;"
></canvas>

<script>
  // 2、獲取 canvas 對象
  const cnv = document.getElementById('c')

  // 3、獲取 canvas 上下文環境對象
  const cxt = cnv.getContext('2d')

  // 4、繪製圖形
  cxt.moveTo(100, 100) // 起點座標 (x, y)
  cxt.lineTo(200, 100) // 終點座標 (x, y)
  cxt.stroke() // 將起點和終點連接起來
</script>
複製代碼

moveTo 、 lineTo 和 stroke 方法暫時可以不用管,它們的作用是繪製圖形,這些方法在後面會講到~

注意點

1、默認寬高

canvas 有 默認的 寬度 (300px) 和 高度 (150px)

如果不在 canvas 上設置寬高,那 canvas 元素的默認寬度是 300px,默認高度是 150px。

2、設置 canvas 寬高

canvas 元素提供了 width 和 height 兩個屬性,可設置它的寬高。

需要注意的是,這兩個屬性只需傳入數值,不需要傳入單位(比如 px 等)。

<canvas width="600" height="400"></canvas>
複製代碼

3、不能通過 CSS 設置畫布的寬高

使用 css 設置 canvas 的寬高,會出現 內容被拉伸 的後果!!!

<style>
  #c {
    width: 400px;
    height: 400px;
    border: 1px solid #ccc;
  }
</style>

<canvas id="c"></canvas>

<script>
  // 1、獲取canvas對象
  const cnv = document.getElementById('c')

  // 2、獲取canvas上下文環境對象
  const cxt = cnv.getContext('2d')

  // 3、繪製圖形
  cxt.moveTo(100, 100) // 起點
  cxt.lineTo(200, 100) // 終點
  cxt.stroke() // 將起點和終點連接起來

  console.log(cnv.width) // 獲取 canvas 的寬度,輸出:300
  console.log(cnv.height) // 獲取 canvas 的高度,輸出:150
</script>
複製代碼

canvas 的默認寬度是 300px,默認高度是 150px。

  1. 如果使用 css 修改 canvas 的寬高(比如本例變成 400px * 400px),那寬度就由 300px 拉伸到 400px,高度由 150px 拉伸到 400px。

  2. 使用 js 獲取 canvas 的寬高,此時返回的是 canvas 的默認值。

最後出現的效果如上圖所示。

4、線條默認寬度和顏色

線條的默認寬度是 1px ,默認顏色是黑色。

但由於默認情況下 canvas 會將線條的中心點和像素的底部對齊,所以會導致顯示效果是 2px 和非純黑色問題。

5、IE 兼容性高

暫時只有 IE 9 以上才支持 canvas 。但好消息是 IE 已經有自己的墓碑了。

如需兼容 IE 7 和 8 ,可以使用 ExplorerCanvas[3] 。但即使是使用了 ExplorerCanvas 仍然會有所限制,比如無法使用 fillText() 方法等。

基礎圖形

座標系

在繪製基礎圖形之前,需要先搞清除 Canvas 使用的座標系。

Canvas 使用的是 W3C 座標系 ,也就是遵循我們屏幕、報紙的閱讀習慣,從上往下,從左往右。

W3C 座標系 和 數學直角座標系 的 X軸 是一樣的,只是 Y軸 的反向相反。

W3C 座標系 的 Y軸 正方向向下。

直線

一條直線

最簡單的起步方式是畫一條直線。這裏所說的 “直線” 是幾何學裏的 “線段” 的意思。

需要用到這 3 個方法:

  1. moveTo(x1, y1):起點座標 (x, y)

  2. lineTo(x2, y2):下一個點的座標 (x, y)

  3. stroke():將所有座標用一條線連起來

起步階段可以先這樣理解。

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 繪製直線
  cxt.moveTo(50, 100) // 起點座標
  cxt.lineTo(200, 50) // 下一個點的座標
  cxt.stroke() // 將上面的座標用一條線連接起來
</script>
複製代碼

上面的代碼所呈現的效果,可以看下圖解釋(手不太聰明,畫得不是很標準,希望能看懂)

多條直線

如需畫多條直線,可以用會上面那幾個方法。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(20, 100)
  cxt.lineTo(200, 100)
  cxt.stroke()

  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.stroke()
</script>
複製代碼

仔細觀察一下,爲什麼兩條線的粗細不一樣的?

明明使用的方法都是一樣的,只是第二條直線的 Y軸 的值是有小數點。

答:默認情況下 canvas 會將線條的中心點和像素的底部對齊,所以會導致顯示效果是 2px 和非純黑色問題。

上圖每個格子代表 1px

線的中心點會和畫布像素點的底部對齊,所以會線中間是黑色的,但由於一個像素就不能再切割了,所以會有半個像素被染色,就變成了淺灰色。

所以如果你設置的 Y軸 值是一個整數,就會出現上面那種情況。

設置樣式

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 繪製直線
  cxt.moveTo(50, 50)
  cxt.lineTo(200, 50)

  // 修改直線的寬度
  cxt.lineWidth = 20

  // 修改直線的顏色
  cxt.strokeStyle = 'pink'

  // 修改直線兩端樣式
  cxt.lineCap = 'round' // 默認: butt; 圓形: round; 方形: square

  cxt.stroke()
</script>
複製代碼

新開路徑

開闢新路徑的方法:

在繪製多條線段的同時,還要設置線段樣式,通常需要開闢新路徑。

要不然樣式之間會相互污染。

比如這樣

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 第一條線
  cxt.moveTo(20, 100)
  cxt.lineTo(200, 100)
  cxt.lineWidth = 10
  cxt.strokeStyle = 'pink'
  cxt.stroke()

  // 第二條線
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.stroke()
</script>
複製代碼

如果不想相互污染,需要做 2 件事:

  1. 使用 beginPath() 方法,重新開一個路徑

  2. 設置新線段的樣式(必須項)

如果上面 2 步卻了其中 1 步都會有影響。

只使用 beginPath()

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 第一條線
  cxt.moveTo(20, 100)
  cxt.lineTo(200, 100)
  cxt.lineWidth = 10
  cxt.strokeStyle = 'pink'
  cxt.stroke()

  // 第二條線
  cxt.beginPath() // 重新開啓一個路徑
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.stroke()
</script>
複製代碼

第一條線的樣式會影響之後的線。

但如果使用了 beginPath() ,後面的線段不會影響前面的線段。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 第一條線
  cxt.moveTo(20, 100)
  cxt.lineTo(200, 100)
  cxt.stroke()

  // 第二條線
  cxt.beginPath() // 重新開啓一個路徑
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.lineWidth = 4
  cxt.strokeStyle = 'red'
  cxt.stroke()
</script>
複製代碼

設置新線段的樣式,沒使用 beginPath() 的情況

這個情況會反過來,後面的線能影響前面的線。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 第一條線
  cxt.moveTo(20, 100)
  cxt.lineTo(200, 100)
  cxt.lineWidth = 10
  cxt.strokeStyle = 'pink'
  cxt.stroke()

  // 第二條線
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.lineWidth = 4
  cxt.strokeStyle = 'red'
  cxt.stroke()
</script>
複製代碼

正確的做法

在設置 beginPath() 的同時,也各自設置樣式。這樣就能做到相互不影響了。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(20, 100)
  cxt.lineTo(200, 100)
  cxt.lineWidth = 10
  cxt.strokeStyle = 'pink'
  cxt.stroke()

  cxt.beginPath() // 重新開啓一個路徑
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.lineWidth = 4
  cxt.strokeStyle = 'red'
  cxt.stroke()
</script>
複製代碼

折線

和 直線 差不多,都是使用 moveTo() 、lineTo() 和 stroke() 方法可以繪製折線。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(50, 200)
  cxt.lineTo(100, 50)
  cxt.lineTo(200, 200)
  cxt.lineTo(250, 50)

  cxt.stroke()
</script>
複製代碼

畫這種折線,最好在草稿紙上畫一個座標系,自己計算並描繪一下每個點大概在什麼什麼位置,最後在 canvas 中看看效果。

矩形

根據前面的基礎,我們可以 使用線段來描繪矩形,但 canvas 也提供了 rect() 等方法可以直接生成矩形。

使用線段描繪矩形

可以使用前面畫線段的方法來繪製矩形

canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
 const cnv = document.getElementById('c')
 const cxt = cnv.getContext('2d')

 // 繪製矩形
 cxt.moveTo(50, 50)
 cxt.lineTo(200, 50)
 cxt.lineTo(200, 120)
 cxt.lineTo(50, 120)
 cxt.lineTo(50, 50) // 需要閉合,又或者使用 closePath() 方法進行閉合,推薦使用 closePath()

 cxt.stroke()
</script>
複製代碼

上面的代碼幾個點分別對應下圖。

使用 strokeRect() 描邊矩形

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // strokeStyle 屬性
  // strokeRect(x, y, width, height) 方法
  cxt.strokeStyle = 'pink'
  cxt.strokeRect(50, 50, 200, 100)
</script>
複製代碼

上面的代碼可以這樣理解

使用 fillRect() 填充矩形

fillRect() 和 strokeRect() 方法差不多,但 fillRect() 的作用是填充。

需要注意的是,fillStyle 必須寫在 fillRect() 之前,不然樣式不生效。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // fillStyle 屬性
  // fillRect(x, y, width, height) 方法
  cxt.fillStyle = 'pink'
  cxt.fillRect(50, 50, 200, 100) // fillRect(x, y, width, height)
</script>
複製代碼

同時使用 strokeRect() 和 fillRect()

同時使用 strokeRect() 和 fillRect() 會產生描邊和填充的效果

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.strokeStyle = 'red'
  cxt.strokeRect(50, 50, 200, 100) // strokeRect(x, y, width, height)
  cxt.fillStyle = 'yellow'
  cxt.fillRect(50, 50, 200, 100) // fillRect(x, y, width, height)
</script>
複製代碼

使用 rect() 生成矩形

rect() 和 fillRect() 、strokeRect() 的用法差不多,唯一的區別是:

strokeRect() 和 fillRect() 這兩個方法調用後會立即繪製;rect() 方法被調用後,不會立刻繪製矩形,而是需要調用 stroke() 或 fill() 輔助渲染。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.strokeStyle = 'red'
  cxt.fillStyle = 'pink'

  cxt.rect(50, 50, 200, 100) // rect(x, y, width, height)

  cxt.stroke()
  cxt.fill()
</script>
複製代碼

等價公式:

cxt.strokeStyle = 'red',
cxt.rect(50, 50, 200, 100)
cxt.stroke()

// 等價於
cxt.strokeStyle = 'red'
cxt.strokerect(50, 50, 200, 100)


// -----------------------------


cxt.fillStyle = 'hotpink'
cxt.rect(50, 50, 200, 100)
cxt.fill()

// 等價於
cxt.fillStyle = 'yellowgreen'
cxt.fillRect(50, 50, 200, 100)
複製代碼

使用 clearRect() 清空矩形

使用 clearRect() 方法可以清空指定區域。

clearRect(x, y, width, height)
複製代碼

其語法和創建 cxt.rect() 差不多。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.fillStyle = 'pink' // 設置填充顏色
  cxt.fillRect(50, 50, 200, 200) // 填充矩形

  cxt.clearRect(60, 60, 180, 90) // 清空矩形
</script>
複製代碼

清空畫布

canvas 畫布元素是矩形,所以可以通過下面的代碼把整個畫布清空掉。

// 省略部分代碼

cxt.clearRect(0, 0, cnv.width, cnv.height)
複製代碼

要清空的區域:從畫布左上角開始,直到畫布的寬和畫布的高爲止。

多邊形

Canvas 要畫多邊形,需要使用 moveTo() 、 lineTo() 和 closePath() 。

三角形

雖然三角形是常見圖形,但 canvas 並沒有提供類似 rect() 的方法來繪製三角形。

需要確定三角形 3 個點的座標位置,然後使用 stroke() 或者 fill() 方法生成三角形。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>

  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(50, 50)
  cxt.lineTo(200, 50)
  cxt.lineTo(200, 200)

  // 注意點:如果使用 lineTo 閉合圖形,是不能很好閉合拐角位的。
  cxt.lineTo(50, 50) // 閉合

  cxt.stroke()

</script>
複製代碼

注意,默認情況下不會自動從最後一個點連接到起點。最後一步需要設置一下 cxt.lineTo(50, 50) ,讓它與 cxt.moveTo(50, 50) 一樣。這樣可以讓路徑回到起點,形成一個閉合效果。

但這樣做其實是有點問題的,而且也比較麻煩,要記住起始點座標。

上面的閉合操作,如果遇到設置了 lineWidth 或者 lineJoin 就會有問題,比如:

// 省略部分代碼
cxt.lineWidth = 20
複製代碼

當線段變粗後,起始點和結束點的鏈接處,拐角就出現 “不正常” 現象。

如果需要真正閉合,可以使用 closePath() 方法。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(50, 50)
  cxt.lineTo(200, 50)
  cxt.lineTo(200, 200)
  // 手動閉合
  cxt.closePath()

  cxt.lineJoin = 'miter' // 線條連接的樣式。miter: 默認; bevel: 斜面; round: 圓角
  cxt.lineWidth = 20
  cxt.stroke()
</script>
複製代碼

使用 cxt.closePath() 可以自動將終點和起始點連接起來,此時看上去就正常多了。

菱形

有一組鄰邊相等的平行四邊形是菱形

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(150, 50)
  cxt.lineTo(250, 100)
  cxt.lineTo(150, 150)
  cxt.lineTo(50, 100)
  cxt.closePath()
  cxt.stroke()
</script>
複製代碼

要繪製直線類型的圖形,在草稿紙上標記出起始點和每個拐角的點,然後再連線即可。相對曲線圖形來說,直線圖形是比較容易的。

圓形

繪製圓形的方法是 arc()

語法:

arc(x, y, r, sAngle, eAngle,counterclockwise)
複製代碼

開始角度和結束角度,都是以弧度爲單位。例如 180° 就寫成 Math.PI ,360° 寫成 Math.PI * 2 ,以此類推。

在實際開發中,爲了讓自己或者別的開發者更容易看懂弧度的數值,1° 應該寫成 Math.PI / 180

注意:繪製圓形之前,必須先調用 beginPath() 方法!!!在繪製完成之後,還需要調用 closePath() 方法!!!

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.beginPath()
  cxt.arc(150, 150, 80, 0, 360 * Math.PI / 180)
  cxt.closePath()

  cxt.stroke()
</script>
複製代碼

半圓

如果使用 arc() 方法畫圓時,沒做到剛好繞完一週(360°)就直接閉合路徑,就會出現半圓的狀態。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.beginPath()
  cxt.arc(150, 150, 100, 0, 180 * Math.PI / 180) // 順時針
  cxt.closePath()

  cxt.stroke()
</script>
複製代碼

上面的代碼中,cxt.arc 最後一個參數沒傳,默認是 false ,所以是順時針繪製。

如果希望半圓的弧面在上方,可以將 cxt.arc 最後一個參數設置成 true

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.beginPath()
  cxt.arc(150, 150, 100, 0, 180 * Math.PI / 180, true)
  cxt.closePath()

  cxt.stroke()
</script>
複製代碼

弧線

使用 arc() 方法畫半圓時,如果最後不調用 closePath() 方法,就不會出現閉合路徑。也就是說,那是一條弧線。

在 canvas 中,畫弧線有 2 中方法:arc() 和 arcTo() 。

arc() 畫弧線

如果想畫一條 0° ~ 30° 的弧線,可以這樣寫

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.beginPath()
  cxt.arc(150, 150, 100, 0, 30 * Math.PI / 180)

  cxt.stroke()
</script>
複製代碼

原理如下圖所示,紅線代表畫出來的那條弧線。

arcTo() 畫弧線

arcTo() 的使用方法會更加複雜,如果初學看不太懂的話可以先跳過,看完後面的再回來補補。

語法:

arcTo(cx, cy, x2, y2, radius)
複製代碼

其中,(cx, cy) 也叫控制點,(x2, y2) 也叫結束點。

是不是有點奇怪,爲什麼沒有 x1 和 y1 ?

(x1, y1) 是開始點,通常是由 moveTo() 或者 lineTo() 提供。

arcTo() 方法利用 開始點、控制點和結束點形成的夾角,繪製一段與夾角的兩邊相切並且半徑爲 radius 的圓弧

舉個例子

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(40, 40)
  cxt.arcTo(120, 40, 120, 120, 80)

  cxt.stroke()
</script>
複製代碼

基礎樣式

前面學完基礎圖形,接下來可以開始瞭解一下如何設置元素的基礎樣式。

描邊 stroke()

前面的案例中,其實已經知道使用 stroke() 方法進行描邊了。這裏就不再多講這個方法。

線條寬度 lineWidth

lineWidth 默認值是 1 ,默認單位是 px

語法:

lineWidth = 線寬
複製代碼

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 線寬 10
  cxt.beginPath()
  cxt.moveTo(50, 50)
  cxt.lineTo(250, 50)
  cxt.lineWidth = 10 // 設置線寬
  cxt.stroke()

  // 線寬 20
  cxt.beginPath()
  cxt.moveTo(50, 150)
  cxt.lineTo(250, 150)
  cxt.lineWidth = 20 // 設置線寬
  cxt.stroke()

  // 線寬 30
  cxt.beginPath()
  cxt.moveTo(50, 250)
  cxt.lineTo(250, 250)
  cxt.lineWidth = 30 // 設置線寬
  cxt.stroke()
</script>
複製代碼

線條顏色 strokeStyle

使用 strokeStyle 可以設置線條顏色

語法:

strokeStyle = 顏色值
複製代碼

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.moveTo(50, 50)
  cxt.lineTo(250, 50)
  cxt.lineWidth = 20
  cxt.strokeStyle = 'pink' // 設置顏色
  cxt.stroke()
</script>
複製代碼

爲了展示方便,我將 lineWidth 設爲 20。

線帽 lineCap

線帽指的是線段的開始和結尾處的樣式,使用 lineCap 可以設置

語法:

lineCap = '屬性值'
複製代碼

屬性值包括:

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 設置線寬,方便演示
  cxt.lineWidth = 16

  // 默認線帽 butt
  cxt.beginPath()
  cxt.moveTo(50, 60)
  cxt.lineTo(250, 60)
  cxt.stroke()


  // 方形線帽 square
  cxt.beginPath()
  cxt.lineCap = 'square'
  cxt.moveTo(50, 150)
  cxt.lineTo(250, 150)
  cxt.stroke()


  // 圓形線帽 round
  cxt.beginPath()
  cxt.lineCap = 'round'
  cxt.moveTo(50, 250)
  cxt.lineTo(250, 250)
  cxt.stroke()
</script>
複製代碼

使用 square 和 round 的話,會使線條變得稍微長一點點,這是給線條增加線帽的部分,這個長度在日常開發中需要注意。

線帽只對線條的開始和結尾處產生作用,對拐角不會產生任何作用。

拐角樣式 lineJoin

如果需要設置拐角樣式,可以使用 lineJoin 。

語法:

lineJoin = '屬性值'
複製代碼

屬性值包括:

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')
  
  cxt.lineWidth = 20

  // 默認,尖角
  cxt.moveTo(50, 40)
  cxt.lineTo(200, 40)
  cxt.lineTo(200, 90)
  cxt.stroke()

  // 斜角 bevel
  cxt.beginPath()
  cxt.moveTo(50, 140)
  cxt.lineTo(200, 140)
  cxt.lineTo(200, 190)
  cxt.lineJoin = 'bevel'
  cxt.stroke()

  // 圓角 round
  cxt.beginPath()
  cxt.moveTo(50, 240)
  cxt.lineTo(200, 240)
  cxt.lineTo(200, 290)
  cxt.lineJoin = 'round'
  cxt.stroke()
</script>
複製代碼

虛線 setLineDash()

使用 setLineDash() 方法可以將描邊設置成虛線。

語法:

setLineDash([])
複製代碼

需要傳入一個數組,且元素是數值型。

虛線分 3 種情況

  1. 只傳 1 個值

  2. 有 2 個值

  3. 有 3 個以上的值

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.lineWidth = 20
  cxt.strokeStyle = 'pink'

  cxt.moveTo(50, 50)
  cxt.lineTo(200, 50)
  cxt.setLineDash([10]) // 只傳1個參數,實線與空白都是 10px
  cxt.stroke()


  cxt.beginPath()
  cxt.moveTo(50, 100)
  cxt.lineTo(200, 100)
  cxt.setLineDash([10, 20]) // 2個參數,此時,實線是 10px, 空白 20px
  cxt.stroke()


  cxt.beginPath()
  cxt.moveTo(50, 150)
  cxt.lineTo(200, 150)
  cxt.setLineDash([10, 20, 5]) // 傳3個以上的參數,此例:10px實線,20px空白,5px實線,10px空白,20px實線,5px空白 ……

  cxt.stroke()
</script>
複製代碼

此外,還可以始終 cxt.getLineDash() 獲取虛線不重複的距離;

用 cxt.lineDashOffset 設置虛線的偏移位。

填充

使用 fill() 可以填充圖形,根據前面的例子應該掌握瞭如何使用 fill()

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.fillStyle = 'pink'

  cxt.rect(50, 50, 200, 100)

  cxt.fill()
</script>
複製代碼

可以使用 fillStyle 設置填充顏色,默認是黑色。

非零環繞填充

在使用 fill() 方法填充時,需要注意一個規則:非零環繞填充

在使用 moveTo 和 lineTo 描述圖形時,如果是按順時針繪製,計數器會加 1;如果是逆時針,計數器會減 1。

當圖形所處的位置,計數器的結果爲 0 時,它就不會被填充。

這樣說有點複雜,先看看例子

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 外層矩形
  cxt.moveTo(50, 50)
  cxt.lineTo(250, 50)
  cxt.lineTo(250, 250)
  cxt.lineTo(50, 250)
  cxt.closePath()

  // 內層矩形
  cxt.moveTo(200, 100)
  cxt.lineTo(100, 100)
  cxt.lineTo(100, 200)
  cxt.lineTo(200, 200)
  cxt.closePath()
  cxt.fill()
</script>
複製代碼

請看看上面的代碼,我畫了 2 個矩形,它們都沒有用 beginPath() 方法開闢新路徑。

內層矩形是逆時針繪製的,所以內層的值是 -1 ,它又經過外層矩形,而外層矩形是順時針繪製,所以經過外層時值 +1,最終內層的值爲 0 ,所以不會被填充。

文本

Canvas 提供了一些操作文本的方法。

爲了方便演示,我們先了解一下在 Canvas 中如何給本文設置樣式。

樣式 font

和 CSS 設置 font 差不多,Canvas 也可以通過 font 設置樣式。

語法:

cxt.font = 'font-style font-variant font-weight font-size/line-height font-family'
複製代碼

如果需要設置字號 font-size,需要同事設置 font-family

cxt.font = '30px 宋體'
複製代碼

描邊 strokeText()

使用 strokeText() 方法進行文本描邊

語法:

strokeText(text, x, y, maxWidth)
複製代碼

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.font = '60px Arial' // 將字號設置成 60px,方便觀察
  cxt.strokeText('雷猴', 30, 90)
</script>
複製代碼

設置描邊顏色 strokeStyle

使用 strokeStyle 設置描邊顏色。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.font = '60px Arial' // 將字號設置成 60px,方便觀察
  cxt.strokeStyle = 'pink' // 設置文本描邊顏色
  cxt.strokeText('雷猴', 30, 90)
</script>
複製代碼

填充 fillText

使用 fillText() 可填充文本。

語法和 strokeText() 一樣。

fillText(text, x, y, maxWidth)
複製代碼

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.font = '60px Arial'
  cxt.fillText('雷猴', 30, 90)
</script>
複製代碼

設置填充顏色 fillStyle

使用 fillStyle 可以設置文本填充顏色。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  cxt.font = '60px Arial'
  cxt.fillStyle = 'pink'
  cxt.fillText('雷猴', 30, 90)
</script>
複製代碼

獲取文本長度 measureText()

measureText().width 方法可以獲取文本的長度,單位是 px 。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  let text = '雷猴'
  cxt.font = 'bold 40px Arial'
  cxt.fillText(text, 40, 80)

  console.log(cxt.measureText(text).width) // 80
</script>
複製代碼

水平對齊方式 textAlign

使用 textAlign 屬性可以設置文字的水平對齊方式,一共有 5 個值可選

紅線是輔助參考線。

<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 豎向的輔助線(參考線,在畫布中間)
  cxt.moveTo(200, 0)
  cxt.lineTo(200, 400)
  cxt.strokeStyle = 'red'
  cxt.stroke()

  cxt.font = '30px Arial'

  // 橫座標開始位對齊
  cxt.textAlign = 'start' // 默認值,
  cxt.fillText('雷猴 start', 200, 40)

  // 橫座標結束位對齊
  cxt.textAlign = 'end' // 結束對齊
  cxt.fillText('雷猴 end', 200, 100)

  // 左對齊
  cxt.textAlign = 'left' // 左對齊
  cxt.fillText('雷猴 left', 200, 160)

  // 右對齊
  cxt.textAlign = 'right' // 右對齊
  cxt.fillText('雷猴 right', 200, 220)

  // 居中對齊
  cxt.textAlign = 'center' // 右對齊
  cxt.fillText('雷猴 center', 200, 280)
</script>
複製代碼

從上面的例子看,start 和 left 的效果好像是一樣的,end 和 right 也好像是一樣的。

在大多數情況下,它們的確一樣。但在某些國家或者某些場合,閱讀文字的習慣是 從右往左 時,start 就和 right 一樣了,end 和 left 也一樣。這是需要注意的地方。

垂直對齊方式 textBaseline

使用 textBaseline 屬性可以設置文字的垂直對齊方式。

在使用 textBaseline 前,需要自行了解 css 的文本基線。

用一張網圖解釋一下基線

textBaseline 可選屬性:

紅線是輔助參考線。

<canvas id="c" width="800" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 橫向的輔助線(參考線,在畫布中間)
  cxt.moveTo(0, 150)
  cxt.lineTo(800, 150)
  cxt.strokeStyle = 'red'
  cxt.stroke()

  cxt.font = '20px Arial'

  // 默認 alphabetic
  cxt.textBaseline = 'alphabetic'
  cxt.fillText('雷猴 alphabetic', 10, 150)

  // 默認 top
  cxt.textBaseline = 'top'
  cxt.fillText('雷猴 top', 200, 150)

  // 默認 bottom
  cxt.textBaseline = 'bottom'
  cxt.fillText('雷猴 bottom', 320, 150)

  // 默認 middle
  cxt.textBaseline = 'middle'
  cxt.fillText('雷猴 middle', 480, 150)

  // 默認 hanging
  cxt.textBaseline = 'hanging'
  cxt.fillText('雷猴 hanging', 640, 150)
</script>
複製代碼

注意:在繪製文字的時候,默認是以文字的左下角作爲參考點進行繪製

圖片

在 Canvas 中可以使用 drawImage() 方法繪製圖片。

渲染圖片

渲染圖片的方式有 2 中,一種是在 JS 里加載圖片再渲染,另一種是把 DOM 裏的圖片拿到 canvas 裏渲染

渲染的語法:

drawImage(image, dx, dy)
複製代碼

JS 版

在 JS 里加載圖片並渲染,有以下幾個步驟:

  1. 創建 Image 對象

  2. 引入圖片

  3. 等待圖片加載完成

  4. 使用 drawImage() 方法渲染圖片

<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 1 創建 Image 對象
  const image = new Image()

  // 2 引入圖片
  image.src = './images/dog.jpg'

  // 3 等待圖片加載完成
  image.onload = () ={
    // 4 使用 drawImage() 方法渲染圖片
    cxt.drawImage(image, 30, 30)
  }
</script>
複製代碼

DOM 版

<style>
  #dogImg {
    display: none;
  }
</style>

<img src="./images/dog.jpg" id="dogImg"/>
<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  const image = document.getElementById('dogImg')

  cxt.drawImage(image, 70, 70)
</script>
複製代碼

因爲圖片是從 DOM 裏獲取到的,所以一般來說,只要在 window.onload 這個生命週期內使用 drawImage 都可以正常渲染圖片。

本例使用了 css 的方式,把圖片的 display 設置成 none 。因爲我不想被 <img> 影響到本例講解。

實際開發過程中按照實際情況設置即可。

設置圖片寬高

前面的例子都是直接加載圖片,圖片默認的寬高是多少就加載多少。

如果需要指定圖片寬高,可以在前面的基礎上再添加兩個參數:

drawImage(image, dx, dy, dw, dh)
複製代碼

image、 dx、 dy 的用法和前面一樣。

dw 用來定義圖片的寬度,dh 定義圖片的高度。

<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  const image = new Image()
  image.src = './images/dog.jpg'

  image.onload = () ={
    cxt.drawImage(image, 30, 30, 100, 100)
  }
</script>
複製代碼

我把圖片的尺寸設爲 100px * 100px,圖片看上去比之前就小了很多。

截取圖片

截圖圖片同樣使用drawImage() 方法,只不過傳入的參數數量比之前都多,而且順序也有點不一樣了。

drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
複製代碼

以上參數缺一不可

<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  const image = new Image()
  image.src = './images/dog.jpg'

  image.onload = () ={
    cxt.drawImage(image, 0, 0, 100, 100, 30, 30, 200, 200)
  }
</script>
複製代碼

總結

本文主要講解了在 Canvas 中繪製一些基礎圖形,還有一些基礎樣式設置。

還有更多高級的玩法會在之後的文章中講到,比如漸變、投影、濾鏡等等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6GQanSsI3ThZxXVIXBiwLg