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

来源:https://www.cnblogs.com/k21vin/archive/2022/07/23/16509946.html
-Advertisement-
Play Games

本文簡介 點贊 + 關註 + 收藏 = 學會了 在前端領域,如果只是懂 Vue 或者 React ,未來在職場的競爭力可能會比較弱。 根據我多年在家待業經驗來看,前端未來在 數據可視化 和 AI 這兩個領域會比較香,而 Canvas 是數據可視化在前端方面的基礎技術。 本文就用光的速度將 canva ...


本文簡介

點贊 + 關註 + 收藏 = 學會了


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

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

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

file

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



Canvas 是什麼?

  • Canvas 中文名叫 “畫布”,是 HTML5 新增的一個標簽。
  • Canvas 允許開發者通過 JS在這個標簽上繪製各種圖案。
  • Canvas 擁有多種繪製路徑、矩形、圓形、字元以及圖片的方法。
  • Canvas 在某些情況下可以 “代替” 圖片。
  • Canvas 可用於動畫、游戲、數據可視化、圖片編輯器、實時視頻處理等領域。


Canvas 和 SVG 的區別

Canvas SVG
用JS動態生成元素(一個HTML元素) 用XML描述元素(類似HTML元素那樣,可用多個元素來描述一個圖形)
點陣圖(受屏幕解析度影響) 矢量圖(不受屏幕解析度影響)
不支持事件 支持事件
數據發生變化需要重繪 不需要重繪

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


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



起步

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

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


畫條直線

  1. HTML 中創建 canvas 元素
  2. 通過 js 獲取 canvas 標簽
  3. canvas 標簽中獲取到繪圖工具
  4. 通過繪圖工具,在 canvas 標簽上繪製圖形

file

<!-- 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>

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


註意點

1、預設寬高

canvas預設的 寬度(300px) 和 高度(150px)

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


2、設置 canvas 寬高

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

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

<canvas width="600" height="400"></canvas>

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

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

file

<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 。但即使是使用了 ExplorerCanvas 仍然會有所限制,比如無法使用 fillText() 方法等。



基礎圖形

坐標系

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

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

file

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

W3C 坐標系Y軸 正方向向下。


直線

一條直線

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

需要用到這3個方法:

  1. moveTo(x1, y1):起點坐標 (x, y)
  2. lineTo(x2, y2):下一個點的坐標 (x, y)
  3. stroke():將所有坐標用一條線連起來

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

file

<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>

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

file


多條直線

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

file

<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 和非純黑色問題。

file

上圖每個格子代表 1px

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

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


設置樣式

  • lineWidth:線的粗細
  • strokeStyle:線的顏色
  • lineCap:線帽:預設: butt; 圓形: round; 方形: square

file

<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()

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

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

比如這樣

file

<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()

file

<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() ,後面的線段不會影響前面的線段。

file

<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() 的情況

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

file

<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() 的同時,也各自設置樣式。這樣就能做到相互不影響了。

file

<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() 方法可以繪製折線。

file

<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() 等方法可以直接生成矩形。


使用線段描繪矩形

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

file

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>

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

file


使用 strokeRect() 描邊矩形

  • strokeStyle:設置描邊的屬性(顏色、漸變、圖案)
  • strokeRect(x, y, width, height):描邊矩形(x和y是矩形左上角起點;width 和 height 是矩形的寬高)
  • strokeStyle 必須寫在 strokeRect() 前面,不然樣式不生效。

file

<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>

上面的代碼可以這樣理解

file


使用 fillRect() 填充矩形

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

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

file

<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() 會產生描邊和填充的效果

file

<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() 輔助渲染。


file

<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() 差不多。


file

<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() 方法生成三角形。


file

<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 就會有問題,比如:

file

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

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


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

file

<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() 可以自動將終點和起始點連接起來,此時看上去就正常多了。


菱形

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

file

<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)
  • xy: 圓心坐標
  • r: 半徑
  • sAngle: 開始角度
  • eAngle: 結束角度
  • counterclockwise: 繪製方向(true: 逆時針; false: 順時針),預設 false

file

開始角度和結束角度,都是以弧度為單位。例如 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() 方法!!!


file

<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°)就直接閉合路徑,就會出現半圓的狀態。

file

<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 ,所以是順時針繪製。

file


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

file

<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° 的弧線,可以這樣寫

file

<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>

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

file


arcTo() 畫弧線

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

語法:

arcTo(cx, cy, x2, y2, radius)
  • cx: 兩切線交點的橫坐標
  • cy: 兩切線交點的縱坐標
  • x2: 結束點的橫坐標
  • y2: 結束點的縱坐標
  • radius: 半徑

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

是不是有點奇怪,為什麼沒有 x1y1

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


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

file


舉個例子

file

<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 = 線寬

file

<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 = 顏色值

