fillStyle = color 設置圖形的填充顏色。
strokeStyle = color設 置圖形輪廓的顏色。
默認都是黑色的顏色和邊框
// 這些 fillStyle 的值均為 '橙色'
ctx.fillStyle ="orange";
ctx.fillStyle ="#FFA500";
ctx.fillStyle ="rgb(255,165,0)";
ctx.fillStyle ="rgba(255,165,0,1)";
通過for循環,利用i、j的漸變繪制漸變色
例子:functiondraw(){
var ctx = document.getElementById('canvas').getContext('2d');
for(var i=0;i<6;i++){
for(var j=0;j<6;j++){
ctx.fillStyle ='rgb('+ Math.floor(255-42.5*i)+','+
Math.floor(255-42.5*j)+',0)';
ctx.fillRect(j*25,i*25,25,25);
}
}}
效果:
例:functiondraw(){
var ctx = document.getElementById('canvas').getContext('2d');
for(var i=0;i<6;i++){
for(var j=0;j<6;j++){
ctx.strokeStyle ='rgb(0,'+ Math.floor(255-42.5*i)+','+
Math.floor(255-42.5*j)+')';
ctx.beginPath();
ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
ctx.stroke();
}
}
}
透明度 Transparency
// 指定透明顏色,用于描邊和填充樣式
ctx.strokeStyle ="rgba(255,0,0,0.5)";
ctx.fillStyle ="rgba(255,0,0,0.5)";
lineWidth = value設置線條寬度。
lineCap = type設置線條末端樣式。(butt默認,round圓角 和 square多出一個方塊效果)
lineJoin = type設定線條與線條間接合處的樣式。(round圓角,bevel角被磨平 和 miter直角。默認是 miter)
miterLimit = value限制當兩條線相交時交接處最大長度;所謂交接處長度(斜接長度)是指線條交接處內角頂點到外角頂點的長度。
例子:通過設置線條寬帶漸增畫圖
functiondraw(){
var ctx = document.getElementById('canvas').getContext('2d');
for(var i =0; i <10; i++){
ctx.lineWidth =1+i;
ctx.beginPath();
ctx.moveTo(5+i*14,5);
ctx.lineTo(5+i*14,140);
ctx.stroke();
}}
效果:
createLinearGradient(x1, y1, x2, y2) createLinearGradient 方法接受 4 個參數,表示漸變的起點 (x1,y1) 與終點 (x2,y2)。
createRadialGradient(x1, y1, r1, x2, y2, r2) createRadialGradient 方法接受 6 個參數,前三個定義一個以 (x1,y1) 為原點,半徑為 r1 的圓,后三個參數則定義另一個以 (x2,y2) 為原點,半徑為 r2 的圓。
var lineargradient = ctx.createLinearGradient(0,0,150,150);var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100);
創建出 canvasGradient 對象后,我們就可以用 addColorStop 方法給它上色了。
gradient.addColorStop(position, color)
addColorStop 方法接受 2 個參數,position 參數必須是一個 0.0 與 1.0 之間的數值,表示漸變中顏色所在的相對位置。例如,0.5 表示顏色會出現在正中間。color 參數必須是一個有效的 CSS 顏色值(如 #FFF, rgba(0,0,0,1),等等)。
你可以根據需要添加任意多個色標(color stops)。下面是最簡單的線性黑白漸變的例子。
var lineargradient = ctx.createLinearGradient(0,0,150,150);
lineargradient.addColorStop(0,'white');
lineargradient.addColorStop(1,'black');
createPattern(image, type)該方法接受兩個參數。Image 可以是一個 Image 對象的引用,或者另一個 canvas 對象。Type 必須是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat。
寫法:
var img =newImage();
img.src ='someimage.png';var ptrn = ctx.createPattern(img,'repeat');
案例:
functiondraw(){
var ctx = document.getElementById('canvas').getContext('2d');
// 創建新 image 對象,用作圖案
var img =newImage();
img.src ='images/wallpaper.png';
img.onload =function(){
// 創建圖案
var ptrn = ctx.createPattern(img,'repeat');
ctx.fillStyle = ptrn;
ctx.fillRect(0,0,150,150);
}}
shadowOffsetX = float
shadowOffsetX和shadowOffsetY用來設定陰影在 X 和 Y 軸的延伸距離,它們是不受變換矩陣所影響的。負值表示陰影會往上或左延伸,正值則表示會往下或右延伸,它們默認都為 0。
shadowOffsetY = float
shadowOffsetX和 shadowOffsetY用來設定陰影在 X 和 Y 軸的延伸距離,它們是不受變換矩陣所影響的。負值表示陰影會往上或左延伸,正值則表示會往下或右延伸,它們默認都為 0。
shadowBlur = float
shadowBlur 用于設定陰影的模糊程度,其數值并不跟像素數量掛鉤,也不受變換矩陣的影響,默認為 0。
shadowColor = color
shadowColor 是標準的 CSS 顏色值,用于設定陰影顏色效果,默認是全透明的黑色。
例:文字陰影的例子
functiondraw(){
var ctx = document.getElementById('canvas').getContext('2d');
ctx.shadowOffsetX =2;
ctx.shadowOffsetY =2;
ctx.shadowBlur =2;
ctx.shadowColor ="rgba(0, 0, 0, 0.5)";
ctx.font ="20px Times New Roman";
ctx.fillStyle ="Black";
ctx.fillText("Sample String",5,30);
未完待續...
點贊 + 關注 + 收藏 = 學會了
在前端領域,如果只是懂 Vue 或者 React ,未來在職場的競爭力可能會比較弱。
根據我多年在家待業經驗來看,前端未來在 數據可視化 和 AI 這兩個領域會比較香,而 Canvas 是數據可視化在前端方面的基礎技術。
本文就用光的速度將 canvas 給入門了。
01.gif
要入門一個技術,前期最重要是快!所以本文只講入門內容,能應付簡單項目。深入的知識點會在其他文章講解。
文章同時收錄于小程序-互聯網小兵,不止于前端,收各平臺優質熱門的技術文章(后端、移動端、算法、人工智能...),大家支持支持,點擊前往體驗!
CanvasSVG用JS動態生成元素(一個HTML元素)用XML描述元素(類似HTML元素那樣,可用多個元素來描述一個圖形)位圖(受屏幕分辨率影響)矢量圖(不受屏幕分辨率影響)不支持事件支持事件數據發生變化需要重繪不需要重繪
就上面的描述而言可能有點難懂,你可以打開 AntV 旗下的圖形編輯引擎做對比。G6[1] 是使用 canvas 開發的,X6[2] 是使用 svg 開發的。
我的建議是:如果要展示的數據量比較大,比如一條數據就是一個元素節點,那使用 canvas 會比較合適;如果用戶操作的交互比較多,而且對清晰度有要求(矢量圖),那么使用 svg 會比較合適。
學習前端一定要動手敲代碼,然后看效果展示。
起步階段會用幾句代碼說明 canvas 如何使用,本例會畫一條直線。
02.png
<!-- 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 方法暫時可以不用管,它們的作用是繪制圖形,這些方法在后面會講到~
canvas 有 默認的 寬度(300px) 和 高度(150px)
如果不在 canvas 上設置寬高,那 canvas 元素的默認寬度是300px,默認高度是150px。
canvas 元素提供了 width 和 height 兩個屬性,可設置它的寬高。
需要注意的是,這兩個屬性只需傳入數值,不需要傳入單位(比如 px 等)。
<canvas width="600" height="400"></canvas>
復制代碼
使用 css 設置 canvas 的寬高,會出現 內容被拉伸 的后果!!!
03.png
<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。
最后出現的效果如上圖所示。
線條的默認寬度是 1px ,默認顏色是黑色。
但由于默認情況下 canvas 會將線條的中心點和像素的底部對齊,所以會導致顯示效果是 2px 和非純黑色問題。
暫時只有 IE 9 以上才支持 canvas 。但好消息是 IE 已經有自己的墓碑了。
如需兼容 IE 7 和 8 ,可以使用 ExplorerCanvas [3]。但即使是使用了 ExplorerCanvas 仍然會有所限制,比如無法使用 fillText() 方法等。
在繪制基礎圖形之前,需要先搞清除 Canvas 使用的坐標系。
Canvas 使用的是 W3C 坐標系 ,也就是遵循我們屏幕、報紙的閱讀習慣,從上往下,從左往右。
04.jpg
W3C 坐標系 和 數學直角坐標系 的 X軸 是一樣的,只是 Y軸 的反向相反。
W3C 坐標系 的 Y軸 正方向向下。
最簡單的起步方式是畫一條直線。這里所說的 “直線” 是幾何學里的 “線段” 的意思。
需要用到這3個方法:
起步階段可以先這樣理解。
05.png
<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>
復制代碼
上面的代碼所呈現的效果,可以看下圖解釋(手不太聰明,畫得不是很標準,希望能看懂)
06.jpg
如需畫多條直線,可以用會上面那幾個方法。
07.png
<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 和非純黑色問題。
08.jpg
上圖每個格子代表 1px。
線的中心點會和畫布像素點的底部對齊,所以會線中間是黑色的,但由于一個像素就不能再切割了,所以會有半個像素被染色,就變成了淺灰色。
所以如果你設置的 Y軸 值是一個整數,就會出現上面那種情況。
09.png
<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>
復制代碼
開辟新路徑的方法:
在繪制多條線段的同時,還要設置線段樣式,通常需要開辟新路徑。
要不然樣式之間會相互污染。
比如這樣
10.png
<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件事:
如果上面2步卻了其中1步都會有影響。
11.png
<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() ,后面的線段不會影響前面的線段。
12.png
<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>
復制代碼
這個情況會反過來,后面的線能影響前面的線。
13.png
<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() 的同時,也各自設置樣式。這樣就能做到相互不影響了。
14.png
<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() 方法可以繪制折線。
15.png
<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() 等方法可以直接生成矩形。
可以使用前面畫線段的方法來繪制矩形
16.png
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>
復制代碼
上面的代碼幾個點分別對應下圖。
17.jpg
18.png
<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>
復制代碼
上面的代碼可以這樣理解
19.jpg
fillRect() 和 strokeRect() 方法差不多,但 fillRect() 的作用是填充。
需要注意的是,fillStyle 必須寫在 fillRect() 之前,不然樣式不生效。
20.png
<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() 會產生描邊和填充的效果
21.png
<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() 和 fillRect() 、strokeRect() 的用法差不多,唯一的區別是:
strokeRect() 和 fillRect() 這兩個方法調用后會立即繪制;rect() 方法被調用后,不會立刻繪制矩形,而是需要調用 stroke() 或 fill() 輔助渲染。
22.png
<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(x, y, width, height)
復制代碼
其語法和創建 cxt.rect() 差不多。
23.png
<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() 方法生成三角形。
24.png
<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 就會有問題,比如:
25.png
// 省略部分代碼
cxt.lineWidth = 20
復制代碼
當線段變粗后,起始點和結束點的鏈接處,拐角就出現“不正常”現象。
如果需要真正閉合,可以使用 closePath() 方法。
26.png
<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() 可以自動將終點和起始點連接起來,此時看上去就正常多了。
有一組鄰邊相等的平行四邊形是菱形
27.png
<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)
復制代碼
28.jpg
開始角度和結束角度,都是以弧度為單位。例如 180°就寫成 Math.PI ,360°寫成 Math.PI * 2 ,以此類推。
在實際開發中,為了讓自己或者別的開發者更容易看懂弧度的數值,1°應該寫成 Math.PI / 180。
注意:繪制圓形之前,必須先調用 beginPath() 方法!!!在繪制完成之后,還需要調用 closePath() 方法!!!
29.png
<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°)就直接閉合路徑,就會出現半圓的狀態。
30.png
<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 ,所以是順時針繪制。
31.jpg
如果希望半圓的弧面在上方,可以將 cxt.arc 最后一個參數設置成 true
32.png
<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() 。
如果想畫一條 0° ~ 30° 的弧線,可以這樣寫
33.png
<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>
復制代碼
原理如下圖所示,紅線代表畫出來的那條弧線。
34.jpg
arcTo() 的使用方法會更加復雜,如果初學看不太懂的話可以先跳過,看完后面的再回來補補。
語法:
arcTo(cx, cy, x2, y2, radius)
復制代碼
其中,(cx, cy) 也叫控制點,(x2, y2) 也叫結束點。
是不是有點奇怪,為什么沒有 x1 和 y1 ?
(x1, y1) 是開始點,通常是由 moveTo() 或者 lineTo() 提供。
arcTo() 方法利用 開始點、控制點和結束點形成的家教,繪制一段與家教的兩邊相切并且半徑為 radius 的圓弧。
35.jpg
舉個例子
36.png
<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() 方法進行描邊了。這里就不再多講這個方法。
lineWidth 默認值是 1 ,默認單位是 px。
語法:
lineWidth = 線寬
復制代碼
37.png
<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 = 顏色值
復制代碼
38.png
<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 = '屬性值'
復制代碼
屬性值包括:
39.png
<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 = '屬性值'
復制代碼
屬性值包括:
40.png
<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([])
復制代碼
需要傳入一個數組,且元素是數值型。
虛線分3種情況
41.png
<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()
42.png
<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時,它就不會被填充。
這樣說有點復雜,先看看例子
43.png
<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() 方法開辟新路徑。
44.png
內層矩形是逆時針繪制的,所以內層的值是 -1 ,它又經過外層矩形,而外層矩形是順時針繪制,所以經過外層時值 +1,最終內層的值為 0 ,所以不會被填充。
Canvas 提供了一些操作文本的方法。
為了方便演示,我們先了解一下在 Canvas 中如何給本文設置樣式。
和 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(text, x, y, maxWidth)
復制代碼
45.png
<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 設置描邊顏色。
46.png
<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() 可填充文本。
語法和 strokeText() 一樣。
fillText(text, x, y, maxWidth)
復制代碼
47.png
<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 可以設置文本填充顏色。
48.png
<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().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 屬性可以設置文字的水平對齊方式,一共有5個值可選
49.png
紅線是輔助參考線。
<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 前,需要自行了解 css 的文本基線。
50.png
用一張網圖解釋一下基線
textBaseline 可選屬性:
51.png
紅線是輔助參考線。
<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 里加載圖片并渲染,有以下幾個步驟:
52.png
<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>
復制代碼
53.png
<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 定義圖片的高度。
54.png
<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)
復制代碼
以上參數缺一不可
55.png
<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 中繪制一些基礎圖形,還有一些基礎樣式設置。
還有更多高級的玩法會在之后的文章中講到,比如漸變、投影、濾鏡等等。
德育處主任,https://juejin.cn/post/7116784455561248775
條APP上有個截圖分享功能,就是把文章轉成一張圖片,然后分享到各渠道中去,如微信、QQ等,非常實用,因此,打算就這項功能自己封裝為一個組件ImageGenerator,后期專門進行圖片生成,html是其中的一個源。
頭條截圖分享的實際效果圖
目前前端使用較多的html轉圖片的工具是Html2Canvas,考慮技術儲備和問題討論的充裕性,決定封裝一下這個工具。步驟如下:
按照官方要求,做安裝操作:
npm
npm install html2canvas
我當時下載的是1.4.1的版本。
這個工具有自身的一些限制,使用時要注意:
[1] 并非真正的截圖軟件,而是根據DOM繪制出來的,其繪制能力,完全依賴于工具對DOM和對應屬性的支持和理解;
[2] 因為使用了Canvas支持,生成圖片的區域不能再有Canvas應用,否則會干擾工具的生成,不能保證生成預期,因此,如果使用了Canvas圖表的應用這個工具不推薦使用
這個很簡單,這里就是封裝一個組件,用于后期引入html之外的源生成圖片,同時也做一下圖片的統一顯示,從而和系統整體的設計進行配合。大致的實現思路如下:
上圖,我們引入了工具本身,并設置的結果的顯示區。生成的結果將以節點的方式注入 #image-box 中。
上圖,封裝了一個方法,用途是利用Html2Canvas工具獲得圖片,這里我們引入了一個組件的數據imageData用以存儲和干預生成結果。在這里,我把ImageGenerator封裝為全局組件。
我們在文章的尾部加入一個share功能,點擊彈出分享設置的彈窗,實際效果如下圖所示:
以上技術實現比較簡單,這里就不進行贅述了。上圖中,我們設置了一個生成圖像按鈕,點擊該按鈕則可以觸發我們組件中的對應操作。關鍵思路包括:
【1】這里設置了一個封裝組件shareHandler,封裝了前導模塊和imageGenerator,這兩個模塊的顯示通過一個開關進行控制,該開關則通過圖像生成成功事件進行賦值,這樣的話,我們可以實現圖片生成后,不再顯示前導模塊,而是顯示圖片結果,即ImageGenerator。
【2】這里有一個比較關鍵的操作是shareHandler通過觸發事件將轉換器發射到文章轉換現場,為什么用事件,還是那句話,事件對于解耦和消除組件依賴是最自然的實現。注意,這里我把imageGenerator通過引用的方式作為參數傳出了,這樣的好處是事件將轉換器代入了轉換現場,并可以攜帶回現場轉換結果。
【3】在文章查看器,solutionViewer中,自然會訂閱事件、事件處理和取消訂閱。注意這里的事件處理,實際上是調用了轉換器中我們封裝的函數,參數則是現場取得的,這里的機制很簡單,定義要轉換div的id,作為參數傳入函數。
那么,點擊圖像后,我們可以看到效果圖:
點擊右鍵另存圖像,我們可以獲得一張png格式的圖片,至于后續對下載和到粘貼板的支持,大家可以自行研究和實現。
實現過程中有幾個注意事項:
【1】Canvas返回時,其長寬都是按照實際大小生成的,而我們的例子中,則要根據右抽屜式的彈窗做width=100%,height=auto的處理,這個要如何實現,就是要通過我們在imageGenerator中引入的imageData。
【2】我們的文章顯示中,引入了文件管理的微服務,因此,文章中圖片的鏈接都是跨域的,所以,必須打開html2Canvas的跨域選項,在封裝的組件里,我是通過一共一個defaultOptions來實現這一點的。
這個選項可以在轉換場景提供,也可提供一系列的默認值,最常用的除跨域外,還有是否允許log輸出等開關,大家感興趣可以自行查閱html2Canvas的官網。
內容比較簡單,大家如果有這個應用場景,可以參考實現一下,有問題歡迎大家隨時交流。謝謝大家的支持。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。