在最近的 JavaScript 30天挑戰中,我有機會了解 HTML 內置 canvas的特性。相對適宜的等級和學習曲線,使我寫下整個過程。javascript
HTML canvas 用最簡單的方式,使web 開發者可以經過JavaScript在網頁上繪製圖形。這樣,HTML 元素變得更加有趣。css
<canvas>
元素只是個容器,你老是須要使用 JavaScript 來準確繪製圖形。有人可能會說,咱們老是能夠添加這些點,也能夠添加SVG,但這又會多麼有趣?html
回到 <canvas>
元素:canvas 在 HTML 頁面上是一個矩形。canvas 是默認沒有邊框和內容的。java
寫法像這樣:git
<canvas id="canvas" width="200" height="100"></canvas>複製代碼
已經作了那麼多介紹,讓咱們專一於使用簡單的原生 JavaScript(不是很舊——ES6)作些有趣的東西。首先,咱們看下初始的文件。github
<!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
作一些事情。web
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
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 的結合中,介紹的只是冰山一角。 我會鼓勵你作更多的研究,使畫布看起來更好。 也許添加幾個按鈕來清除屏幕,或者選擇一種特定的顏色在畫布上繪製。 有不少選擇!