主要是讀書筆記枕磁,里面物理知識都是通用,記錄一下渡蜻,里面的示例做小游戲時(shí)會經(jīng)常遇到很實(shí)用。
1Canvas元素
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script type="text/javascript">
window.onload = function () {
//1计济、獲取canvas對象
var cnv = document.getElementById("canvas");
//2茸苇、獲取上下文環(huán)境對象context
var cxt = cnv.getContext("2d");
//3、開始繪制圖形
cxt.moveTo(50, 100);
cxt.lineTo(150, 50);
cxt.stroke();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px dashed
gray; "></canvas>
</body>
</html>
注意:width和height分別定義Canvas的寬度和高度沦寂。默認(rèn)情況下学密,Canvas的寬度為300px,高度為150px传藏。(css 設(shè)置無效)
2 直線
2.1 畫直線
cxt.moveTo(x1, y1);
cxt.lineTo(x2, y2);
cxt.stroke();
2.2 描邊
cxt.strokeStyle = 屬性值腻暮;
cxt.strokeRect(x, y, width, height);
strokeStyle屬性 取值有三種彤守,即顏色值、漸變色哭靖、圖案具垫。
strokeRect()方法 用于確定矩形的坐標(biāo),其中x和y為矩形最左上角的坐標(biāo)试幽。
2.3 填充
cxt.fillStyle=屬性值筝蚕;
cxt.fillRect(x, y, width, height);
fillStyle屬性跟strokeStyle屬性一樣,取值也有三種铺坞,即顏色值饰及、漸變色、圖案康震。fillRect()方法跟strokeRect()方法一樣燎含,用于確定矩形的坐標(biāo),其中x和y為矩形最左上角的坐標(biāo)腿短,width表示矩形的寬度屏箍,height表示矩形的高度。跟描邊矩形一樣橘忱,填充矩形的fillStyle屬性也必須在fillRect()方法之前定義赴魁,否則fillStyle屬性無效。
2.4 rect() 方法
2.5 清空矩形
cxt.clearRect(x, y, width, height);
3. 曲線
圖形钝诚、弧線颖御、二次貝塞爾曲線、三次貝塞爾曲線凝颇。
3.1 圓
cxt.beginPath();
cxt.arc(x, y潘拱,半徑,開始角度拧略,結(jié)束角度芦岂,anticlockwise); //anticlockwise:默認(rèn)false 順時(shí)針
cxt.closePath();、三次貝塞爾曲線垫蛆。
上面這個(gè)語法僅僅是對圓形的一個(gè)“狀態(tài)描述”禽最,我們還需要對圓形進(jìn)行“描邊”或“填充”,才會有實(shí)際效果袱饭。這一點(diǎn)跟繪制矩形是一樣的道理川无,大家別忘了對比一下
//狀態(tài)描述
cxt.beginPath();
cxt.arc(x, y,半徑虑乖,開始角度懦趋,結(jié)束角度,anticlockwise);
cxt.closePath();
//描邊
cxt.strokeStyle = "顏色值";
cxt.stroke();
//狀態(tài)描述
cxt.beginPath();
cxt.arc(x, y愕够,半徑走贪,開始角度逃沿,結(jié)束角度,anticlockwise);
cxt.closePath();
//填充
cxt.fillStyle = "顏色值";
cxt.fill();
3.2 圓弧
arc()畫弧線不使用closePath()來關(guān)閉路徑幻锁。這一點(diǎn)大家一定要區(qū)分開凯亮,因?yàn)榛【€不是一個(gè)閉合圖形。而closePath()是用來繪制“封閉圖形”的(作用在于關(guān)閉路徑哄尔、連接起點(diǎn)與終點(diǎn))
-
cxt.arcTo(cx, cy, x2, y2, radius);
3.3 二次貝塞爾曲線
cxt.quadraticCurveTo(cx , cy , x2, y2);
3.4 三次貝塞爾曲線
cxt.bezierCurveTo(cx1 , cy1 , cx2 , cy2 , x , y);
4 線條操作
4.1 lineWidth屬性
lineWidth屬性:取值為整數(shù)假消,默認(rèn)值為1,默認(rèn)單位為px岭接。
lineWidth屬性不僅可以用于直線圖形富拗,也可以用于曲線圖形。此外需要注意的是鸣戴,假設(shè)線條寬度為lineWidth啃沪,則strokeRect()方法繪制的矩形實(shí)際寬度是(width+lineWidth),實(shí)際高度是(height+lineWidth)窄锅。而arc()方法繪制的圓形實(shí)際半徑為(radius+lineWidth)创千。
4.2 lineCap屬性
注意,round和square值會使線條稍微變長一點(diǎn)入偷,因?yàn)樗鼈兘o線條增加了線帽部分追驴。
我們必須使用beginPath()來開始新的路徑,才可以使用一個(gè)新的lineCap屬性值盯串。此外還需要注意氯檐,round和square值會使線條稍微變長一點(diǎn),因?yàn)樗鼈兘o線條增加了線帽部分体捏。
4.3 LineJoin屬性
cxt.lineJoin = "屬性值";
4.4 setLineDash()方法
setLineDash()方法
5. 文本操作
5.1strokeText()方法
strokeText(text , x , y , maxWidth)
參數(shù)maxWidth為可選參數(shù),表示允許的最大文本的寬度(單位為px)糯崎。如果文本寬度超出maxWidth值几缭,文本會被壓縮至maxWidth值的寬度。
5.2 fillText()方法
fillText(text , x , y , maxWidth)
5.3 measureText()方法
var length = cxt.measureText(text).width;
參數(shù)text表示一個(gè)字符串文本沃呢,measureText().width返回文本的長度年栓,單位為px。
5.4 textBaseline屬性
6. 圖片操作
6.1 繪制圖片
cxt.drawImage(image , dx , dy);
參數(shù)image薄霜,表示頁面中的圖片某抓。該圖片可以是頁面中的img元素纸兔,也可以是JavaScript創(chuàng)建的Image對象
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//創(chuàng)建image對象
var image = new Image();
image.src = "images/princess.png";
//必須在圖片載入完成后才能將圖片繪制到Canvas上
image.onload = function () {
cxt.drawImage(image, 40, 20);
}
}
2. cxt.drawImage(image , dx , dy , dw , dh)
-
cxt.drawImage(image , sx , sy , sw, sh , dx , dy , dw , dh)
6.2 平鋪照片
var pattern = cxt.createPattern(image , type);
cxt.fillStyle = pattern;
cxt.fillRect();
6.3 切割圖片
cxt.clip();
想要使用clip()方法切割一張圖片,一般需要以下三步否副。
(1)繪制基本圖形汉矿。(2)使用clip()方法。(3)繪制圖片备禀。
//第1步洲拇,繪制基本圖形,用來切割
cxt.beginPath();
cxt.arc(70, 70, 50, 0, 360 * Math.PI / 180, true);
cxt.closePath();
cxt.stroke();
//第2步曲尸,使用clip()方法赋续,使得切割區(qū)域?yàn)樯厦胬L制的基本圖形
cxt.clip();
//第3步,繪制一張圖片
var image = new Image();
image.src = "images/princess.png";
image.onload = function () {
cxt.drawImage(image, 10, 20);
}
7 變形操作
7.1 平移
cxt.translate(x, y); //“狀態(tài)”都必須在“動(dòng)作”之前定義
7.2 清空畫布
cxt.clearRect(0,0, cnv.width, cnv.height); //等價(jià) cxt.clearRect();
7.3 縮放
cxt.scale(x, y);
scale()方法會改變圖形的以下幾點(diǎn)另患。(1)左上角坐標(biāo)纽乱。(2)寬度或高度。(3)線條寬度昆箕。
7.4 圖形旋轉(zhuǎn)
cxt.rotate(angle); //“度數(shù)*Math.PI/180”
默認(rèn)情況下鸦列,rotate()方法的旋轉(zhuǎn)中心就是原點(diǎn)坐標(biāo)(0,0)
-
改變旋轉(zhuǎn)中心: 如果我們想要以某一點(diǎn)(x, y)為旋轉(zhuǎn)中心,可以先使用translate(x, y)为严,然后再使用rotate()方法敛熬。(配合save restore方法)
cxt.clearRect(0, 0, cnv.width, cnv.height); cxt.save(); cxt.translate(cnv.width / 2, cnv.height / 2); //將坐標(biāo)移 動(dòng)到中心 cxt.rotate(Math.PI * (i / 100)); //累進(jìn)旋轉(zhuǎn) cxt.fillStyle = "HotPink"; cxt.fillRect(-rectWidth / 2, -rectHeight / 2, rectWidth, rectHeight); //填充矩形 cxt.restore();
7.5 transform()
cxt.transform(a, b, c, d, e, f);
7.6 setTransform()方法
setTransform()方法會重置圖形的狀態(tài),然后再進(jìn)行變換
變形操作除了可以用于圖形第股,還可以用于文字和圖片
8 像素操作
8.1 getImageData()方法
var imgData = cxt.getImageData(x , y , width , height);
var data = imgData.data;
說明:x应民、y表示所選圖片區(qū)域的坐標(biāo),width夕吻、height表示所選圖片區(qū)域的寬度和高度诲锹。getImageData()方法返回的是一個(gè)canvasPixelArray對象,我們可以用一個(gè)變量(如imgData)來保存這個(gè)對象涉馅。canvasPixelArray對象有一個(gè)data屬性归园,這個(gè)data屬性是一個(gè)保存了這一張圖片像素?cái)?shù)據(jù)的數(shù)組,數(shù)組取值如[r1, g1, b1, a1, r2,g2, b2, a2, ……]稚矿。也就是說庸诱,在imgData.data這個(gè)數(shù)組中每四個(gè)數(shù)存儲著1個(gè)像素的rgba顏色值,這四個(gè)數(shù)分別是該像素的紅(r)晤揣、綠(g)桥爽、藍(lán)(b)、透明度(a)
8.2 putImageData()方法
cxt.putImageData(image , x , y);
說明:image表示重新繪制的圖形昧识,也就是使用getImageData()方法獲取的canvasPixelArray對象钠四。x、y表示重新繪制圖形左上角的橫坐標(biāo)跪楞、縱坐標(biāo)缀去。getImageData()和putImageData()都是配合使用的侣灶,一般我們都是先用getImageData()獲取像素?cái)?shù)據(jù),然后利用一定的算法進(jìn)行像素操作缕碎,最后再使用putImageData()方法輸出像素?cái)?shù)據(jù)(即在Canvas中顯示一張圖片)褥影。上面只是簡單介紹了一下getImageData()和putImageData()這兩個(gè)方法的語法,在后面這幾節(jié)里阎曹,我們結(jié)合相應(yīng)例子并經(jīng)澄苯祝回過頭來看看這一節(jié),就很容易理解了
[圖片上傳失敗...(image-5f588d-1677137124942)]
- 顏色反轉(zhuǎn)
for (var i = 0; i < data.length; i += 4)
{
data[i + 0] = 255- data[i + 0];
data[i + 1] = 255- data[i + 1];
data[i + 2] = 255- data[i + 2];
}
-
黑白效果
for (var i = 0; i < data.length; i += 4) { //var average = (data[i + 0] + data[i + 1] + data[i + 2] + data[i + 3]) / 3; var grayscale = data[i] * 0.3 + data[i + 1] * 0.6 + data[i + 2] * 0.1; data[i + 0] = average; //紅 data[i + 1] = average; //綠 data[i + 2] = average; //藍(lán) }
-
亮度效果
for (var i = 0; i < data.length; i += 4) { var a = 50; data[i + 0] += a; data[i + 1] += a; data[i + 2] += a; }
-
復(fù)古效果
for (var i = 0; i < data.length; i += 4) { var r = data[i + 0]; var g = data[i + 1]; var b = data[i + 2]; data[i + 0] = r * 0.39 + g * 0.76 + b * 0.18; data[i + 1] = r * 0.35 + g * 0.68 + b * 0.16; data[i + 2] = r * 0.27 + g * 0.53 + b * 0.13; }
-
紅色蒙版
for (var i = 0; i < data.length; i += 4) { var r = data[i + 0]; var g = data[i + 1]; var b = data[i + 2]; var average = (r + g + b) / 3; data[i + 0] = average; data[i + 1] = 0; data[i + 2] = 0; }
-
透明處理
for (var i = 0; i < data.length; i += 4) { data[i + 3] = data[i + 3]*n; }
8.3 createImageData()方法
cxt.createImageData(sw, sh); //第1種格式
cxt.createImageData(imageData); //第2種格式
(1)第一種格式:接收兩個(gè)參數(shù)处嫌,sw和sh分別表示要?jiǎng)?chuàng)建區(qū)域的寬度和高度栅贴。
(2)第二種格式:接收一個(gè)像素對象,表示要“創(chuàng)建區(qū)域”的寬度和高度與“這個(gè)像素對象”的寬度和高度相等熏迹。
createImageData(sw, sh) 和createImageData(imageData)這兩個(gè)方法說白了都是用來創(chuàng)建一個(gè)區(qū)域而已檐薯。只不過一個(gè)是我們自己定寬高,另外一個(gè)是“復(fù)制”別人的寬高注暗。
9. 漸變與陰影
9.1 線性漸變
var gnt = cxt.createLinearGradient(x1, y1, x2, y2);
gnt.addColorStop(value1, color1);
gnt.addColorStop(value2, color2);
cxt.fillStyle = gnt;
cxt.fill();
參數(shù)value表示漸變位置的偏移量坛缕,取值為0~1之間任意值。value1捆昏,表示漸變開始位置赚楚,value2表示漸變結(jié)束位置。
9.2 徑向漸變
var gnt = cxt.createRadialGradient(x1, y1, r1, x2, y2, r2);
gnt.addColorStop(value1, color1);
gnt.addColorStop(value2, color2);
cxt.fillStyle = gnt;
cxt.fill();
(1)調(diào)用createLinearGradient()方法創(chuàng)建一個(gè)radialGradient對象骗卜,并賦值給變量gnt宠页。
(2)調(diào)用radialGradient對象(即gnt)的addColorStop()方法N次,第1次表示漸變開始的顏色寇仓,第2次表示漸變結(jié)束時(shí)的顏色举户。然后第3次則以第2次漸變顏色作為開始顏色,進(jìn)行漸變遍烦,以此類推俭嘁。
(3)把radialGradient對象(即gnt)賦值給fillStyle屬性,并且調(diào)用fill()方法來繪制有漸變色的圖形服猪。(x1 , y1)表示漸變開始圓心的坐標(biāo)供填,r1表示漸變開始圓心的半徑。(x2 , y2)表示漸變結(jié)束圓心的坐標(biāo)罢猪,r2表示漸變結(jié)束圓的半徑捕虽。
9.3 陰影
//定義繪制圖形的函數(shù)
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//設(shè)置右下方向的陰影
cxt.shadowOffsetX = 5;
cxt.shadowOffsetY = 5;
cxt.shadowColor = "LightSkyBlue ";
cxt.shadowBlur = 10;
cxt.fillStyle = "HotPink";
cxt.fillRect(100, 30, 50, 50);
}
10 Canvas路徑
10.1 beginPath()
Canvas是基于“狀態(tài)”來繪制圖形的。每一次繪制(stroke()或fill()), Canvas會檢測整個(gè)程序定義的所有狀態(tài)坡脐,這些狀態(tài)包括strokeStyle、fillStyle房揭、lineWidth等备闲。當(dāng)一個(gè)狀態(tài)值沒有被改變時(shí)晌端,Canvas就一直使用最初的值。當(dāng)一個(gè)狀態(tài)值被改變時(shí)恬砂,需要分兩種情況考慮咧纠。
(1)如果使用beginPath()開始一個(gè)新的路徑,則不同路徑使用不同的值泻骤。
(2)如果沒有使用beginPath()開始一個(gè)新的路徑漆羔,則后面的值會覆蓋前面的值(后來者居上原則)。
//第1條直線
cxt.beginPath();
cxt.moveTo(50, 40);
cxt.lineTo(150, 40);
cxt.strokeStyle = "red";
cxt.stroke();
//第2條直線
cxt.beginPath();// 注意:這里開啟一個(gè)新的路徑
cxt.moveTo(50, 80);
cxt.lineTo(150, 80);
cxt.strokeStyle = "green";
cxt.stroke();
10.2 closePath()
“關(guān)閉路徑”并不等于“結(jié)束路徑”狱掂,所謂的“關(guān)閉路徑”一般是指將同一個(gè)路徑的起點(diǎn)與終點(diǎn)這兩點(diǎn)連接起來演痒,使其成為一個(gè)封閉的圖形。
10.3 isPointInPath()方法
cxt.isPointInPath(x , y);
注意趋惨,isPointInPath()不支持Canvas自帶的兩個(gè)方法:strokeRect()鸟顺、fillRect()。如果想要使用strokeRect()和fillRect()器虾,請使用rect()方法來代替讯嫂。
11 Canvas 狀態(tài)
Canvas為我們提供了兩個(gè)操作狀態(tài)的方法:save()和restore()。在Canvas中兆沙,我們可以使用save()方法來保存“當(dāng)前狀態(tài)”欧芽,然后可以使用restore()方法來恢復(fù)“之前保存的狀態(tài)”。save()和restore()一般情況下都是成對配合使用的葛圃。
11.1 clip()方法
cxt.clip();
我們在使用clip()方法之前千扔,必須要在Canvas中繪制一個(gè)基本圖形。然后調(diào)用clip()方法后装悲,這個(gè)基本圖形就會變?yōu)橐粋€(gè)剪切區(qū)域昏鹃。
注意,與之前接觸的isPointInPath()方法一樣诀诊,clip()方法也不支持Canvas自帶的兩個(gè)方法:strokeRect()洞渤、fillRect()。如果要使用strokeRect()和fillRect()属瓣,請使用rect()方法來代替载迄。
11.1 使用場景
Canvas狀態(tài)的保存和恢復(fù),主要用于以下三種場合抡蛙。
(1)圖形或圖片裁切护昧。
(2)圖形或圖片變形。
(3)以下屬性改變的時(shí)候:fillStyle粗截、font惋耙、globalAlpha、globalCompositeOperation、lineCap绽榛、lineJoin湿酸、lineWidth、miterLimit灭美、shadowBlur推溃、shadowColor、shadowOffsetX届腐、shadowOffsetY铁坎、strokeStyle、textAlign犁苏、textBaseline硬萍。
cxt.save();
cxt.fillStyle = "HotPink";
cxt.translate(30, 30);
cxt.fillRect(0, 0, 100, 50);
cxt.restore();//恢復(fù) 后面的平移坐標(biāo)系會基于原點(diǎn)位置
cxt.fillStyle = "LightSkyBlue ";
cxt.translate(60, 60);
cxt.fillRect(0, 0, 100, 50);
在變形操作(平移、縮放傀顾、旋轉(zhuǎn))中霞掺,我們一般都是在變形操作之前使用save()方法保存當(dāng)前狀態(tài)禀崖,其中當(dāng)前狀態(tài)包括參考坐標(biāo)、圖形大小等。然后再使用restore()方法來恢復(fù)之前保存的狀態(tài)忍捡。save( )和restore( )定页,在變形操作中會大量用到梦湘。
12 其他應(yīng)用
12.1 Canvas對象屬性
使用cnv.width和cnv.height來獲取Canvas的寬度和高度是經(jīng)常被用到的税娜,像文本居中時(shí)需要計(jì)算Canvas的寬度和高度、clearRect()清空Canvas時(shí)也需要計(jì)算Canvas的寬度和高度婉徘。
12.1 Canvas對象方法
cnv.toDataURL(type);
參數(shù)type是可選參數(shù)漠嵌,表示輸出的MIME類型。如果參數(shù)type省略盖呼,將使用image/png類型
12.2 globalAlpha屬性
context.globalAlpha = 數(shù)值儒鹿;
globalAlpha默認(rèn)值為1.0(完全不透明),取值范圍為:0.0~1.0几晤。其中0.0表示完全透明约炎,1.0表示完全不透明。globalAlpha屬性必須在圖形繪制之前定義才有效蟹瘾。注意圾浅,定義globalAlpha之后,會對整個(gè)畫布都起作用憾朴,因此我們在定義時(shí)要多加小心狸捕。
13 Canvas事件
在Canvas中,鼠標(biāo)事件分為以下三種众雷。(1)鼠標(biāo)按下:mousedown(2)鼠標(biāo)松開:mouseup(3)鼠標(biāo)移動(dòng):mousemove
13.1 獲取鼠標(biāo)的位置封裝工具
//將tools定義為window對象的屬性灸拍,該屬性的值是一個(gè)對象
window.tools = {};
//獲取鼠標(biāo)位置
window.tools.getMouse = function (element) {
//定義一個(gè)mouse的對象
var mouse = { x: 0, y: 0 };
//為傳入的元素添加mousemove事件
addEvent(element, "mousemove", function (e) {
var x, y;
//在IE中做祝,event對象是作為window對象的一個(gè)屬性存在
var e = e || window.event;
//獲取鼠標(biāo)當(dāng)前位置,并作兼容處理
//兼容Firefox株搔、chrome剖淀、IE9及以上
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
}
//兼容IE8及以下,以及混雜模式下的Chrome和Safari
else {
x = e.clientX + document.body.scrollLeft || document.
documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop || document.
documentElement.scrollTop;
}
//將當(dāng)前的坐標(biāo)值減去canvas元素的偏移位置纤房,則x、y為鼠標(biāo)在canvas中的相對坐標(biāo)
x -= element.offsetLeft;
y -= element.offsetTop;
mouse.x = x;
mouse.y = y;
})
//返回值為mouse對象
return mouse;
}
13.2 獲取鍵盤方向
//獲取鍵盤控制方向
window.tools.getKey = function () {
var key = {};
window.addEventListener("keydown", function (e) {
if (e.keyCode == 38 || e.keyCode == 87) {
key.direction = "up";
} else if (e.keyCode == 39 || e.keyCode == 68) {
key.direction = "right";
} else if (e.keyCode == 40 || e.keyCode == 83) {
key.direction = "down";
} else if (e.keyCode == 37 || e.keyCode == 65) {
key.direction = "left";
} else {
key.direction = "";
}
}, false);
return key;
}
13.3 循環(huán)事件
在Canvas中翻诉,我們都是使用requestAnimationFrame()方法來實(shí)現(xiàn)循環(huán)炮姨,從而達(dá)到動(dòng)畫效果。
(function frame(){
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
……
})();
小球向右移動(dòng)動(dòng)畫
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化變量碰煌,也就是初始化圓的x軸坐標(biāo)為0
var x = 0;
//動(dòng)畫循環(huán)
(function frame() {
window.requestAnimationFrame(frame);
//每次動(dòng)畫循環(huán)都先清空畫布舒岸,再重繪新的圖形
cxt.clearRect(0, 0, cnv.width, cnv.height);
//繪制圓
cxt.beginPath();
cxt.arc(x, 70, 20, 0, 360 * Math.PI / 180, true);
cxt.closePath();
cxt.fillStyle = "#6699FF";
cxt.fill();
//變量遞增
x += 2;
})();
14 物理動(dòng)畫
在Canvas中我們可以使用反正切函數(shù)Math.atan2()來求出兩條邊之間夾角的度數(shù),并且能夠準(zhǔn)確判斷該度數(shù)對應(yīng)的是哪一個(gè)夾角芦圾。
14.1 跟隨鼠標(biāo)旋轉(zhuǎn)
function Arrow(x, y, color, angle)
{
//箭頭中心x坐標(biāo)蛾派,默認(rèn)值為0
this.x = x || 0;
//箭頭中心y坐標(biāo),默認(rèn)值為0
this.y = y || 0;
//顏色个少,默認(rèn)值為"#FF0099"
this.color = color || "#FF0099";
//旋轉(zhuǎn)角度洪乍,默認(rèn)值為0
this.angle = angle || 0;
}
Arrow.prototype = {
stroke: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.strokeStyle = this.color;
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.stroke();
cxt.restore();
},
fill: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.fillStyle = this.color;
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.fill();
cxt.restore();
}
};
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/arrow.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//實(shí)例化一個(gè)箭頭,中點(diǎn)坐標(biāo)為畫布中心坐標(biāo)
var arrow = new Arrow(cnv.width / 2, cnv.height / 2);
//獲取鼠標(biāo)坐標(biāo)
var mouse = tools.getMouse(cnv);
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var dx = mouse.x - cnv.width / 2;
var dy = mouse.y - cnv.height / 2;
//使用Math.atan2()方法計(jì)算出鼠標(biāo)與箭頭中心的夾角
arrow.angle = Math.atan2(dy, dx);
arrow.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
14.2 圓周運(yùn)動(dòng)
“圓的標(biāo)準(zhǔn)方程”經(jīng)過數(shù)學(xué)推理可以得到圓上任意一點(diǎn)的坐標(biāo)夜焦。
x = centerX + Math.cos(angle)*radius;
y = centerY + Math.sin(angle)*radius;
//再建立一個(gè)ball.js文件壳澳,用于存放一個(gè)小球類
function Ball(x, y, radius, color)
{
//小球中心的x坐標(biāo),默認(rèn)值為0
this.x = x || 0;
//小球中心的y坐標(biāo)茫经,默認(rèn)值為0
this.y = y || 0;
//小球半徑巷波,默認(rèn)值為12
this.radius = radius || 12;
//小球顏色,默認(rèn)值為"#6699FF"
this.color = color || "#6699FF";
this.scaleX = 1;
this.scaleY = 1;
}
Ball.prototype = {
//繪制"描邊"小球
stroke: function (cxt) {
cxt.save();
cxt.scale(this.scaleX, this.scaleY);
cxt.strokeStyle = this.color;
cxt.beginPath();
cxt.arc(this.x, this.y, this.radius, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.stroke();
cxt.restore();
},
//繪制"填充"小球
fill: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.rotation);
cxt.scale(this.scaleX, this.scaleY);
cxt.fillStyle = this.color;
cxt.beginPath();
cxt.arc(0, 0, this.radius, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.fill();
cxt.restore();
}
}
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//實(shí)例化一個(gè)小球卸伞,中心坐標(biāo)為(100,25)抹镊,半徑、顏色都取默認(rèn)值
var ball = new Ball(100, 25);
var centerX = cnv.width / 2;
var centerY = cnv.height / 2;
var radius = 50;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//繪制圓形
cxt.beginPath();
cxt.arc(centerX, centerY, 50, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.stroke();
//計(jì)算小球坐標(biāo)
ball.x = centerX + Math.cos(angle) * radius;
ball.y = centerY + Math.sin(angle) * radius;
ball.fill(cxt);
//角度遞增
angle += 0.05;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
14.2 波形運(yùn)動(dòng)
常見的波形運(yùn)動(dòng)可以分為三種
(1)作用于x軸坐標(biāo)荤傲。
(2)作用于y軸坐標(biāo)垮耳。
(3)作用于縮放屬性(scaleX或scaleY)。
(1) 作用于x軸坐標(biāo)弃酌。
x = centerX + Math.sin(angle) * range;
angle += speed;
(2)作用于y軸坐標(biāo)氨菇。
y = centerY + Math.sin(angle) * range;
angle += speed;
(3) 作用于縮放屬性(scaleX或scaleY)。
當(dāng)正弦函數(shù)sin作用于物體的縮放屬性(scaleX或scaleY)時(shí)妓湘,物體會不斷地放大然后縮小查蓉,從而產(chǎn)生一種脈沖動(dòng)畫的效果。
var ball = new Ball(cnv.width / 2, cnv.height / 2, 25);
var range = 0.5;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.scaleX = 1 + Math.sin(angle) * range;
ball.scaleY = 1 + Math.sin(angle) * range;
ball.fill(cxt);
//角度遞增
angle += 0.05;
})();
14.3 勻速運(yùn)動(dòng)
小球沿著任意方向進(jìn)行勻速運(yùn)動(dòng)
vx = speed * Math.cos(angle * Math.PI/180);
vy = speed * Math.sin(angle * Math.PI/180);
object.x += vx;
object.y += vy;
14.4 加速運(yùn)動(dòng)
小球慢慢減速反向
//實(shí)例化一個(gè)小球
var ball = new Ball(0, cnv.height / 2);
//初始化x軸速度以及加速度
var vx = 8;
var ax = -0.2;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.fill(cxt);
vx += ax;
})();
ax = a * Math.cos(angle * Math.PI/180);
ay = a * Math.sin(angle * Math.PI/180);
vx += ax;
vy += ay;
object.x += vx;
object.y += vy;
14.5 重力
vy += gravity;
object.y += vy;
小球從空中自由降落到地面榜贴,然后反彈豌研,循環(huán)往復(fù)妹田,直到它最終速度為0而停止在地面上。
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化數(shù)據(jù)
var ball = new Ball(cnv.width / 2, 0);
//y軸初始速度為0鹃共,重力加速度為0.2鬼佣,反彈系數(shù)為-0.8
var vy = 0;
var gravity = 0.2;
var bounce = -0.8;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.y += vy;
//邊界檢測
if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
//速度反向并且減小
vy = vy * bounce;
}
ball.fill(cxt);
vy += gravity;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
14.6 摩擦力
摩擦力只會改變速度的大小而不會改變它的方向。換句話說霜浴,摩擦力只能將物體的速度降至0晶衷,但它無法讓物體掉頭往相反的方向移動(dòng)。
vx *= friction;
vy *= friction;
object.x += vx;
object.y += vy;
15 邊界檢測
對于邊界檢測阴孟,將從以下四個(gè)方面來介紹晌纫。(1)邊界限制(2)邊界生成(3)邊界環(huán)繞(4)邊界反彈
15.1 邊界限制
if (ball.x < ball.radius) {
//小球"碰到"左邊界時(shí)
} else if (ball.x > cnv.width - ball.radius) {
//小球"碰到"右邊界時(shí)
}
if (ball.y < ball.radius) {
//小球"碰到"上邊界時(shí)
} else if (ball.y > cnv.height - ball.radius) {
//小球"碰到"下邊界時(shí)
}
15.2 邊界環(huán)繞
指的是當(dāng)物體從一個(gè)邊界消失后,它就會從對立的邊界重新出現(xiàn)永丝,從而形成一種環(huán)繞效果
if(ball.x < -ball.radius){
//小球"完全超出"左邊界時(shí)
} else if(ball.x>cnv.width + ball.radius){
//小球"完全超出"右邊界時(shí)
}
if(ball.y<-ball.radius){
//小球"完全超出"上邊界時(shí)
} else if(ball.y>cnv.height + ball.radius){
//小球"完全超出"下邊界時(shí)
}
15.3 邊界生成
邊界生成锹漱,指的是物體完全超出邊界之后,會在最開始的位置重新生成慕嚷。
if (ball.x < -ball.radius ||
ball.x > cnv.width + ball.radius ||
ball.y < -ball.radius ||
ball.y > cnv.height + ball.radius) {
……
}
15.4 邊界反彈
指的是物體觸碰到邊界之后就會反彈回來哥牍,就像現(xiàn)實(shí)世界中小球碰到墻壁反彈一樣。
//碰到左邊界
if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = -vx;
//碰到右邊界
} else if (ball.x > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
vx = -vx;
}
//碰到上邊界
if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = -vy;
//碰到下邊界
} else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius;
vy = -vy;
}
16 碰撞檢測
碰撞檢測比較常用的是以下兩種方法喝检。(1)外接矩形判定法(2)外接圓判定法
16.1 外接矩形判定法
兩個(gè)矩形左上角的坐標(biāo)所處的范圍嗅辣。如果兩個(gè)矩形左上角的坐標(biāo)滿足一定條件,則兩個(gè)矩形就發(fā)生了碰撞蛇耀。
window.tools.checkRect = function (rectA, rectB) {
return ! (rectA.x + rectA.width < rectB.x ||
rectB.x + rectB.width < rectA.x ||
rectA.y + rectA.height < rectB.y ||
rectB.y + rectB.height < rectA.y);
}
16.2 外接圓判定法
兩個(gè)圓心之間的距離辩诞。如果兩個(gè)圓心之間的距離大于或等于兩個(gè)圓的半徑之和,則兩個(gè)圓沒有發(fā)生碰撞纺涤;如果兩個(gè)圓心之間的距離小于兩個(gè)圓的半徑之和译暂,則兩個(gè)圓發(fā)生了碰撞。
window.tools.checkCircle = function (circleB, circleA) {
var dx = circleB.x - circleA.x;
var dy = circleB.y - circleA.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < (circleA.radius + circleB.radius)) {
return true;
}
else {
return false;
}
}
17 用戶交互
用戶交互的效果(1)捕獲物體(2)拖曳物體(3)拋擲物體
17.1 捕獲物體
(1)矩形的捕獲撩炊。(2)圓的捕獲外永。
-
矩形的捕獲
if (mouse.x > rect.x && mouse.x < rect.x + rect.width && mouse.y > rect.y && mouse.y < rect.y + rect.height) { …… }
-
元的捕獲
dx = mouse.x - ball.x; dy = mouse.y - ball.y; distance = Math.sqrt(dx*dx + dy*dy); if(distance < ball.radius){ …… }
17.2 拖拽物體
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化數(shù)據(jù)
var ball = new Ball(cnv.width / 2, cnv.height / 2, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
//初始化2個(gè)變量:dx和dy
var dx = 0, dy = 0;
cnv.addEventListener("mousedown", function () {
if (ball.checkMouse(mouse)) {
//dx為鼠標(biāo)與球心的水平偏移量
dx = mouse.x - ball.x;
//dy為鼠標(biāo)與球心的垂直偏移量
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//更新小球坐標(biāo)
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
}
function onMouseUp() {
//鼠標(biāo)松開時(shí)伯顶,移除鼠標(biāo)松開事件:mouseup(自身事件)
document.removeEventListener("mouseup", onMouseUp, false);
//鼠標(biāo)松開時(shí)骆膝,移除鼠標(biāo)移動(dòng)事件:mousemove
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
重力反彈
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width / 2, cnv.height, 20);
ball.fill(cxt);
var mouse = tools.getMouse(cnv);
var isMouseDown = false;
var dx = 0, dy = 0;
//oldX和oldY用于存儲小球舊的坐標(biāo)
var oldX, oldY;
//初始速度vx和vy都為0
var vx = 0, vy = 0;
//加入重力和反彈消耗
var gravity = 1.5;
var bounce = -0.8;
cnv.addEventListener("mousedown", function () {
//判斷鼠標(biāo)點(diǎn)擊是否落在小球上
if (ball.checkMouse(mouse)) {
//鼠標(biāo)按下小球時(shí),isMouseDown設(shè)置為true
isMouseDown = true;
//鼠標(biāo)按下小球時(shí)掐暮,將當(dāng)前鼠標(biāo)位置賦值給oldX和oldY
oldX = ball.x;
oldY = ball.y;
dx = mouse.x - ball.x;
dy = mouse.y - ball.y;
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
}
}, false);
function onMouseMove() {
//鼠標(biāo)移動(dòng)時(shí),更新小球坐標(biāo)
ball.x = mouse.x - dx;
ball.y = mouse.y - dy;
//加入邊界限制
//當(dāng)小球碰到左邊界時(shí)
if (ball.x < ball.radius) {
ball.x = ball.radius;
//當(dāng)小球碰到右邊界時(shí)
} else if (ball.x > cnv.width - ball.radius) {
ball.x = cnv.width - ball.radius;
}
//當(dāng)小球碰到上邊界時(shí)
if (ball.y < ball.radius) {
ball.y = ball.radius;
//當(dāng)小球碰到下邊界時(shí)
} else if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
}
}
function onMouseUp() {
//鼠標(biāo)松開時(shí)政钟,isMouseDown設(shè)置為false
isMouseDown = false;
document.removeEventListener("mouseup", onMouseUp, false);
document.removeEventListener("mousemove", onMouseMove, false);
}
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
if (isMouseDown) {
//如果isMouseDown為true,用當(dāng)前小球的位置減去上一幀的坐標(biāo)
vx = ball.x - oldX;
vy = ball.y - oldY;
//如果isMouseDown為true瓢宦,更新oldX和oldY為當(dāng)前小球中心坐標(biāo)
oldX = ball.x;
oldY = ball.y;
} else {
//如果isMouseDown為false驮履,小球沿著拋擲方向運(yùn)動(dòng)
vy += gravity;
ball.x += vx;
ball.y += vy;
//邊界檢測
//碰到右邊界
if (ball.x > canvas.width - ball.radius) {
ball.x = canvas.width - ball.radius;
vx = vx * bounce;
//碰到左邊界
} else if (ball.x < ball.radius) {
ball.x = ball.radius;
vx = vx * bounce;
}
//碰到下邊界
if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius;
vy = vy * bounce;
//碰到上邊界
} else if (ball.y < ball.radius) {
ball.y = ball.radius;
vy = vy * bounce;
}
}
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="200" style="border:1px solid
silver; "></canvas>
</body>
</html>
18 高級動(dòng)畫
18.1 緩動(dòng)動(dòng)畫
想要實(shí)現(xiàn)緩動(dòng)動(dòng)畫疲吸,一般需要以下五個(gè)步驟。
(1)定義一個(gè)0~1之間的緩動(dòng)系數(shù)easing舰绘。
(2)計(jì)算出物體與終點(diǎn)之間的距離捂寿。
(3)計(jì)算出當(dāng)前速度秦陋,其中當(dāng)前速度=距離×緩動(dòng)系數(shù)治笨。
(4)計(jì)算新的位置旷赖,其中新的位置=當(dāng)前位置+當(dāng)前速度等孵。
(5)重復(fù)執(zhí)行第2~4步俯萌,直到物體達(dá)到目標(biāo)咐熙。
var targetX = 任意位置糖声;
var targetY = 任意位置分瘦;
//動(dòng)畫循環(huán)
var vx = (targetX - object.x) * easing;
var vy = (targetY- object.y) * easing;
任意方向的緩動(dòng)動(dòng)畫
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(0, 0);
//定義終點(diǎn)的x軸坐標(biāo)和y軸坐標(biāo)
var targetX = cnv.width * (3 / 4);
var targetY = cnv.height * (1 / 2);
//定義緩動(dòng)系數(shù)
var easing = 0.05;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var vx = (targetX - ball.x) * easing;
var vy = (targetY - ball.y) * easing;
ball.x += vx;
ball.y += vy;
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>
18.2 緩動(dòng)動(dòng)畫應(yīng)用
緩動(dòng)動(dòng)畫不僅可以用于物體的運(yùn)動(dòng),還可以應(yīng)用于物體的其他各種屬性去团,包括大小土陪、顏色鬼雀、透明度以及旋轉(zhuǎn)等
不管緩動(dòng)動(dòng)畫應(yīng)用于什么方面源哩,其實(shí)現(xiàn)思路是一樣的励烦,也就是以下兩個(gè)步驟坛掠。
(1)當(dāng)前速度 =(最終值 - 當(dāng)前值)×緩動(dòng)系數(shù)屉栓。
(2)新的值 = 當(dāng)前值 + 當(dāng)前速度系瓢。
18.3 彈性動(dòng)畫簡介
在緩動(dòng)動(dòng)畫中夷陋,物體滑動(dòng)到終點(diǎn)就停下來了骗绕;但是在彈性動(dòng)畫中酬土,物體滑動(dòng)到終點(diǎn)后還會來回反彈一會兒格带,直至停止。
從技術(shù)上來說微宝,緩動(dòng)動(dòng)畫和彈性動(dòng)畫有以下幾個(gè)共同點(diǎn)。
(1)需要設(shè)置一個(gè)終點(diǎn)嗽桩。
(2)需要確定物體到終點(diǎn)的距離碌冶。
(3)運(yùn)動(dòng)和距離是成正比的扑庞。
兩者的不同在于“運(yùn)動(dòng)和距離成正比的”這一點(diǎn)的實(shí)現(xiàn)方式是不一樣的嫩挤。
(1)在緩動(dòng)動(dòng)畫中岂昭,跟距離成正比的是“速度”约啊。物體離終點(diǎn)越遠(yuǎn)恰矩,速度就越快外傅。當(dāng)物體接近終點(diǎn)時(shí)萎胰,它就幾乎停下來了技竟。
(2)在彈性動(dòng)畫中榔组,跟距離成正比的是“加速度”搓扯。物體離終點(diǎn)越遠(yuǎn)擅编,加速度越大爱态。剛剛開始锦担,由于加速度的影響洞渔,速度會快速增大磁椒。當(dāng)物體接近終點(diǎn)時(shí)本辐,加速度變得很小慎皱,但是它還在加速叶骨。由于加速度的影響天揖,物體會越過終點(diǎn)宝剖。然后隨著距離的變大万细,反向加速度也隨之變大赖钞,就會把物體拉回來雪营。物體在終點(diǎn)附近來回反彈一會兒献起,最終在摩擦力的作用下停止谴餐。
ax = (targetX - object.x) * spring;
ay = (targetY - object.y) * spring;
vx += ax;
vy += ay;
vx *= friction;
vy *= friction;
object.x += vx;
object.y += vy;
舉例:加入摩擦力的彈性動(dòng)畫
<! DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(0, cnv.height / 2);
var targetX = cnv.width / 2;
var spring = 0.02;
var vx = 0;
var friction = 0.95;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var ax = (targetX - ball.x) * spring;
vx += ax;
vx *= friction;
ball.x += vx;
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid
silver; "></canvas>
</body>
</html>