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
:
- 紋理是從畫布的(0,0)點開始無縮放渲染的;
- 設定
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);
複製程式碼
注意:當畫布中包含圖片時,此圖片必須是允許跨域的,否則呼叫toDataURL 會報錯!