canvas 2D 繪圖上下文
????2D 繪圖上下文提供了繪制2D圖形的方法存璃,包括矩形仑荐、弧形和路徑。2D上下文的坐標(biāo)原點(diǎn)(0纵东,0)在<canvs>
元素的左上角粘招。所有的坐標(biāo)值都相對于該點(diǎn)計算,因此x坐標(biāo)向右增長偎球,y坐標(biāo)向下增長洒扎。默認(rèn)情況下, width和height表示兩個方向上像素的最大值衰絮。
填充和描邊
????2D上下文有兩個基本繪制操作袍冷;填充和描邊。填充以指定樣式(顏色猫牡、漸變或圖像)自動填充形狀胡诗,而描邊只為圖形邊界著色。大多數(shù)的2D上下文操作有填充和描邊的變體淌友,顯示效果取決于兩個屬性
fillStyle和strokeStyle煌恢。
????這兩個屬性可以是字符串、漸變對象或圖案對象震庭,默認(rèn)值都為“#000000”.字符串可以表示顏色值瑰抵,可以是CSS支持的任意格式:名稱、十六進(jìn)制代碼器联、rgb二汛、rgba、hsl或hsla拨拓。比如
let canvas = document.getElementById('canvas');
canvas.width = 1920;
canvas.height = 1080;
if (canvas.getContext) {
let context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 100);
context.lineTo(400, 100);
context.lineTo(400, 400);
// context.lineTo(100, 400);
context.closePath();
context.width = 2;
context.fillStyle = '#aaa';
context.strokeStyle = 'red';
context.fill();
context.stroke();
}
在這里設(shè)置之后肴颊,所有與描邊和填充相關(guān)的操作都會使用這兩種樣式,除非再次修改千元。這兩個屬性也可以是漸變或圖案苫昌。
繪制矩形
????矩形是唯一一個可以在2D繪圖上下文種hi之的形狀。與繪制矩形相關(guān)的方法有3個:fillRect()幸海、strokeRect()祟身、clearRect()。這些方法都接受4個參數(shù):矩形x坐標(biāo)物独、
矩形y坐標(biāo)袜硫、矩形寬度和矩形高度。這幾個參數(shù)的單位都是像素挡篓。 ????fillRect()方法用于以指定顏色在畫布上繪制并填充矩形婉陷。填充的顏色使用fillStyle屬性指定。
let canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
// 確保瀏覽器支持<canvas>
if (canvas.getContext) {
let context = canvas.getContext("2d");
/*
* 引自 MDN 文檔
*/
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 繪制半透明藍(lán)色矩形
context.fillStyle = "rgba(0,0,255,0.5)";
context.fillRect(30, 30, 50, 50);
}
????以上代碼先將fillStyle設(shè)置為紅色并在坐標(biāo)點(diǎn)(10,10)繪制了一個寬高均為50像素的矩形官研。接著秽澳,使用rgba()格式將fillStyle設(shè)置為半透明藍(lán)色,并繪制了另一個
與第一個部分重疊的矩形戏羽。結(jié)果就是可以透過藍(lán)色矩形看到紅色矩形
????stroke()方法使用通過strokeStyle屬性指定的顏色繪制矩形輪廓担神。下面是一個例子:
let canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
// 確保瀏覽器支持<canvas>
if (canvas.getContext) {
let context = canvas.getContext("2d");
/*
* 引自 MDN 文檔
*/
// 繪制紅色輪廓的矩形
context.strokeStyle = "#ff0000";
context.strokeRect(10, 10, 50, 50);
// 繪制半透明藍(lán)色輪廓的矩形
context.strokeStyle = "rgba(0,0,255,0.5)";
context.strokeRect(30, 30, 50, 50);
}
????以上代碼同樣繪制了兩個重疊的矩形,不過只有輪廓始花,而不是實心的
注意:描邊寬度由lineWidth屬性控制妄讯,它可以是任意整數(shù)值。類似地酷宵,lineCap屬性控制線條端點(diǎn)的形狀["butt"(平頭)亥贸、"round"(出圓頭)或"square"(出方頭)],
而lineJoin屬性控制線條交點(diǎn)的形狀["round"(圓轉(zhuǎn))浇垦、"bevel"(取平)或"miter"(出尖)]炕置。
????使用clearRect() 方法可以擦除畫布中的某個區(qū)域。該方法用于把繪圖上下文中的某個區(qū)域變透明男韧。通過先繪制形狀再擦除指定區(qū)域朴摊,可以創(chuàng)建出有趣的效果,比如從已有矩形
中開個孔煌抒。來看下面的例子:
let canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
// 確保瀏覽器支持<canvas>
if (canvas.getContext) {
let context = canvas.getContext("2d");
/*
* 引自 MDN 文檔
*/
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 繪制半透明藍(lán)色矩形
context.fillStyle = "rgba(0,0,255,0.5)";
context.fillRect(30, 30, 50, 50);
//在前兩個矩形重疊的區(qū)域擦除一個矩形區(qū)域
context.clearRect(40, 40, 10, 10);
}
????以上代碼在兩個矩形重疊的區(qū)域擦除了一個小矩形仍劈,如下圖所示:
繪制路徑
????2D繪制上下文支持很多在畫布上繪制路徑的方法。通過路徑可以創(chuàng)建復(fù)雜的形狀和線條寡壮。要繪制路徑贩疙,必須首先調(diào)用beginPath()方法 以表示要開始繪制新路徑。然后况既,再調(diào)用下列方法來繪制路徑这溅。
arc(x,y,radius,startAngle,endAngle,counterclockwise): 以坐標(biāo)(x,y)為圓心,以radius為半徑繪制一條弧線棒仍,起始角度為startAngle悲靴,結(jié)束角度為endAngle(都是弧度)
。最后一個 參數(shù)counterclockwise表示是否逆時針計算起始角度和結(jié)束角度(默認(rèn)為順時針)arcTo(x1,y1,x2,y2,radius): 以給定半徑radius莫其,經(jīng)由(x1,y1)繪制一條從上一點(diǎn)到(x2,y2)的弧線癞尚。
bezierCurveTo(c1x,c1y,c2x,c2y,x,y): 以(c1x,c1y)和(c2x,c2y)為控制點(diǎn)耸三,繪制一條從上一點(diǎn)到(x,y)的弧線(三次貝塞爾曲線)。
lineTo(x,y): 繪制一條從上一點(diǎn)到(x,y)的直線浇揩。
moveTo(x,y): 不繪制線條仪壮,只把繪制光標(biāo)移動到(x,y).
quadraticCurveTo(cx,cy,x,y): 以(cx,cy)為控制點(diǎn),繪制一條從上一點(diǎn)到(x,y)的弧線(二次貝塞爾曲線)
rect(x,y,width,height): 給定寬度和高度在坐標(biāo)點(diǎn)(x,y)繪制一個矩形胳徽。這個方法與strokeRect()和fillRect()的區(qū)別在于积锅,它創(chuàng)建的是一條路徑,而不是獨(dú)立的圖形养盗。
????創(chuàng)建路徑之后缚陷,可以使用closePath()方法來繪制一條返回起點(diǎn)的線。如果路徑已經(jīng)完成往核,則既可以指定fillStyle屬性并調(diào)用fill()方法
來填充路徑箫爷,也可以指定strokeStyle屬性并調(diào)用stroke()方法來描畫路徑,還可以調(diào)用clip()方法基于已有路徑創(chuàng)建一個剪切區(qū)域铆铆。
????下面這個例子使用前面提到的方法繪制了一個不帶數(shù)字的表盤:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>18.3.3 繪制路徑</title>
<style>
#canvas {
/*border: 1px solid #aaaaaa;*/
display: block;
margin: 50px auto;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
if (canvas.getContext) {
let context = canvas.getContext('2d');
//創(chuàng)建路徑
context.beginPath();
//繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
//繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI);
//繪制分針
context.moveTo(100, 100);
context.lineTo(100, 15);
//繪制時針
context.moveTo(100, 100);
context.lineTo(35, 100);
//描畫路徑
context.stroke();
}
</script>
</body>
</html>
????這個例子使用arc()繪制了兩個圓形蝶缀,一個外圓和一個內(nèi)圓,以構(gòu)成表盤的邊框薄货。外圓半徑99像素翁都,原點(diǎn)為(100,100),也就是畫布的中心。要繪制完整的圓形谅猾,必須從 0 弧度繪制到 2Π 弧度(
使用數(shù)學(xué)常量Math.PI)柄慰。而在繪制內(nèi)圓之前,必須先把路徑移動到內(nèi)圓上的一點(diǎn)税娜,以避免繪制出多余的線條坐搔。第二次調(diào)用arc()時使用了稍小一點(diǎn)的半徑,以呈現(xiàn)邊框效果敬矩。然后概行,在組合運(yùn)用moveTo()
和lineTo()分別繪制分針和時針,最后一步是調(diào)用stroke(),得到如下圖所示的圖像弧岳。
????路徑是2D上下文的主要繪圖機(jī)制凳忙,為繪制結(jié)果提供了很多控制。因為路徑經(jīng)常被使用禽炬,所以也有一個isPointInPath()方法涧卵,接受x軸和y軸作為參數(shù)。這個方法用于確定指定的點(diǎn)是否在路徑上腹尖,
可以在關(guān)閉路徑前隨時調(diào)用柳恐,比如:
let context = canvas.getContext('2d');
//創(chuàng)建路徑
context.beginPath();
//繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
//繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI);
//繪制分針
context.moveTo(100, 100);
context.lineTo(100, 15);
//繪制時針
context.moveTo(100, 100);
context.lineTo(35, 100);
if (context.isPointInPath(100, 100)) {
alert("Point (100,100) is in the path.")
}
//描畫路徑
context.stroke();
???? 2D上下文的路徑API非常可靠,可用于創(chuàng)建設(shè)計各種填充樣式乐设、描述樣式的復(fù)雜圖像讼庇。
繪制文本
????文本和圖像混合也是常見的繪圖需求,因此2D繪圖上下文還提供了繪制文本的方法伤提,即fillText()和strokeText()巫俺。這兩個方法都接受4個參數(shù):要繪制的字符串认烁、
x坐標(biāo)肿男、y坐標(biāo)和可選的最大像素寬度。并且却嗡,這兩個方法最終繪制的結(jié)果都取決于以下3個屬性舶沛。
font:以CSS語法指定的字體樣式、大小窗价、字體族等如庭,比如 "10px Arial"。
textAlign: 指定文本的對齊方式撼港,可能的值包括 "start" 坪它、"end" 、"left"帝牡、 "right" 和 "center"往毡。推薦使用 "start" 和 "end" ,不使用 "left" 和 "right"
靶溜,因為前者無論在從左到從右 書寫的語言還是從右到左書寫的語言中含義都更明確开瞭。textBaseLine: 指定文本的基線,可能的值包括 "top"罩息、 "handing" 嗤详、"middle"、 "alphabetic"瓷炮、 "ideographic" 和 "bottom"葱色。
????這些屬性都有相應(yīng)的默認(rèn)值,因此沒必要每次繪制文本時都設(shè)置它們娘香。fillText()方法使用fillStyle屬性繪制文本苍狰,而strokeText()方法使用strokeStyle屬性
。通常fillText()方法是使用最多的茅主,因為它模擬了在網(wǎng)頁中渲染文本舞痰。例如,在表盤頂部繪制數(shù)字 "12":
context.font = 'bold 14px Arial';
context.textAlign = "center";
context.textBaseline = 'middle';
context.fillText('12', 100, 15);
????結(jié)果就得到了如下圖所示的圖像:
????因為把textAlign設(shè)置為了"center",把textBaseLine設(shè)置為了 "middle"诀姚,所以(100,20)表示文本水平和垂直中心點(diǎn)的坐標(biāo)响牛。如果textAlign是"
start", 那么x坐標(biāo)在從左到右書寫的語言中表示文本的左側(cè)坐標(biāo),而"end"會讓x坐標(biāo)在從左到右書寫的語言中表示文本的右側(cè)坐標(biāo)。例如:
//正常
context.font = 'bold 14px Arial';
context.textAlign = "center";
context.textBaseline = 'middle';
context.fillText('12', 100, 15);
//與開頭對齊
context.textAlign = 'start';
context.fillText('12', 100, 40);
//與末尾對齊
context.textAlign = 'end';
context.fillText('12', 100, 60);
????字符串 "12" 被繪制了3次呀打,每次使用的坐標(biāo)都一樣矢赁,但textAlign值不同。為了讓每個字符串不至于重疊贬丛,每次繪制的y坐標(biāo)都會設(shè)置的大一些撩银。結(jié)果就是上圖所示的圖像。
????因為表盤中垂直的線條是居中的豺憔,所以文本的對齊方式就一目了然了额获。類似地,通過修改textBaseline屬性恭应,可以改變文本的垂直對齊方式抄邀。 比如,設(shè)置為"top"
意味著y坐標(biāo)表示文本頂部昼榛,bottom表示文本底部境肾,"hanging"、"alphabetic"胆屿、"ideographic"分別引用字體中特定的基準(zhǔn)點(diǎn)奥喻。
????由于繪制文本很復(fù)雜,特別是想把文本繪制到特定區(qū)域的時候非迹,因此2D上下文提供了 用于輔助確定文本大小的measureText()方法环鲤。這個方法接受一個參數(shù),
即要繪制的文本彻秆,然后返回一個TextMetrics對象楔绞,這個返回的對象目前只有一個屬性width,不過將來應(yīng)該會增加更多的度量指標(biāo)唇兑。
????measureText()方法使用font酒朵、textAlign和textBaseline屬性當(dāng)前的值計算繪制指定文本后的大小。例如扎附,假設(shè)要把文本"Hello world蔫耽!"
放到一個140像素寬的矩形 中,可以使用以下代碼留夜,從100像素的字體大小開始計算匙铡,不斷遞減,知道文本大小合適:
let fontSize = 100;
context.font = fontSize + "px Arial";
while (context.measureText("Hello World!").width > 140) {
fontSize--;
context.font = fontSize + "px Arial";
}
console.log(fontSize)
context.fillText("Hello World!", 100, 25);
context.fillText("Font size is " + fontSize + "px", 100, 50,1000);
????fillText()和strokeText()方法還有第四個參數(shù)碍粥,即文本的最大寬度鳖眼。這個參數(shù)是可選的,如果調(diào)用fillText和strokeText()時提供了此參數(shù)嚼摩,但要
繪制的字符串超過了最大寬度限制钦讳,則文本會以正確的高度繪制矿瘦,這時候字符會被水平壓縮,以達(dá)到限定寬度愿卒。
????繪制文本是一項比較復(fù)雜的操作缚去,因此支持<canvas>
元素的瀏覽器不一定全部實現(xiàn)了相關(guān)的文本繪制API。
變換
????上下文變換可以操作繪制在畫布上的圖像琼开。2D繪圖上下文支持所有常見的繪制變換易结。 ????以下方法可用于繪制上下文的變換矩陣
- rotate(angle): 圍繞原點(diǎn)把圖像旋轉(zhuǎn)angle弧度
- scale(scaleX,scaleY):通過在x軸乘以scaleX、在y軸乘以scaleY來縮放圖像柜候。scaleX和scaleY的默認(rèn)值都是1.0
- translate(x,y): 把原點(diǎn)移動到(x,y)搞动。執(zhí)行這個操作后,坐標(biāo)(0,0)就會編程(x,y).
- transform(m1_1,m1_2,m2_1,m2_2,dx,dy):像下面這樣通過矩陣乘法直接修改矩陣改橘。 m1_1,m1_2 dx m2_1,m2_2 dy 0 0 1
- setTransform(m1_1,m1_2,m2_1,m2_2,dx,dy): 把矩陣重置為默認(rèn)值滋尉,再以傳入的參數(shù)調(diào)用transform()。
????變換可以簡單飞主,也可以復(fù)雜。例如高诺,在前面繪制表盤的例子中碌识,如果把坐標(biāo)原點(diǎn)移動到表盤中心,那再繪制表針就非常簡單那了:
(function () {
const canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
if (canvas.getContext) {
let context = canvas.getContext('2d');
//創(chuàng)建路徑
context.beginPath();
//繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
//繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
//移動原點(diǎn)到表盤中心
context.translate(100, 100);
//繪制分針
context.moveTo(0, 0);
context.lineTo(0, -85);
//繪制時針
context.moveTo(0, 0);
context.lineTo(-80, 0);
//描畫路徑
context.stroke();
}
})();
????把原點(diǎn)移動到(100,100)虱而,也就是表盤的中心后筏餐,要繪制表針只需要簡單的數(shù)學(xué)計算即可。這是因為所有計算都是基于(100,100)了牡拇。當(dāng)然魁瞪,也可以使用rotate()方法來轉(zhuǎn)動指針:
(function () {
const canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
if (canvas.getContext) {
let context = canvas.getContext('2d');
//創(chuàng)建路徑
context.beginPath();
//繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
//繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
//移動原點(diǎn)到表盤中心
context.translate(100, 100);
//旋轉(zhuǎn)表針
context.rotate(1);
//繪制分針
context.moveTo(0, 0);
context.lineTo(0, -85);
//繪制時針
context.moveTo(0, 0);
context.lineTo(-80, 0);
//描畫路徑
context.stroke();
}
})();
????因為原點(diǎn)已經(jīng)移動到表盤中心,所以旋轉(zhuǎn)就是以該點(diǎn)為圓心的惠呼。這相當(dāng)于把表針一頭固定在表盤中心导俘,然后向右撥了一個弧度。結(jié)果如圖18-8所示剔蹋。
????所有這些變換旅薄,包括fillStyle和strokeStyle屬性,會一直保留在上下文中泣崩,直到再次修改它們少梁。雖然沒有辦法明確地將所有值都重置為默認(rèn)值,但是有兩個方法可以幫我們跟蹤變化矫付,可以調(diào)用save()
方法凯沪。 調(diào)用這個方法后,所有這一時刻的設(shè)置會被暫時放到一個暫存棧中买优。保存之后妨马,可以繼續(xù)修改上下文樟遣。而在需要恢復(fù)之前的上下文時,可以調(diào)用restore()中的方法身笤。這個方法會從暫存棧中取出并恢復(fù)之前保存的設(shè)置豹悬。多次調(diào)用save()方法可以
在暫存棧中存儲多套設(shè)置,然后通過restore()可以系統(tǒng)地恢復(fù)液荸。下面來看一個例子:
(function () {
const canvas = document.getElementById('canvas');
canvas.width = 200;
canvas.height = 200;
if (canvas.getContext) {
let context = canvas.getContext('2d');
context.fillStyle = '#ff0000';
context.save();
context.fillStyle = '#00ff00';
context.translate(100, 100);
context.save();
context.fillStyle = '#0000ff';
//在(100,100)繪制藍(lán)色矩形
context.fillRect(0, 0, 100, 200);
context.restore();
//在(100,100)繪制綠色矩形
context.fillRect(10, 10, 100, 200);
context.restore();
//在(0,0)繪制紅色矩形
context.fillRect(0, 0, 100, 200);
}
})();
????以上代碼先將fillStyle設(shè)置為紅色瞻佛,然后調(diào)用save()。接著娇钱,將fillStyle修改為綠色伤柄,坐標(biāo)移動到(100,100),并再次調(diào)用save()
,保存設(shè)置。隨后文搂,將fillStyle屬性設(shè)置為藍(lán)色并繪制一個矩形适刀。 因為此時坐標(biāo)被移動了,所以繪制矩形的坐標(biāo)實際上是(100,100)煤蹭。在調(diào)用restore()之后笔喉,fillStyle恢復(fù)為綠色,因此這一次
繪制的矩形是綠色的硝皂。而繪制的矩形的坐標(biāo)是(100,100),因為變換仍然在起作用常挚。再次調(diào)用restore()之后,變化被移除稽物,fillStyle也恢復(fù)為紅色奄毡。繪制最后一個矩形的坐標(biāo)變成了(0,0)
????注意,save()方法只保存應(yīng)用到繪圖上下文的設(shè)置和變換贝或,不保存繪圖上下文的內(nèi)容吼过。
繪制圖像
????2D繪圖上下文內(nèi)置支持操作圖像。如果想把現(xiàn)有圖像繪制到畫布上咪奖,可以使用drawImage()方法盗忱。
這個方法可以接受3組不同的參數(shù),并產(chǎn)生不同的結(jié)果赡艰。最簡單的調(diào)用時傳入一個HTML的<img>
元素售淡,以及表示繪制目標(biāo)的x和y坐標(biāo),結(jié)果是把圖像繪制到指定位置慷垮。比如:
let image = document.images[0];
//表示在坐標(biāo)(0,0)的位置繪制
context.drawImage(image, 0, 0);
????以上代碼獲取了文本中的第一個圖像揖闸,然后在畫布上的坐標(biāo)(0,0)處將它繪制了出來。繪制出來的圖像與原來的圖像一樣大料身。如果想改變所繪制圖像的大小
可以再傳入另外一個參數(shù):目標(biāo)寬度和目標(biāo)高度汤纸。這里的縮放只影響繪制的圖像,不影響上下文的變換矩陣芹血。比如下面的例子:
let image = document.images[0];
//表示在坐標(biāo)(0,0)的位置繪制
context.drawImage(image, 0, 0贮泞,1920, 1080
)
;
????執(zhí)行之后楞慈,圖像會被縮放到1920像素寬、1080像素高啃擦。
????還可以只把圖像繪制到上下文中的一個區(qū)域囊蓝。此時,需要給drawImage()提供9個參數(shù):要繪制的圖像令蛉、源圖像x坐標(biāo)聚霜、源圖像y坐標(biāo)、源圖像寬度珠叔、源圖像高度
目標(biāo)區(qū)域x坐標(biāo)蝎宇、目標(biāo)區(qū)域y坐標(biāo)、目標(biāo)區(qū)域?qū)挾群湍繕?biāo)區(qū)域高度祷安。這個重載后的drawImage()方法可以實現(xiàn)最大限制的控制姥芥,
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
????最終,原始圖像中只有一部分會繪制到畫布上汇鞭。這一部分從(0,10)開始凉唐,50像素寬、50像素高虱咧。而繪制到畫布上時熊榛,會從(0,100)開始,變成40像素寬腕巡、60像素高。
????第一個參數(shù)除了可以是HTML的<img>元素血筑,還可以是另一個<canvas>元素绘沉,這樣就會把另一個畫布的內(nèi)容繪制到當(dāng)前畫布上。
????結(jié)合其他一些方法豺总,drawImage()方法可以方便地實現(xiàn)常見的圖像操作车伞。操作的結(jié)果可以使用toDataURL()方法獲取