[轉(zhuǎn)帖]學(xué)習(xí) HTML5 Canvas 這一篇文章就夠了
liguoquan
2023年11月28日 11:52
本文熱度 612
:學(xué)習(xí) HTML5 Canvas 這一篇文章就夠了
一、canvas 簡介 <canvas>
是 HTML5
新增的,一個可以使用腳本(通常為 Javascript
) 在其中繪制圖像的 HTML
元素。它可以用來制作照片集或者制作簡單(也不是那么簡單)的動畫,甚至可以進(jìn)行實(shí)時視頻處理和渲染。
它最初由蘋果內(nèi)部使用自己 MacOS X WebKit
推出,供應(yīng)用程序使用像儀表盤的構(gòu)件和 Safari
瀏覽器使用。后來,有人通過 Gecko
內(nèi)核的瀏覽器 (尤其是 Mozilla
和Firefox
),Opera
和 Chrome
和超文本網(wǎng)絡(luò)應(yīng)用技術(shù)工作組建議為下一代的網(wǎng)絡(luò)技術(shù)使用該元素。
Canvas
是由 HTML
代碼配合高度和寬度屬性而定義出的可繪制區(qū)域。Javascript
代碼可以訪問該區(qū)域,類似于其他通用的二維 API
,通過一套完整的繪圖函數(shù)來動態(tài)生成圖形。
Mozilla 程序從 Gecko 1.8 (Firefox 1.5) 開始支持 <canvas>
, Internet Explorer 從 IE9 開始 <canvas>
。Chrome 和 Opera 9+ 也支持 <canvas>
。
二、Canvas基本使用 <canvas id="tutorial" width="300" height="300"></canvas> 2.1 <canvas>
元素 <canvas>
看起來和 <img>
標(biāo)簽一樣,只是 <canvas>
只有兩個可選的屬性 width、heigth
屬性,而沒有 src、alt
屬性。
如果不給 <canvas>
設(shè)置 widht、height
屬性時,則默認(rèn) width
為300、height
為 150,單位都是 px
。也可以使用 css
屬性來設(shè)置寬高,但是如寬高屬性和初始比例不一致,他會出現(xiàn)扭曲。所以,建議永遠(yuǎn)不要使用 css
屬性來設(shè)置 <canvas>
的寬高。
替換內(nèi)容
由于某些較老的瀏覽器(尤其是 IE9 之前的 IE 瀏覽器)或者瀏覽器不支持 HTML 元素 <canvas>
,在這些瀏覽器上你應(yīng)該總是能展示替代內(nèi)容。
支持 <canvas>
的瀏覽器會只渲染 <canvas>
標(biāo)簽,而忽略其中的替代內(nèi)容。不支持 <canvas>
的瀏覽器則 會直接渲染替代內(nèi)容。
用文本替換:
<canvas>
你的瀏覽器不支持 canvas,請升級你的瀏覽器。
</canvas> 用 <img>
替換:
<canvas>
<img decoding="async" src="./美女.jpg" alt="">
</canvas> 結(jié)束標(biāo)簽 </canvas>
不可省略。
與 <img>
元素不同,<canvas>
元素需要結(jié)束標(biāo)簽(</canvas>
)。如果結(jié)束標(biāo)簽不存在,則文檔的其余部分會被認(rèn)為是替代內(nèi)容,將不會顯示出來。
2.2 渲染上下文(Thre Rending Context) <canvas>
會創(chuàng)建一個固定大小的畫布,會公開一個或多個渲染上下文 (畫筆),使用渲染上下文 來繪制和處理要展示的內(nèi)容。
我們重點(diǎn)研究 2D 渲染上下文。 其他的上下文我們暫不研究,比如, WebGL 使用了基于 OpenGL ES的3D 上下文 ("experimental-webgl") 。
var canvas = document.getElementById('tutorial');
//獲得 2d 上下文對象
var ctx = canvas.getContext('2d'); 2.3 檢測支持性 var canvas = document.getElementById('tutorial');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
} 2.4 代碼模板 <html>
<head>
<title>Canvas tutorial</title>
<style type="text/css">
canvas {
border: 1px solid black;
}
</style>
</head>
<canvas id="tutorial" width="300" height="300"></canvas>
</body>
<script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if(!canvas.getContext) return;
var ctx = canvas.getContext("2d");
//開始代碼
}
draw();
</script>
</html> 2.5 一個簡單的例子 以下實(shí)例繪制兩個長方形:
實(shí)例
<html>
<head>
<title>Canvas tutorial</title>
<style type="text/css">
canvas {
border: 1px solid black;
}
</style>
</head>
<canvas id="tutorial" width="300" height="300"></canvas>
</body>
<script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if(!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(200,0,0)";
//繪制矩形
ctx.fillRect (10, 10, 55, 50);
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect (30, 30, 55, 50);
}
draw();
</script>
</html> 三、繪制形狀 3.1 柵格 (grid)
和坐標(biāo)空間 如下圖所示,canvas
元素默認(rèn)被網(wǎng)格所覆蓋。通常來說網(wǎng)格中的一個單元相當(dāng)于 canvas
元素中的一像素。柵格的起點(diǎn)為左上角,坐標(biāo)為 (0,0) 。所有元素的位置都相對于原點(diǎn)來定位。所以圖中藍(lán)色方形左上角的坐標(biāo)為距離左邊(X 軸)x 像素,距離上邊(Y 軸)y 像素,坐標(biāo)為 (x,y)。
后面我們會涉及到坐標(biāo)原點(diǎn)的平移、網(wǎng)格的旋轉(zhuǎn)以及縮放等。
3.2 繪制矩形 <canvas>
只支持一種原生的圖形繪制:矩形 。所有其他圖形都至少需要生成一種路徑 (path
)。不過,我們擁有眾多路徑生成的方法讓復(fù)雜圖形的繪制成為了可能。
canvast 提供了三種方法繪制矩形:
1、fillRect(x, y, width, height) :繪制一個填充的矩形。
2、strokeRect(x, y, width, height) :繪制一個矩形的邊框。
3、clearRect(x, y, widh, height) :清除指定的矩形區(qū)域,然后這塊區(qū)域會變的完全透明。
說明: 這 3 個方法具有相同的參數(shù)。
x, y :指的是矩形的左上角的坐標(biāo)。(相對于canvas的坐標(biāo)原點(diǎn))
width, height :指的是繪制的矩形的寬和高。
function draw(){
var canvas = document.getElementById('tutorial');
if(!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.fillRect(10, 10, 100, 50); //繪制矩形,填充的默認(rèn)顏色為黑色
ctx.strokeRect(10, 70, 100, 50); //繪制矩形邊框
}
draw();
ctx.clearRect(15, 15, 50, 25);
四、繪制路徑 (path
) 圖形的基本元素是路徑。
路徑是通過不同顏色和寬度的線段或曲線相連形成的不同形狀的點(diǎn)的集合。
一個路徑,甚至一個子路徑,都是閉合的。
使用路徑繪制圖形需要一些額外的步驟:
創(chuàng)建路徑起始點(diǎn)
調(diào)用繪制方法去繪制出路徑
把路徑封閉
一旦路徑生成,通過描邊或填充路徑區(qū)域來渲染圖形。
下面是需要用到的方法:
beginPath()
新建一條路徑,路徑一旦創(chuàng)建成功,圖形繪制命令被指向到路徑上生成路徑
moveTo(x, y)
把畫筆移動到指定的坐標(biāo)(x, y)
。相當(dāng)于設(shè)置路徑的起始點(diǎn)坐標(biāo)。
closePath()
閉合路徑之后,圖形繪制命令又重新指向到上下文中
stroke()
通過線條來繪制圖形輪廓
fill()
通過填充路徑的內(nèi)容區(qū)域生成實(shí)心的圖形
4.1 繪制線段 function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath(); //新建一條path
ctx.moveTo(50, 50); //把畫筆移動到指定的坐標(biāo)
ctx.lineTo(200, 50); //繪制一條從當(dāng)前位置到指定坐標(biāo)(200, 50)的直線.
//閉合路徑。會拉一條從當(dāng)前點(diǎn)到path起始點(diǎn)的直線。如果當(dāng)前點(diǎn)與起始點(diǎn)重合,則什么都不做
ctx.closePath();
ctx.stroke(); //繪制路徑。
}
draw(); 4.2 繪制三角形邊框 function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 200);
ctx.closePath(); //雖然我們只繪制了兩條線段,但是closePath會closePath,仍然是一個3角形
ctx.stroke(); //描邊。stroke不會自動closePath()
}
draw();
4.3 填充三角形 function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 200);
ctx.fill(); //填充閉合區(qū)域。如果path沒有閉合,則fill()會自動閉合路徑。
}
draw();
4.4 繪制圓弧 有兩個方法可以繪制圓弧:
1、arc(x, y, r, startAngle, endAngle, anticlockwise) : 以(x, y)
為圓心,以r
為半徑,從 startAngle
弧度開始到endAngle
弧度結(jié)束。anticlosewise
是布爾值,true
表示逆時針,false
表示順時針(默認(rèn)是順時針)。
注意:
這里的度數(shù)都是弧度。
0
弧度是指的 x
軸正方向。 radians=(Math.PI/180)*degrees //角度轉(zhuǎn)換成弧度
2、arcTo(x1, y1, x2, y2, radius) : 根據(jù)給定的控制點(diǎn)和半徑畫一段圓弧,最后再以直線連接兩個控制點(diǎn)。
圓弧案例 1
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
ctx.stroke();
}
draw();
圓弧案例 2
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
ctx.stroke();
ctx.beginPath();
ctx.arc(150, 50, 40, 0, -Math.PI / 2, true);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(50, 150, 40, -Math.PI / 2, Math.PI / 2, false);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 150, 40, 0, Math.PI, false);
ctx.fill();
}
draw();
圓弧案例 3
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50);
//參數(shù)1、2:控制點(diǎn)1坐標(biāo) 參數(shù)3、4:控制點(diǎn)2坐標(biāo) 參數(shù)4:圓弧半徑
ctx.arcTo(200, 50, 200, 200, 100);
ctx.lineTo(200, 200)
ctx.stroke();
ctx.beginPath();
ctx.rect(50, 50, 10, 10);
ctx.rect(200, 50, 10, 10)
ctx.rect(200, 200, 10, 10)
ctx.fill()
}
draw();
arcTo
方法的說明:
這個方法可以這樣理解。繪制的弧形是由兩條切線所決定。
第 1 條切線:起始點(diǎn)和控制點(diǎn)1決定的直線。
第 2 條切線:控制點(diǎn)1 和控制點(diǎn)2決定的直線。
其實(shí)繪制的圓弧就是與這兩條直線相切的圓弧。
4.5 繪制貝塞爾曲線 4.5.1 什么是貝塞爾曲線
貝塞爾曲線(Bézier curve),又稱貝茲曲線或貝濟(jì)埃曲線,是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線。
一般的矢量圖形軟件通過它來精確畫出曲線,貝茲曲線由線段與節(jié)點(diǎn)組成,節(jié)點(diǎn)是可拖動的支點(diǎn),線段像可伸縮的皮筋,我們在繪圖工具上看到的鋼筆工具就是來做這種矢量曲線的。
貝塞爾曲線是計(jì)算機(jī)圖形學(xué)中相當(dāng)重要的參數(shù)曲線,在一些比較成熟的位圖軟件中也有貝塞爾曲線工具如 PhotoShop 等。在 Flash4 中還沒有完整的曲線工具,而在 Flash5 里面已經(jīng)提供出貝塞爾曲線工具。
貝塞爾曲線于 1962,由法國工程師皮埃爾·貝塞爾(Pierre Bézier)所廣泛發(fā)表,他運(yùn)用貝塞爾曲線來為汽車的主體進(jìn)行設(shè)計(jì)。貝塞爾曲線最初由Paul de Casteljau 于 1959 年運(yùn)用 de Casteljau 演算法開發(fā),以穩(wěn)定數(shù)值的方法求出貝茲曲線。
一次貝塞爾曲線其實(shí)是一條直線
二次貝塞爾曲線
三次貝塞爾曲線
4.5.2 繪制貝塞爾曲線
繪制二次貝塞爾曲線:
quadraticCurveTo(cp1x, cp1y, x, y) 說明:
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(10, 200); //起始點(diǎn)
var cp1x = 40, cp1y = 100; //控制點(diǎn)
var x = 200, y = 200; // 結(jié)束點(diǎn)
//繪制二次貝塞爾曲線
ctx.quadraticCurveTo(cp1x, cp1y, x, y);
ctx.stroke();
ctx.beginPath();
ctx.rect(10, 200, 10, 10);
ctx.rect(cp1x, cp1y, 10, 10);
ctx.rect(x, y, 10, 10);
ctx.fill();
}
draw();
繪制三次貝塞爾曲線:
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) 說明:
參數(shù) 1 和 2:控制點(diǎn) 1 的坐標(biāo)
參數(shù) 3 和 4:控制點(diǎn) 2 的坐標(biāo)
參數(shù) 5 和 6:結(jié)束點(diǎn)的坐標(biāo)
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(40, 200); //起始點(diǎn)
var cp1x = 20, cp1y = 100; //控制點(diǎn)1
var cp2x = 100, cp2y = 120; //控制點(diǎn)2
var x = 200, y = 200; // 結(jié)束點(diǎn)
//繪制二次貝塞爾曲線
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
ctx.stroke();
ctx.beginPath();
ctx.rect(40, 200, 10, 10);
ctx.rect(cp1x, cp1y, 10, 10);
ctx.rect(cp2x, cp2y, 10, 10);
ctx.rect(x, y, 10, 10);
ctx.fill();
}
draw();
五、添加樣式和顏色 在前面的繪制矩形章節(jié)中,只用到了默認(rèn)的線條和顏色。
如果想要給圖形上色,有兩個重要的屬性可以做到。
fillStyle = color
設(shè)置圖形的填充顏色
strokeStyle = color
設(shè)置圖形輪廓的顏色
備注:
1. color 可以是表示 css 顏色值的字符串、漸變對象或者圖案對象。
2. 默認(rèn)情況下,線條和填充顏色都是黑色。
3. 一旦您設(shè)置了 strokeStyle 或者 fillStyle 的值,那么這個新值就會成為新繪制的圖形的默認(rèn)值。如果你要給每個圖形上不同的顏色,你需要重新設(shè)置 fillStyle 或 strokeStyle 的值。
fillStyle function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = 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 * 50, i * 50, 50, 50);
}
}
}
draw();
strokeStyle <script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
for (var i = 0; i < 6; i++){
for (var j = 0; j < 6; j++){
ctx.strokeStyle = `rgb(${randomInt(0, 255)},${randomInt(0, 255)},${randomInt(0, 255)})`;
ctx.strokeRect(j * 50, i * 50, 40, 40);
}
}
}
draw();
/**
作者:李振超 4 Jun 2017 12:12
返回隨機(jī)的 [from, to] 之間的整數(shù)(包括from,也包括to)
*/
function randomInt(from, to){
return parseInt(Math.random() * (to - from + 1) + from);
}
</script>
Transparency(透明度) globalAlpha = transparencyValue : 這個屬性影響到 canvas 里所有圖形的透明度,有效的值范圍是 0.0 (完全透明)到 1.0(完全不透明),默認(rèn)是 1.0。
globalAlpha 屬性在需要繪制大量擁有相同透明度的圖形時候相當(dāng)高效。不過,我認(rèn)為使用rgba()設(shè)置透明度更加好一些。
1、line style 線寬。只能是正值。默認(rèn)是 1.0。
起始點(diǎn)和終點(diǎn)的連線為中心,上下各占線寬的一半 。
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.lineWidth = 10;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(110, 10);
ctx.lineTo(160, 10)
ctx.lineWidth = 20;
ctx.stroke()
2. lineCap = type 線條末端樣式。
共有 3 個值:
butt
:線段末端以方形結(jié)束
round
:線段末端以圓形結(jié)束
square
:線段末端以方形結(jié)束,但是增加了一個寬度和線段相同,高度是線段厚度一半的矩形區(qū)域。
var lineCaps = ["butt", "round", "square"];
for (var i = 0; i < 3; i++){
ctx.beginPath();
ctx.moveTo(20 + 30 * i, 30);
ctx.lineTo(20 + 30 * i, 100);
ctx.lineWidth = 20;
ctx.lineCap = lineCaps[i];
ctx.stroke();
}
ctx.beginPath();
ctx.moveTo(0, 30);
ctx.lineTo(300, 30);
ctx.moveTo(0, 100);
ctx.lineTo(300, 100)
ctx.strokeStyle = "red";
ctx.lineWidth = 1;
ctx.stroke(); 3. lineJoin = type 同一個 path 內(nèi),設(shè)定線條與線條間接合處的樣式。 共有 3 個值 round
, bevel
和 miter
:
round
通過填充一個額外的,圓心在相連部分末端的扇形,繪制拐角的形狀。 圓角的半徑是線段的寬度。
bevel
在相連部分的末端填充一個額外的以三角形為底的區(qū)域, 每個部分都有各自獨(dú)立的矩形拐角。
miter
(默認(rèn)) 通過延伸相連部分的外邊緣,使其相交于一點(diǎn),形成一個額外的菱形區(qū)域。
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
var lineJoin = ['round', 'bevel', 'miter'];
ctx.lineWidth = 20;
for (var i = 0; i < lineJoin.length; i++){
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(50, 50 + i * 50);
ctx.lineTo(100, 100 + i * 50);
ctx.lineTo(150, 50 + i * 50);
ctx.lineTo(200, 100 + i * 50);
ctx.lineTo(250, 50 + i * 50);
ctx.stroke();
}
}
draw();
4. 虛線 用 setLineDash
方法和 lineDashOffset
屬性來制定虛線樣式。 setLineDash
方法接受一個數(shù)組,來指定線段與間隙的交替;lineDashOffset
屬性設(shè)置起始偏移量。
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.setLineDash([20, 5]); // [實(shí)線長度, 間隙長度]
ctx.lineDashOffset = -0;
ctx.strokeRect(50, 50, 210, 210);
}
draw();
備注 : getLineDash() 返回一個包含當(dāng)前虛線樣式,長度為非負(fù)偶數(shù)的數(shù)組。
六、繪制文本 繪制文本的兩個方法 canvas 提供了兩種方法來渲染文本:
fillText(text, x, y [, maxWidth])
在指定的 (x,y) 位置填充指定的文本,繪制的最大寬度是可選的。
strokeText(text, x, y [, maxWidth])
在指定的 (x,y) 位置繪制文本邊框,繪制的最大寬度是可選的。
var ctx;
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
ctx = canvas.getContext("2d");
ctx.font = "100px sans-serif"
ctx.fillText("天若有情", 10, 100);
ctx.strokeText("天若有情", 10, 200)
}
draw();
給文本添加樣式 font = value
當(dāng)前我們用來繪制文本的樣式。這個字符串使用和 CSS font
屬性相同的語法。 默認(rèn)的字體是 10px sans-serif
。
textAlign = value
文本對齊選項(xiàng)。 可選的值包括:start
, end
, left
, right
or center
。 默認(rèn)值是 start
。
textBaseline = value
基線對齊選項(xiàng),可選的值包括:top
, hanging
, middle
, alphabetic
, ideographic
, bottom
。默認(rèn)值是 alphabetic。
。
direction = value
文本方向。可能的值包括:ltr
, rtl
, inherit
。默認(rèn)值是 inherit
。
七、繪制圖片 我們也可以在 canvas
上直接繪制圖片。
7.1 由零開始創(chuàng)建圖片 創(chuàng)建<img>
元素
var img = new Image(); // 創(chuàng)建一個<img>元素
img.src = 'myImage.png'; // 設(shè)置圖片源地址 腳本執(zhí)行后圖片開始裝載。
繪制 img
// 參數(shù) 1:要繪制的 img
// 參數(shù) 2、3:繪制的 img 在 canvas 中的坐標(biāo)
ctx.drawImage(img,0,0); 注意 :考慮到圖片是從網(wǎng)絡(luò)加載,如果 drawImage
的時候圖片還沒有完全加載完成,則什么都不做,個別瀏覽器會拋異常。所以我們應(yīng)該保證在 img
繪制完成之后再 drawImage
。
var img = new Image(); // 創(chuàng)建img元素
img.onload = function(){
ctx.drawImage(img, 0, 0)
}
img.src = 'myImage.png'; // 設(shè)置圖片源地址 7.2 繪制 img
標(biāo)簽元素中的圖片 img
可以 new
也可以來源于我們頁面的 <img>
標(biāo)簽。
<img src="./美女.jpg" alt="" width="300"><br>
<canvas id="tutorial" width="600" height="400"></canvas>
<script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
var img = document.queryselector("img");
ctx.drawImage(img, 0, 0);
}
document.queryselector("img").onclick = function (){
draw();
}
</script> 第一張圖片就是頁面中的 <img>
標(biāo)簽:
7.3 縮放圖片 drawImage()
也可以再添加兩個參數(shù):
drawImage(image, x, y, width, height) 這個方法多了 2 個參數(shù):width
和 height
,這兩個參數(shù)用來控制 當(dāng)像 canvas 畫入時應(yīng)該縮放的大小。
ctx.drawImage(img, 0, 0, 400, 200)
7.4 切片(slice) drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) 第一個參數(shù)和其它的是相同的,都是一個圖像或者另一個 canvas 的引用。
其他 8 個參數(shù):
前 4 個是定義圖像源的切片位置和大小,后 4 個則是定義切片的目標(biāo)顯示位置和大小。
八、狀態(tài)的保存和恢復(fù) Saving and restoring state
是繪制復(fù)雜圖形時必不可少的操作。
save()
和 restore()
save
和 restore
方法是用來保存和恢復(fù) canvas
狀態(tài)的,都沒有參數(shù)。
Canvas
的狀態(tài)就是當(dāng)前畫面應(yīng)用的所有樣式和變形的一個快照。
1、關(guān)于 save() :Canvas狀態(tài)存儲在棧中,每當(dāng)save()方法被調(diào)用后,當(dāng)前的狀態(tài)就被推送到棧中保存。
一個繪畫狀態(tài)包括:
當(dāng)前應(yīng)用的變形(即移動,旋轉(zhuǎn)和縮放)
strokeStyle
, fillStyle
, globalAlpha
, lineWidth
, lineCap
, lineJoin
, miterLimit
, shadowOffsetX
, shadowOffsetY
, shadowBlur
, shadowColor
, globalCompositeOperation 的值
當(dāng)前的裁切路徑(clipping path
) 可以調(diào)用任意多次 save
方法(類似數(shù)組的 push()
)。
可以調(diào)用任意多次 save
方法 (類似數(shù)組的push()
)。
2、關(guān)于restore():每一次調(diào)用 restore 方法,上一個保存的狀態(tài)就從棧中彈出,所有設(shè)定都恢復(fù)(類似數(shù)組的 pop()
)。
var ctx;
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 150, 150); // 使用默認(rèn)設(shè)置繪制一個矩形
ctx.save(); // 保存默認(rèn)狀態(tài)
ctx.fillStyle = 'red' // 在原有配置基礎(chǔ)上對顏色做改變
ctx.fillRect(15, 15, 120, 120); // 使用新的設(shè)置繪制一個矩形
ctx.save(); // 保存當(dāng)前狀態(tài)
ctx.fillStyle = '#FFF' // 再次改變顏色配置
ctx.fillRect(30, 30, 90, 90); // 使用新的配置繪制一個矩形
ctx.restore(); // 重新加載之前的顏色狀態(tài)
ctx.fillRect(45, 45, 60, 60); // 使用上一次的配置繪制一個矩形
ctx.restore(); // 加載默認(rèn)顏色配置
ctx.fillRect(60, 60, 30, 30); // 使用加載的配置繪制一個矩形
}
draw();
九、變形 9.1 translate translate(x, y)
用來移動 canvas
的原點(diǎn) 到指定的位置
translate
方法接受兩個參數(shù)。x
是左右偏移量,y
是上下偏移量,如右圖所示。
在做變形之前先保存狀態(tài)是一個良好的習(xí)慣。大多數(shù)情況下,調(diào)用 restore
方法比手動恢復(fù)原先的狀態(tài)要簡單得多。又如果你是在一個循環(huán)中做位移但沒有保存和恢復(fù) canvas
的狀態(tài),很可能到最后會發(fā)現(xiàn)怎么有些東西不見了,那是因?yàn)樗芸赡芤呀?jīng)超出 canvas
范圍以外了。
注意:translate
移動的是 canvas
的坐標(biāo)原點(diǎn)(坐標(biāo)變換)。
var ctx;
function draw(){
var canvas = document.getElementById('tutorial1');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.save(); //保存坐原點(diǎn)平移之前的狀態(tài)
ctx.translate(100, 100);
ctx.strokeRect(0, 0, 100, 100)
ctx.restore(); //恢復(fù)到最初狀態(tài)
ctx.translate(220, 220);
ctx.fillRect(0, 0, 100, 100)
}
draw();
9.2 rotate
旋轉(zhuǎn)坐標(biāo)軸。
這個方法只接受一個參數(shù):旋轉(zhuǎn)的角度(angle),它是順時針方向的,以弧度為單位的值。
旋轉(zhuǎn)的中心是坐標(biāo)原點(diǎn)。
var ctx;
function draw(){
var canvas = document.getElementById('tutorial1');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.save();
ctx.translate(100, 100);
ctx.rotate(Math.PI / 180 * 45);
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 100, 100);
ctx.restore();
ctx.save();
ctx.translate(0, 0);
ctx.fillRect(0, 0, 50, 50)
ctx.restore();
}
draw();
9.3 scale 我們用它來增減圖形在 canvas
中的像素數(shù)目,對形狀,位圖進(jìn)行縮小或者放大。
scale
方法接受兩個參數(shù)。x,y
分別是橫軸和縱軸的縮放因子,它們都必須是正值。值比 1.0 小表示縮 小,比 1.0 大則表示放大,值為 1.0 時什么效果都沒有。
默認(rèn)情況下,canvas
的 1 單位就是 1 個像素。舉例說,如果我們設(shè)置縮放因子是 0.5,1 個單位就變成對應(yīng) 0.5 個像素,這樣繪制出來的形狀就會是原先的一半。同理,設(shè)置為 2.0 時,1 個單位就對應(yīng)變成了 2 像素,繪制的結(jié)果就是圖形放大了 2 倍。
9.4 transform (變形矩陣) transform(a, b, c, d, e, f)
a (m11): Horizontal scaling.
b (m12): Horizontal skewing.
c (m21): Vertical skewing.
d (m22): Vertical scaling.
e (dx): Horizontal moving.
f (dy): Vertical moving.
var ctx;
function draw(){
var canvas = document.getElementById('tutorial1');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.transform(1, 1, 0, 1, 0, 0);
ctx.fillRect(0, 0, 100, 100);
}
draw();
十、合成
在前面的所有例子中、,我們總是將一個圖形畫在另一個之上,對于其他更多的情況,僅僅這樣是遠(yuǎn)遠(yuǎn)不夠的。比如,對合成的圖形來說,繪制順序會有限制。不過,我們可以利用 globalCompositeOperation 屬性來改變這種狀況。
globalCompositeOperation = type
var ctx;
function draw(){
var canvas = document.getElementById('tutorial1');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 200, 200);
ctx.globalCompositeOperation = "source-over"; //全局合成操作
ctx.fillStyle = "red";
ctx.fillRect(100, 100, 200, 200);
}
draw();
</script> 注 :下面的展示中,藍(lán)色是原有的,紅色是新的。
type 是下面 13 種字符串值之一:
1、這是默認(rèn)設(shè)置,新圖像會覆蓋在原有圖像。
2. source-in
僅僅會出現(xiàn)新圖像與原來圖像重疊的部分,其他區(qū)域都變成透明的。(包括其他的老圖像區(qū)域也會透明)
3. source-out 僅僅顯示新圖像與老圖像沒有重疊的部分,其余部分全部透明。(老圖像也不顯示)
4. source-atop 新圖像僅僅顯示與老圖像重疊區(qū)域。老圖像仍然可以顯示。
5. destination-over 新圖像會在老圖像的下面。
6. destination-in 僅僅新老圖像重疊部分的老圖像被顯示,其他區(qū)域全部透明。
7. destination-out 僅僅老圖像與新圖像沒有重疊的部分。 注意顯示的是老圖像的部分區(qū)域。
8. destination-atop 老圖像僅僅僅僅顯示重疊部分,新圖像會顯示在老圖像的下面。
9. lighter 新老圖像都顯示,但是重疊區(qū)域的顏色做加處理。
10. darken 保留重疊部分最黑的像素。(每個顏色位進(jìn)行比較,得到最小的)
blue: #0000ff
red: #ff0000 所以重疊部分的顏色:#000000 。
11. lighten 保證重疊部分最量的像素。(每個顏色位進(jìn)行比較,得到最大的)
blue: #0000ff
red: #ff0000 所以重疊部分的顏色:#ff00ff 。
12. xor 重疊部分會變成透明。
13. copy 只有新圖像會被保留,其余的全部被清除(邊透明)。
十一、裁剪路徑
把已經(jīng)創(chuàng)建的路徑轉(zhuǎn)換成裁剪路徑。
裁剪路徑的作用是遮罩。只顯示裁剪路徑內(nèi)的區(qū)域,裁剪路徑外的區(qū)域會被隱藏。
注意: clip() 只能遮罩在這個方法調(diào)用之后繪制的圖像,如果是 clip() 方法調(diào)用之前繪制的圖像,則無法實(shí)現(xiàn)遮罩。
var ctx;
function draw(){
var canvas = document.getElementById('tutorial1');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(20,20, 100, 0, Math.PI * 2);
ctx.clip();
ctx.fillStyle = "pink";
ctx.fillRect(20, 20, 100,100);
}
draw(); 十二、動畫 動畫的基本步驟 清空 canvas
再繪制每一幀動畫之前,需要清空所有。清空所有最簡單的做法就是 clearRect()
方法。
保存 canvas
狀態(tài) 如果在繪制的過程中會更改 canvas
的狀態(tài)(顏色、移動了坐標(biāo)原點(diǎn)等),又在繪制每一幀時都是原始狀態(tài)的話,則最好保存下 canvas
的狀態(tài)
繪制動畫圖形 這一步才是真正的繪制動畫幀
恢復(fù) canvas
狀態(tài) 如果你前面保存了 canvas
狀態(tài),則應(yīng)該在繪制完成一幀之后恢復(fù) canvas
狀態(tài)。
控制動畫 我們可用通過 canvas
的方法或者自定義的方法把圖像會知道到 canvas
上。正常情況,我們能看到繪制的結(jié)果是在腳本執(zhí)行結(jié)束之后。例如,我們不可能在一個 for
循環(huán)內(nèi)部完成動畫。
也就是,為了執(zhí)行動畫,我們需要一些可以定時執(zhí)行重繪的方法。
一般用到下面三個方法:
setInterval()
setTimeout()
requestAnimationFrame()
案例1:太陽系
let sun;
let earth;
let moon;
let ctx;
function init(){
sun = new Image();
earth = new Image();
moon = new Image();
sun.src = "sun.png";
earth.src = "earth.png";
moon.src = "moon.png";
let canvas = document.queryselector("#solar");
ctx = canvas.getContext("2d");
sun.onload = function (){
draw()
}
}
init();
function draw(){
ctx.clearRect(0, 0, 300, 300); //清空所有的內(nèi)容
/*繪制 太陽*/
ctx.drawImage(sun, 0, 0, 300, 300);
ctx.save();
ctx.translate(150, 150);
//繪制earth軌道
ctx.beginPath();
ctx.strokeStyle = "rgba(255,255,0,0.5)";
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke()
let time = new Date();
//繪制地球
ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds())
ctx.translate(100, 0);
ctx.drawImage(earth, -12, -12)
//繪制月球軌道
ctx.beginPath();
ctx.strokeStyle = "rgba(255,255,255,.3)";
ctx.arc(0, 0, 40, 0, 2 * Math.PI);
ctx.stroke();
//繪制月球
ctx.rotate(2 * Math.PI / 6 * time.getSeconds() + 2 * Math.PI / 6000 * time.getMilliseconds());
ctx.translate(40, 0);
ctx.drawImage(moon, -3.5, -3.5);
ctx.restore();
requestAnimationFrame(draw);
}
案例2:模擬時鐘
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body {
padding: 0;
margin: 0;
background-color: rgba(0, 0, 0, 0.1)
}
canvas {
display: block;
margin: 200px auto;
}
</style>
</head>
<body>
<canvas id="solar" width="300" height="300"></canvas>
<script>
init();
function init(){
let canvas = document.queryselector("#solar");
let ctx = canvas.getContext("2d");
draw(ctx);
}
function draw(ctx){
requestAnimationFrame(function step(){
drawDial(ctx); //繪制表盤
drawAllHands(ctx); //繪制時分秒針
requestAnimationFrame(step);
});
}
/*繪制時分秒針*/
function drawAllHands(ctx){
let time = new Date();
let s = time.getSeconds();
let m = time.getMinutes();
let h = time.getHours();
let pi = Math.PI;
let secondAngle = pi / 180 * 6 * s; //計(jì)算出來s針的弧度
let minuteAngle = pi / 180 * 6 * m + secondAngle / 60; //計(jì)算出來分針的弧度
let hourAngle = pi / 180 * 30 * h + minuteAngle / 12; //計(jì)算出來時針的弧度
drawHand(hourAngle, 60, 6, "red", ctx); //繪制時針
drawHand(minuteAngle, 106, 4, "green", ctx); //繪制分針
drawHand(secondAngle, 129, 2, "blue", ctx); //繪制秒針
}
/*繪制時針、或分針、或秒針
* 參數(shù)1:要繪制的針的角度
* 參數(shù)2:要繪制的針的長度
* 參數(shù)3:要繪制的針的寬度
* 參數(shù)4:要繪制的針的顏色
* 參數(shù)4:ctx
* */
function drawHand(angle, len, width, color, ctx){
ctx.save();
ctx.translate(150, 150); //把坐標(biāo)軸的遠(yuǎn)點(diǎn)平移到原來的中心
ctx.rotate(-Math.PI / 2 + angle); //旋轉(zhuǎn)坐標(biāo)軸。 x軸就是針的角度
ctx.beginPath();
ctx.moveTo(-4, 0);
ctx.lineTo(len, 0); // 沿著x軸繪制針
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.lineCap = "round";
ctx.stroke();
ctx.closePath();
ctx.restore();
}
/*繪制表盤*/
function drawDial(ctx){
let pi = Math.PI;
ctx.clearRect(0, 0, 300, 300); //清除所有內(nèi)容
ctx.save();
ctx.translate(150, 150); //一定坐標(biāo)原點(diǎn)到原來的中心
ctx.beginPath();
ctx.arc(0, 0, 148, 0, 2 * pi); //繪制圓周
ctx.stroke();
ctx.closePath();
for (let i = 0; i < 60; i++){//繪制刻度。
ctx.save();
ctx.rotate(-pi / 2 + i * pi / 30); //旋轉(zhuǎn)坐標(biāo)軸。坐標(biāo)軸x的正方形從 向上開始算起
ctx.beginPath();
ctx.moveTo(110, 0);
ctx.lineTo(140, 0);
ctx.lineWidth = i % 5 ? 2 : 4;
ctx.strokeStyle = i % 5 ? "blue" : "red";
ctx.stroke();
ctx.closePath();
ctx.restore();
}
ctx.restore();
}
</script>
</body>
</html>
該文章在 2023/11/28 11:52:30 編輯過