canvas-座標系、圓角矩形、紋理、剪裁
畫布大小
:由canvas標籤上設定的width,height決定,預設是 300 x 150,這也是畫布座標系x軸和y軸的最大值,超過這兩個值,則意味著超過了畫布大小,超過的部分自然不會生效。
canvas標籤大小
:由css樣式的width,height決定,預設是畫布的大小。
特殊情況: 畫布大小和canvas標籤大小不相等的時候,畫布會被縮放到跟標籤大小一樣。 縮放不是等比例的,並且縮放完成後,畫布的座標系不變 。因此最好把canvas標籤的css大小和canvas畫布大小設定為一致。
預設畫布(300 * 150)
<canvas id="canvas1" style={{ width: '100px', height: '100px' }}></canvas> 複製程式碼
ctx.moveTo(0, 0); ctx.lineTo(300, 150); ctx.stroke(); 複製程式碼

可以看到從(0,0)到(300, 150)這條線是從左上角到右下角的,可見畫布被不等比例縮放到跟標籤一樣大,同時座標系還是畫布的大小:300 * 150。
當畫布較小,而canvas標籤比較大的時候,圖形就會被放大,變形變模糊:
<canvas id="canvas2" width={10} height={20} style={{ width: '100px', height: '100px' }}></canvas> 複製程式碼
ctx.moveTo(0, 0); ctx.lineTo(10, 20); ctx.stroke(); 複製程式碼

在2倍屏和3倍屏上,可把畫布大小設定成標籤大小的2倍和3倍,這樣可以實現1px細線的效果,同時使線條更細膩
<canvas id="canvas3" width={200} height={200} style={{ width: '100px', height: '100px' }}></canvas> 複製程式碼
ctx.moveTo(0, 0); ctx.lineTo(200, 200); ctx.stroke(); 複製程式碼

圓角矩形
要實現圓角矩形,先來了解一下畫圓的api。
arc(x, y, r, sAngle, eAngle, counterclockwise)
引數 | 描述 |
---|---|
x | 圓的中心的 x 座標。 |
y | 圓的中心的 y 座標。 |
x | 圓的半徑。 |
sAngle | 起始角,以弧度計。(弧的圓形的三點鐘位置是 0 度)。 |
eAngle | 結束角,以弧度計。 |
counterclockwise | 可選。規定應該逆時針還是順時針繪圖,預設值false。false = 順時針,true = 逆時針。 |

畫圓
<canvas id="canvas11" width={100} height={100}></canvas> 複製程式碼
ctx.arc(50, 50, 20, 0, 2 * Math.PI); 複製程式碼

圓弧
<canvas id="canvas10" width={100} height={100}></canvas> 複製程式碼
ctx.arc(20, 20, 20, Math.PI, 1.5 * Math.PI);// 左上角 ctx.arc(80, 20, 20, 1.5 * Math.PI, 2 * Math.PI);// 右上角 ctx.arc(20, 80, 20, 0.5 * Math.PI, Math.PI);// 左下角 ctx.arc(80, 80, 20, 0, 0.5 * Math.PI);// 右下角 複製程式碼

closePath
方法會連線路徑的特點,即可畫出圓角矩形了。來封裝一個畫圓角矩形的函式:
const drawRoundedRect = (ctx, x, y, width, height, radius, type) => { ctx.moveTo(x, y + radius); ctx.beginPath(); ctx.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI); ctx.arc(x + width - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI); ctx.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * Math.PI); ctx.arc(x + radius, y + height - radius, radius, 0.5 * Math.PI, Math.PI); ctx.closePath(); const method = type || 'stroke';// 預設描邊,傳入fill即可填充矩形 ctx[method](); }; drawRoundedRect(ctx, 20, 20, 50, 50, 10); 複製程式碼

紋理
createPattern(img, "repeat|repeat-x|repeat-y|no-repeat")
引數 | 描述 |
---|---|
img | 規定要使用的圖片、畫布或視訊元素。 |
repeat | 預設。該模式在水平和垂直方向重複。 |
repeat-x | 該模式只在水平方向重複。 |
repeat-y | 該模式只在垂直方向重複。 |
no-repeat | 該模式只顯示一次(不重複)。 |
原圖

嘗試如下程式碼:
<canvas id="canvas13" width={400} height={300}></canvas> 複製程式碼
const img = new Image(); img.onload = () => { console.log('catImg', img.width, img.height); const pat = ctx.createPattern(img, 'no-repeat'); ctx.fillStyle = pat; drawRoundedRect(ctx, 130, 30, 250, 260, 10, 'fill'); }; img.src = catImgSrc; 複製程式碼

從結果裡發現兩個問題:
- 圖片的左上角和矩形的左上角不在同一點上;
- 矩形貌似少了右邊和下邊的部分。
接下來嘗試修改程式碼裡的 no-repeat
為 repeat
:

結合這兩個結果和上面的問題,總結出紋理的如下特點:
no-repeat
這意味著我們不能自由的使用紋理來實現圓角圖片的效果。 接下來了解一下canvas提供的專門用於圖片剪裁的api。
剪裁
剪裁分為畫布的剪裁 clip
和圖片的剪裁 drawImage
,先來介紹一下圖片的剪裁。
drawImage(img, sx, sy, swidth, sheight, x, y, width, height)
引數 | 描述 |
---|---|
img | 規定要使用的影象、畫布或視訊。 |
sx | 可選。開始剪下的 x 座標位置。 |
sy | 可選。開始剪下的 y 座標位置。 |
swidth | 可選。被剪下影象的寬度。 |
sheight | 可選。被剪下影象的高度。 |
x | 在畫布上放置影象的 x 座標位置。 |
y | 在畫布上放置影象的 y 座標位置。 |
width | 可選。要使用的影象的寬度。(伸展或縮小影象) |
height | 可選。要使用的影象的高度。(伸展或縮小影象) |
drawImage可接受3、5、9個引數。 接下來介紹一下分別傳這幾個引數的表現,先看原圖:
大小:1080 * 720