file

<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: 圓形線帽

file

<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>

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

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


拐角樣式 lineJoin

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

語法:

lineJoin = '屬性值'

屬性值包括:

  • miter: 預設值,尖角
  • round: 圓角
  • bevel: 斜角

file

<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個以上的值

file

<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()


file

<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() 方法填充時,需要註意一個規則:非零環繞填充

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

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


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

file

<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() 方法開闢新路徑。

file

內層矩形是逆時針繪製的,所以內層的值是 -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 設置的值,文本會被壓縮。

file

<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 設置描邊顏色。


file

<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)

file

<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 可以設置文本填充顏色。


file

<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: 居中對齊。

file

紅線是輔助參考線。

<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>

從上面的例子看,startleft 的效果好像是一樣的,endright 也好像是一樣的。

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


垂直對齊方式 textBaseline

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

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

file

用一張網圖解釋一下基線


textBaseline 可選屬性:

  • alphabetic: 預設。文本基線是普通的字母基線。
  • top: 文本基線是 em 方框的頂端。
  • bottom: 文本基線是 em 方框的底端。
  • middle: 文本基線是 em 方框的正中。
  • hanging: 文本基線是懸掛基線。

file

紅線是輔助參考線。

<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 裡加載圖片並渲染,有以下幾個步驟:

  1. 創建 Image 對象
  2. 引入圖片
  3. 等待圖片載入完成
  4. 使用 drawImage() 方法渲染圖片

file

<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版

file

<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 定義圖片的高度。


file

<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: 圖片高度

file

<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 中繪製一些基礎圖形,還有一些基礎樣式設置。

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



代碼倉庫

雷猴 Canvas



推薦閱讀


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、商品輪播圖 1-1、整體流程 1、swiper組件用來放置swiper-item組件 2、swiper-item組件用來放置圖片 3、image組件顯示輪播圖圖片 4、wx:for列表迴圈,設置每一個圖片 5、雲資料庫請求數據完成動態響應 1-2、根據微信開發官方文檔來設計商品輪播圖 1、從官方 ...
  • 前言 剛開始使用three.js時會不太熟悉,想寫一些項目增加理解,網上翻了好多文章,不是畫立方體就是畫三角形,最後偶然看到這個網站,十分炫酷。 我們也許沒那麼牛逼,但我們可以整個簡略版的太陽系來練練手,加強理解,增加熟練度。 實現目標 1、繪製宇宙背景。 2、添加宇宙星辰。 3、繪製行星軌道。 4 ...
  • 發現問題 通過vue建立的項目使用electron-builder打包(不會eletron打包vue項目的看這裡)出exe後發現名字就直接是項目文件夾的名字,但此時想自定義漢字名稱,通過嘗試直接修改package.json中的name後發現此法僅限於修改為英文名稱,漢字會報錯無法打包。 踩坑過程 在 ...
  • @(文章目錄) 提示:本文僅供學習交流,請勿用於非法活動! 前言 本文大概內容: 例如:隨著elementui的廣泛應用,用戶的的操作越來越簡化。本文主要針對用戶下拉框選項需要請求後臺數據獲得,而且後面的下拉框選項需要在前面的多個條件選擇基礎上,再通過請求後態數據才能獲得。而期間用戶如何連一個提交操 ...
  • @(文章目錄) 提示:本文僅供學習交流,請勿用於非法活動! 前言 本文大概內容: 例如:隨著ElementUI前後端交互的技術的更新,用戶的的體驗越來越好。本文主要針對用戶在保持原頁面結構,再添加另一個頁面,並可以按需要調整兩個頁面之間的比例大小. 一、ElementUI如何在原有頁面添加另外一個頁 ...
  • #CSS樣式快速入門 ##前言 前端基礎的博客主要分為HTML、CSS和JavaScript,本類博客主要用於記錄博主的學習過程和分享學習經驗,由於博主學識淺薄,經驗不足,難免會出現錯誤,歡迎大家提出問題。 註:前端基礎的文章參考於b站up主遇見狂神說的前端視頻,文章中的源碼筆記等資源放在百度網盤了 ...
  • 1. 預期效果 當數據變動時,觸發自定義的回調函數。 2. 思路 對對象 object 的 setter 進行設置,使 setter 在賦值之後執行回調函數 callback()。 3.細節 3.1 設置 setter 和 getter JS提供了 [Object.defineProperty()] ...
  • 有些效果無法在這兒體現,如果想看完整的效果,請移步個人站點。 原文鏈接:基於 Hexo 從零開始搭建個人博客(五) 閱讀本篇前,請先閱讀前幾篇文章: 基於 Hexo 從零開始搭建個人博客(一) 基於 Hexo 從零開始搭建個人博客(二) 基於 Hexo 從零開始搭建個人博客(三) 基於 Hexo 從 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...