把網頁當畫布揮灑的 Canvas 03:操作座標系

Canvas 繪圖固然厲害,但每次繪圖都要算座標點,太累了吧?

所以,我們要學會操作座標系統!


為什麼需要操作座標系?

如同前言所述,當我們在 Canvas 繪製圖形時,如果沒有先把座標軸畫出來,其實很難憑空想像繪圖座標。除此之外,當我們遇到需要重複繪製的圖形難以計算的角度或是繪製圖形的相對位置時,固定的座標系統也會顯得綁手綁腳。

因此,學會操作座標系統可以幫助我們提升繪圖的彈性與效率,使用上也較為直覺,因為我們只要思考如何移動座標系幫助我們繪圖


畫布座標系的操作

畫布座標系統的操作,就如同 CSS 的 transform 屬性,有三種變形方式,分別是 translate(x, y)rotate(deg) 以及 scale(x, y) 。 

Canvas 畫布座標系操作

translate()

對畫布座標系目前的位置進行偏移, dx 為水平偏移量, dy 為垂直偏移量。

1
ctx.translate(dx, dy)

rotate()

以畫布座標系原點為中心旋轉,單位為 Math.PI

1
ctx.translate(angle)

scale()

以畫布座標系原點為中心縮放, dx 為水平縮放量, dy 為垂直縮放量。

1
ctx.translate(dx, dy)


setTransform() 設定矩陣

setTransform(a, b, c, d, e, f) 可以傳入 6 個參數,分別代表:

  • a :水平縮放(若值為 1 代表沒有縮放)
  • b :垂直傾斜
  • c :水平傾斜
  • d :垂直縮放(若值為 1 代表沒有縮放)
  • e :水平偏移
  • f :垂直偏移

重設畫布座標系:ctx.setTransform(1, 0, 0, 1, 0, 0)

或傳入一個矩陣(Matrix)

setTransform 矩陣設定畫布座標系


實際範例

實作範例在 CodePen ,有興趣的朋友可以自己先試著畫畫看。

初始化 Canvas :

1
2
3
4
5
6
7
8
9
10
const canvas = document.getElementById('my-canvas')
const ctx = canvas.getContext('2d')

// 事先宣告常用的變數
const blockWidth = 200
const PI = Math.PI
const PI2 = Math.PI * 2

canvas.width = blockWidth
canvas.height = blockWidth

透過 translate 繪製黑白相間的西洋棋盤:

1
2
3
4
5
6
7
8
9
10
11
for(let i = 0 ; i < 10 ; i++) {
for(let j = 0 ; j < 10 ; j++) {
ctx.fillStyle = i % 2 ? (j % 2 ? 'black' : 'white') : (j % 2 ? 'white' : 'black')
ctx.fillRect(0, 0, 20, 20)
ctx.translate(20, 0)
}
// 重設畫布座標系
ctx.setTransform(1, 0, 0, 1, 0, 0)
// 垂直偏移畫布座標系 20 單位
ctx.translate(0, (i + 1) * 20)
}

透過 rotate 繪製十二邊形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ctx.setTransform(1, 0, 0, 1, 0, 0)
// 偏移畫布座標至第二個 Block
ctx.translate(blockWidth, 0)
// 偏移畫布座標至第二個 Block 中心
ctx.translate(blockWidth / 2, blockWidth / 2)
ctx.beginPath()
ctx.moveTo(0, 80)
for(let i = 0 ; i < 12 ; i++) {
ctx.lineTo(0, 80)
ctx.rotate(PI2 / 12) // 每畫一次畫布旋轉 360 / 12 = 30 度
}
ctx.closePath()
ctx.lineWidth = 2.5
ctx.strokeStyle = 'white'
ctx.stroke()

透過 scalerotate 繪製螺旋:

1
2
3
4
5
6
7
8
9
10
11
12
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.translate(blockWidth * 2, 0)
ctx.translate(blockWidth / 2, blockWidth / 2)
ctx.fillStyle = 'white'
// 總共畫 60 個圓
for(let i = 0 ; i < 60 ; i++) {
ctx.beginPath() // 每畫一個圓都必須重置繪圖
ctx.arc(0, -80, 10, 0, PI2)
ctx.fill()
ctx.rotate(PI2 / 12)
ctx.scale(1 - i / 80, 1 - i / 80) // 每畫完一個圓就縮小畫布座標系
}

本篇實作範例


參考資料

  1. 動畫互動網頁特效入門(JS/CANVAS):5-2 畫布的座標系操作
把網頁當畫布揮灑的 Canvas 04:狀態儲存與還原 把網頁當畫布揮灑的 Canvas 02:動畫原理

評論

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×