三個引數
會被當做:img、x、y。圖片不會縮放和剪裁,直接渲染到畫布上,超過畫布的區域被隱藏,也可理解成超過畫布的區域被剪掉了。
const img = new Image(); img.onload = () => { console.log('img:', img.width, img.height); ctx.drawImage(img, 10, 20); }; img.src = imgSrc; 複製程式碼

九個引數
按照上面表格的定義進行剪裁和縮放。
const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0, 500, 500, 30, 30, 150, 150); }; img.src = imgSrc; 複製程式碼

五個引數
img、x、y、width、height,此時圖片不會被剪裁,而是直接縮放到目標寬高,且是不等比例的。
const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0, 150, 150); }; img.src = imgSrc; 複製程式碼

接下來傳入9個引數,剪裁圖片的寬高等於圖片的寬高,來驗證和5個引數是一樣的效果:
const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0, 1080, 720, 0, 0, 150, 150); }; img.src = imgSrc; 複製程式碼

另外,從原圖上剪裁的時候,不要超過圖片的寬高,否則會出現空白。
drawImage這個api能實現把圖片剪裁成直角矩形,卻不能實現圓角的效果。而上面的紋理方法,能實現圓角圖片,但效果不是十分理想,畢竟它是用來做紋理的,而不是圖片剪裁。接下來再瞭解一個api:clip
,通過它配合drawImage,能實現把圖片剪裁成圓角矩形、圓形、甚至任意形狀。
clip()
clip()
方法就是把畫布中的某個區域臨時剪切出來,剪下之前要定義這個區域的路徑。剪下以後,所有的繪製只有落在這個區域裡才會生效,在這個區域外的不會生效。之所以說“臨時”,是因為如果在剪下之前呼叫了 save()
方法,則畫布狀態會被儲存下來,之後呼叫 restore()
方法即可恢復之前的狀態,即 clip 的那個區域的限制不再繼續生效,而之前落在區域外的繪製也不會因為 restore 而被繪製出來。 嘗試如下程式碼:
<canvas id="canvas15" width={150} height={150}></canvas> 複製程式碼
// 定義一個區域 drawRoundedRect(ctx, 20, 20, 100, 100, 10); const img = new Image(); img.onload = () => { ctx.save(); ctx.clip(); ctx.drawImage(img, 0, 0, 100, 100); }; img.src = imgSrc; 複製程式碼
效果:

看到這個效果很興奮,接下來只要把圖片的目標區域先從畫布上剪下下來,再呼叫drawImage去繪製圖片,則圖片就會變成想要的形狀。至於原始圖片,則可以通過 drawImage
先剪裁一個想要的區域,再進行繪製。
從原圖上剪裁出一部分,再繪製成兩個圓角的圖片:
<canvas id="canvas16" width={300} height={200}></canvas> 複製程式碼
const img = new Image(); img.onload = () => { ctx.save(); ctx.strokeStyle = '#fff'; drawRoundedRect(ctx, 20, 20, 100, 100, 10); ctx.clip(); ctx.drawImage(img, 500, 100, 500, 500, 20, 20, 100, 100); ctx.restore(); ctx.strokeStyle = '#fff'; drawRoundedRect(ctx, 150, 20, 100, 100, 5); ctx.clip(); ctx.drawImage(img, 500, 100, 500, 500, 150, 20, 100, 100); }; img.src = imgSrc; 複製程式碼
效果:

drawImage
:
const drawRoundedImage = (ctx, radius, img, sx, sy, swidth, sheight, x, y, width, height) => { ctx.save(); ctx.moveTo(x, y + radius); ctx.beginPath(); if (width === height && radius >= width / 2) { ctx.arc(x + radius, y + radius, radius, 0, 2 * Math.PI); } else { ctx.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI); ctx.arc(x + width - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI); ctx.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * Math.PI); ctx.arc(x + radius, y + height - radius, radius, 0.5 * Math.PI, Math.PI); } ctx.closePath(); ctx.clip(); ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height); ctx.restore(); }; const img = new Image(); img.onload = () => { drawRoundedImage(ctx, 10, img, (1080 - 720) / 2, 0, 720, 720, 10, 10, 180, 180); }; img.src = imgSrc; 複製程式碼
效果:

如果把上面的繪製路徑部分提出來當成引數傳入,則可實現使用者自定義圖形然後將圖片剪裁成該形狀的功能:
const drawImageToWhatYouWant = (ctx, getPath, img, sx, sy, swidth, sheight, x, y, width, height) => { ctx.save(); getPath(ctx);// 自定義圖形的路徑 ctx.clip(); ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height); ctx.restore(); }; 複製程式碼
儲存圖片
toDataURL([type, encoderOptions])
引數 | 描述 |
---|---|
type | 圖片格式,預設為image/png |
encoderOptions | 在指定圖片格式為 image/jpeg 或 image/webp的情況下,可以從 0 到 1 的區間內選擇圖片的質量。如果超出取值範圍,將會使用預設值 0.92。其他引數會被忽略。 |
呼叫: const imgStr = canvas.toDataURL("image/jpeg", 1.0); 複製程式碼