在最近的 JavaScript 30天挑戰中,我有機會了解 HTML 內建 canvas的特性。相對適宜的等級和學習曲線,使我寫下整個過程。
HTML canvas 用最簡單的方式,使web 開發者能夠通過JavaScript在網頁上繪製圖形。這樣,HTML 元素變得更加有趣。
<canvas>
元素只是個容器,你總是需要使用 JavaScript 來準確繪製圖形。有人可能會說,我們總是可以新增這些點,也可以新增SVG,但這又會多麼有趣?
回到 <canvas>
元素:canvas 在 HTML 頁面上是一個矩形。canvas 是預設沒有邊框和內容的。
寫法像這樣:
<canvas id="canvas" width="200" height="100"></canvas>複製程式碼
開始
已經做了那麼多介紹,讓我們專注於使用簡單的原生 JavaScript(不是很舊——ES6)做些有趣的東西。首先,我們看下初始的檔案。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas</title>
<link rel="stylesheeet" href="style.css" />
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="app.js"></script>
</body>
</html>複製程式碼
讓我們慢慢講。我們有個叫 style.css 的樣式層疊表。然後我們定義一個寬800和高800的 canvas 。最後,我們在 script
標籤裡引用了 app.js
,所有魔法都在這裡。我們開始使用 app.js
做一些事情。
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innnerWidth;
canvas.height = window.innerHeight;
ctx.strokeStyle = "#BADA55"
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';複製程式碼
- 我們一開始,在第一行選擇 canvas 元素儲存 到 叫
canvas
的常量裡。 - 然後,我們在同一個 canvas 中獲取2D上下文,並儲存到指定的變數中。
- 設定 canvas 的寬度和高度,分別等於 視窗的寬度和高度。
現在,我們終於有了畫布,繼續定義畫布的最基本屬性。
ctx.strokeStyle
設定或返回用於描繪的顏色,漸變色或圖案。是的,你理解的對:預設顏色是#BADASS
。ctx.lineWidth
設定或返回 當前線條的寬度。我們把它設定為1
,稍後我們會講到這個。ctx.lineJoin
設定或返回 兩條線相匯時所建立的邊角的型別。我們設定當兩條線交匯時的邊角為圓角。ctx.lineCap
設定或返回線的端點的樣式。我們將它設定為圓形,這樣當我們不遇到其他線時,我們仍然會得到相同的整齊的圓形,具體取決於之前定義的線的寬度。
線我們已經完成了所有這些工作,讓我們看看如何在畫布上繪圖。
首先,我們需要為畫布上的滑鼠移動新增事件偵聽器,然後觸發一個繪製內容的函式。我們來看看 app.js
檔案中可能新增的內容。
let isDrawing = fasle;
function draw(e){
if(!isDrawing) return;
console.log(e)
}
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown', () => isDrawing = true);
canvas.addEventListener('mouseup', () => isDrawing = fasle);
canvas.addEventListener('mousedown', () => isDrawing = fasle);複製程式碼
我們來講解下:
- 我們開始定義一個叫
isDrawing
的變數,來判斷使用者是否要在畫布上畫圖。我們會在後面再講到這個。 - 現在,我們有一個叫
draw
的函式,它會被觸發,並負責完成整個繪製動作。 - 最後,我們增加一個約束條件,確保我們捕獲正確的事件,並且只在需要的時候執行
draw
函式。
通過宣告 isDrawing
變數,並將其值設定為 false
,我們在 canvas
元素被載入後設定 canvas
的初始狀態,但還不繪製。然後在每個後續事件偵聽器中,我們使用內嵌函式,並每次根據觸發的事件型別更改 變數 isDrawing
變數的值。
在 draw
函式 的開頭,如果 isDrawing
的值為 false
,函式會執行 return
語句。如果 isDrawing
的值為 true
, 會執行draw 函式。
繪製函式
我們來擴充套件下 draw 函式:
let isDrawing = false;
let lastX = 0;
let lastY = 0;
function draw(e){
if(!isDrawing) return;
console.log(e);
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTO(e.offsetX,e.offsetY);
ctx.stroke();
}複製程式碼
- 我們在開頭定義兩個全域性變數:
lastX
,lastY
,並設定他們的初始值為0
. - 如果你現在進入瀏覽器的控制檯,你會發現你已經記錄了所有滑鼠移動。這個
MouseEvent
物件有一些非常重要和有用的屬性:
滑鼠事件物件
我們只關注物件裡的 offsetX
和 offsetY
屬性。
- 每次執行
ctx.beginPath
,我們新建一個路徑,或者重置當前的路徑。每次事件被觸發,我們希都會執行這個動作。 ctx.moveTo
移動路徑到畫布指定的點上,但還沒繪製線。在例子中,在函式外面的全域性環境裡定義lastX
和lastY
。ctx.lineTo
增加新的點,並從上一個點到新的點之間繪製一條線。ctx.stroke()
真正繪製你已經定義的路徑 —— 辛苦了,夥計們!
在 ctx.lineTo
裡,我們利用事件的屬性 offsetX
和 offsetY
獲得 在畫布上的最新點的 X
和Y
,只用 ctx.lineTo
繪製一條線。
我們幾乎所有的東西都準備好了。在頁面上的每個滑鼠事件都會在畫布上繪製一條線——但還有些問題,沒有太多酷的東西。所以,讓我們加點酷的東西。
酷!
現在,所有線都是從畫布中的(0,0)
點繪製的。 我們將其設定為載入畫布或執行 draw
函式時開始繪製的初始點。
讓我們解決這個問題,以獲得更好的實時體驗。
如果考慮一下,答案非常簡單:每次執行draw函式時,我們都希望初始點始終是 MouseEvent
物件的 offsetX
和 offsetY
屬性。
通過使用 ES6 的陣列解構,我們可以將變數 lastX
和 lastY
分別重置為 滑鼠事件物件的屬性 offsetX
和 offsetY
,我們在 draw
函式的最後執行。我們來看看加了新東西后的 app.js
。
function draw(e){
if(!isDrawing) return;
console.log(e);
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(e.offsetX,e.offsetY);
ctx.stroke();
[lastX,lastY] = [e.offsetX,e.offsetY];
}
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown',(e) => {
isDrawing = true;
[lastX,lastY] = [e.offsetX,e.offsetY];
};
canvas.addEventListener('mouseup',()=> isDrawing = false);
canvas.addEventListener('mousemove',()=> isDrawing = false);複製程式碼
- 當
mousemove
事件發生時,我們會執行draw
函式。然後繼續執行,在draw
函式裡使用 ES6 解構 設定 變數lastX
和lastY
。 - 當
mousedown
事件發生時,首先我們執行巢狀函式,如你所見,我們再一次將 變數lastX
和lastY
設定為當前事件的偏移屬性。這是為了確保當我們在畫布上從一個點移動到另一個點時,我們可以在畫布上看到這條線。
讓它變得豐富多彩,並在筆畫中新增一些動態元素。
let hue = 0;
let direction = true;
function draw(e){
if(!isDrawing) return;
console.log(e);
ctx.strokeStyle = `hsl(${hue},100%,50%)`;
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(e.offsetX,e.offsetY);
ctx.stroke();
[lastX,lastY] = [e.offsetX,e.offsetY];
hue++;
if(hue>=360){
hue = 0;
}
if(ctx.lineWidth >= 75 || ctx.lineWidth <= 1){
direction = !direction;
}
if(direction){
ctx.lineWidth++;
} else {
ctx.lineWidth = 1;
}
}
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown',(e) => {
isDrawing = true;
[lastX,lastY] = [e.offsetX,e.offsetY];
};
canvas.addEventListener('mouseup',()=> isDrawing = false);
canvas.addEventListener('mousemove',()=> isDrawing = false);複製程式碼
神聖時刻!!
這還有很多事要處理,我們一一分解:
- 我定義一個新的變數
hue
,並設定其值為0
。 - 如果你還不知道色調,為什麼會很棒,那就去谷歌試試吧,或者只需點選這裡就可以了。
在其最簡單的形式,hsl
讓我們在從0到360範圍裡使用相同的彩虹的顏色。
每個數字都有一個亮度和透明度。
定義 hsl 看起來像這樣:hsl(173,99%,50%)
。
此處 173
表示顏色 , 99%
是亮度,50%
是透明度。
同樣,通過使用 ES6 的模板字串,來修改 hsl
的值,像這樣:
ctx.strokeStyle = `hsl( ${hue}, 100%, 50%)`
接下來,我們增加 hue
變數的值,該變數在每個mousemove
事件中更改筆觸的顏色。
一旦色調值增加到360,我們將在上述要點的第14行上將 hue
的值重置為0。
但即使我們不這樣做,我們仍然會有同樣的結果。
即便如此,我們還是做正確的事吧
if(hue > 360){
hue = 0
}複製程式碼
下一步,我們加點動態的東西,使筆畫的寬度實時變化,像這樣:
if(ctx.linewidth >= 75 || ctx.lineWidth <= 1){
direction = ! direction;
}
if(direction){
ctx.lineWidth++
}else {
ctx.lineWidth = 0
}複製程式碼
這裡我們所做的就是首先檢查當前的線寬是大於75還是小於1。
如果是,則將初始值為 true
的變數 direction
取反。
接下來,我們檢查變數 direction
的值是否為true
。
如果是,則將 lineWidth
的值增加1
,否則將 lineWidth
重置為0
。
沒有很多JavaScript。 如果你正確跟著做,你應該有個漂亮的畫布了。
讓我們快速地看一下最終的檔案結構是什麼樣子的。
因為我們只更改了app.js
檔案,所以我將只向你展示這一點,因為index.html從一開始就幾乎沒有變化。
canvas.width = window.innerWidth;canvas.height = window.innerHeight;ctx.strokeStyle = '#BADA55';ctx.lineWidth = 1;ctx.lineJoin = 'round';ctx.lineCap = 'round';let isDrawing = false;let lastX = 0;let lastY = 0;let hue = 0;let direction = true;function draw(e) { if(!isDrawing) { return; // 滑鼠沒有按下時不執行. } console.log(e); ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`; ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(e.offsetX, e.offsetY); ctx.stroke(); [lastX, lastY] = [e.offsetX, e.offsetY]; hue++; if(hue>=360){ hue = 0; } if(ctx.lineWidth >= 75 || ctx.lineWidth <= 1) { direction = !direction; } if(direction){ ctx.lineWidth++; } else { ctx.lineWidth = 1; }}canvas.addEventListener('mousemove', draw);canvas.addEventListener('mousedown', (e) => { isDrawing = true; [lastX, lastY] = [e.offsetX, e.offsetY];});canvas.addEventListener('mouseup', ()=> isDrawing = false);canvas.addEventListener('mouseout', () => isDrawing = false);複製程式碼
在 canvas 和 JavaScript 的結合中,介紹的只是冰山一角。 我會鼓勵你做更多的研究,使畫布看起來更好。 也許新增幾個按鈕來清除螢幕,或者選擇一種特定的顏色在畫布上繪製。 有很多選擇!