本文簡介 點贊 + 關註 + 收藏 = 學會了 在前端領域,如果只是懂 Vue 或者 React ,未來在職場的競爭力可能會比較弱。 根據我多年在家待業經驗來看,前端未來在 數據可視化 和 AI 這兩個領域會比較香,而 Canvas 是數據可視化在前端方面的基礎技術。 本文就用光的速度將 canva ...
本文簡介
點贊 + 關註 + 收藏 = 學會了
在前端領域,如果只是懂 Vue
或者 React
,未來在職場的競爭力可能會比較弱。
根據我多年在家待業經驗來看,前端未來在 數據可視化
和 AI
這兩個領域會比較香,而 Canvas
是數據可視化在前端方面的基礎技術。
本文就用光的速度將 canvas
給入門了。
要入門一個技術,前期最重要是快!所以本文只講入門內容,能應付簡單項目。深入的知識點會在其他文章講解。
Canvas 是什麼?
Canvas
中文名叫 “畫布”,是HTML5
新增的一個標簽。Canvas
允許開發者通過JS
在這個標簽上繪製各種圖案。Canvas
擁有多種繪製路徑、矩形、圓形、字元以及圖片的方法。Canvas
在某些情況下可以 “代替” 圖片。Canvas
可用於動畫、游戲、數據可視化、圖片編輯器、實時視頻處理等領域。
Canvas 和 SVG 的區別
Canvas | SVG |
---|---|
用JS動態生成元素(一個HTML元素) | 用XML描述元素(類似HTML元素那樣,可用多個元素來描述一個圖形) |
點陣圖(受屏幕解析度影響) | 矢量圖(不受屏幕解析度影響) |
不支持事件 | 支持事件 |
數據發生變化需要重繪 | 不需要重繪 |
就上面的描述而言可能有點難懂,你可以打開 AntV
旗下的圖形編輯引擎做對比。G6 是使用 canvas
開發的,X6 是使用 svg
開發的。
我的建議是:如果要展示的數據量比較大,比如一條數據就是一個元素節點,那使用 canvas
會比較合適;如果用戶操作的交互比較多,而且對清晰度有要求(矢量圖),那麼使用 svg
會比較合適。
起步
學習前端一定要動手敲代碼,然後看效果展示。
起步階段會用幾句代碼說明 canvas
如何使用,本例會畫一條直線。
畫條直線
- 在
HTML
中創建canvas
元素 - 通過
js
獲取canvas
標簽 - 從
canvas
標簽中獲取到繪圖工具 - 通過繪圖工具,在
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。
- 如果使用
css
修改canvas
的寬高(比如本例變成 400px * 400px),那寬度就由 300px 拉伸到 400px,高度由 150px 拉伸到 400px。 - 使用
js
獲取canvas
的寬高,此時返回的是canvas
的預設值。
最後出現的效果如上圖所示。
4、線條預設寬度和顏色
線條的預設寬度是 1px
,預設顏色是黑色。
但由於預設情況下 canvas
會將線條的中心點和像素的底部對齊,所以會導致顯示效果是 2px
和非純黑色問題。
5、IE相容性高
暫時只有 IE 9
以上才支持 canvas
。但好消息是 IE
已經有自己的墓碑了。
如需相容 IE 7 和 8
,可以使用 ExplorerCanvas 。但即使是使用了 ExplorerCanvas
仍然會有所限制,比如無法使用 fillText()
方法等。
基礎圖形
坐標系
在繪製基礎圖形之前,需要先搞清除 Canvas
使用的坐標系。
Canvas
使用的是 W3C 坐標系 ,也就是遵循我們屏幕、報紙的閱讀習慣,從上往下,從左往右。
W3C 坐標系 和 數學直角坐標系 的 X軸
是一樣的,只是 Y軸
的反向相反。
W3C 坐標系 的 Y軸
正方向向下。
直線
一條直線
最簡單的起步方式是畫一條直線。這裡所說的 “直線” 是幾何學里的 “線段” 的意思。
需要用到這3個方法:
moveTo(x1, y1)
:起點坐標 (x, y)lineTo(x2, y2)
:下一個點的坐標 (x, y)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軸
值是一個整數,就會出現上面那種情況。
設置樣式
lineWidth
:線的粗細strokeStyle
:線的顏色lineCap
:線帽:預設:butt
; 圓形:round
; 方形:square
<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>
新開路徑
開闢新路徑的方法:
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.stroke()
</script>
如果不想相互污染,需要做2件事:
- 使用
beginPath()
方法,重新開一個路徑 - 設置新線段的樣式(必須項)
如果上面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()
描邊矩形
strokeStyle
:設置描邊的屬性(顏色、漸變、圖案)strokeRect(x, y, width, height)
:描邊矩形(x和y是矩形左上角起點;width 和 height 是矩形的寬高)strokeStyle
必須寫在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)
x
和y
: 圓心坐標r
: 半徑sAngle
: 開始角度eAngle
: 結束角度counterclockwise
: 繪製方向(true: 逆時針; false: 順時針),預設 false
開始角度和結束角度,都是以弧度為單位。例如 180°就寫成 Math.PI
,360°寫成 Math.PI * 2
,以此類推。
在實際開發中,為了讓自己或者別的開發者更容易看懂弧度的數值,1°應該寫成 Math.PI / 180
。
- 100°:
100 * Math.PI / 180
- 110°:
110 * Math.PI / 180
- 241°:
241 * 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)
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
: 結束點的縱坐標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, 80, 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 = '屬性值'
屬性值包括:
butt
: 預設值,無線帽square
: 方形線帽round
: 圓形線帽
<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 = '屬性值'
屬性值包括:
miter
: 預設值,尖角round
: 圓角bevel
: 斜角
<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個值
- 有2個值
- 有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)
text
: 字元串,要繪製的內容x
: 橫坐標,文本左邊要對齊的坐標(預設左對齊)y
: 縱坐標,文本底邊要對齊的坐標maxWidth
: 可選參數,表示文本渲染的最大寬度(px),如果文本超出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個值可選
start
: 預設。在指定位置的橫坐標開始。end
: 在指定坐標的橫坐標結束。left
: 左對齊。right
: 右對齊。center
: 居中對齊。
紅線是輔助參考線。
<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
可選屬性:
alphabetic
: 預設。文本基線是普通的字母基線。top
: 文本基線是em
方框的頂端。bottom
: 文本基線是em
方框的底端。middle
: 文本基線是em
方框的正中。hanging
: 文本基線是懸掛基線。
紅線是輔助參考線。
<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)
image
: 要渲染的圖片對象。dx
: 圖片左上角的橫坐標位置。dy
: 圖片左上角的縱坐標位置。
JS版
在 JS
裡加載圖片並渲染,有以下幾個步驟:
- 創建
Image
對象 - 引入圖片
- 等待圖片載入完成
- 使用
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
用來定義圖片的寬度,dy
定義圖片的高度。
<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)
以上參數缺一不可
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
中繪製一些基礎圖形,還有一些基礎樣式設置。
還有更多高級的玩法會在之後的文章中講到,比如漸變、投影、濾鏡等等。
代碼倉庫
推薦閱讀