新手村08 - HTML5 Canvas

08 - Fun with HTML5 Canvas

俗話說的好,一天一蘋果,醫生遠離我

一天一 JS,What the f*ck JavaScript?

small steps every day - 記錄著新手村日記

完成目標

  • 功能
    • 可以畫圖的書布
    • 粗細、顏色變化
  • 畫面
    • 邊畫邊換色、粗細
    • 停下來,再繼續畫,粗細和顏色與上次停筆相同

index_START.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas</title>
</head>
<body>
<canvas id="draw" width="800" height="800"></canvas>
<script>
</script>

<style>
html, body {
margin: 0;
}
</style>

</body>
</html>

JS - step by step

首先,這次的範例只是Canvas的基本、滑鼠的操作,如果很喜歡 Canvas 的人,歡迎來我的 Codepen 看更多範例

繪圖基礎語法與動畫原理:https://tinyurl.com/yxftfxoz

畫布的座標系操作:https://tinyurl.com/y4s8fxjr

從事件的角度,先來寫使用者的觸發事件吧!分別是 mousedownmousemovemouseupmouseleave 四種事件,記得 mousemove 這個事件是會一直觸發,使用者在脫離 Canvas 這個 800x800 畫布的時候,也會觸發脫離框框事件,分別有兩種作法:一個是 mouseoutmouseleave,而兩者的差別在 DOM 的層級。

另外,因為要使用者按下去才能畫,因此在 mousemove 方法中,我們要假設如果狀態不能畫畫的 if(!drawing) return,就不能畫畫

mouveseout v.s mouse leave:https://tinyurl.com/y282qc3g

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
const canvas = document.querySelector("#draw");
let drawing = false;

canvas.addEventListener("mousedown",()=> {
drawing = true;
})
canvas.addEventListener("mousemove",()=> {
if(!drawing) return;
console.log("move"); // 假設console視為畫畫
})
canvas.addEventListener("mouseup",()=> {
drawing = false;
})
canvas.addEventListener("mouseleave",()=> {
drawing = false;
})
</script>

透過 CanvasRenderingContext2D 來操作 Canvas 的 Context,記得不是操作它的 DOM!處理一下要畫畫線條的顏色 ctx.fillStyle,它是透過HSL 色環來計算顏色 "hsl(0,100%,50%)",各欄位分別意思是(顏色、飽和度、明亮度)、調整筆畫粗細 linewidth 、筆畫收尾的形狀 lineCap、轉折角 lineJoin

CanvasRenderingContext2D:https://tinyurl.com/y39wgnms
CanvasRenderingContext2D.fillStyle:https://tinyurl.com/y5j8zu5g
CanvasRenderingContext2D.lineWidth:https://tinyurl.com/q3pm3aw
CanvasRenderingContext2D.lineCap:https://tinyurl.com/y63rq9rf
CanvasRenderingContext2D.lineJoin:https://tinyurl.com/yxesyrbw

1
2
3
4
5
6
7
<script>
let ctx = canvas.getContext('2d');
ctx.strokeStyle = `hsl(0,100%,50%)`;
ctx.lineWidth = 100;
ctx.lineCap = 'round';
// 下略
</script>

現在來製作畫畫吧!(點對點的概念,按下去會是一個點,然後會連到結束時候的點成為一條線),必須將按下時的點及結束的點存起來,透過 x、y 變數存下 offsetX、offsetY

MouseEvent.offsetX:https://tinyurl.com/phhseue
MouseEvent.offsetY:https://tinyurl.com/yys9e773

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script>
const canvas = document.querySelector("#draw");
let ctx = canvas.getContext('2d');
ctx.strokeStyle = `hsl(0,100%,50%)`;
ctx.lineWidth = 50;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
let x = 0;
let y = 0;

let drawing = false;
canvas.addEventListener("mousedown",(e)=> {
drawing = true;
[x,y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mousemove",()=> {
if(!drawing) return;
console.log("move");
})
canvas.addEventListener("mouseup",()=> {
drawing = false;
})
canvas.addEventListener("mouseleave",()=> {
drawing = false;
})
</script>

畫製 Canvas 圖的規則滿前顯易懂的,可以參考下面的範例連結(切記在製作Canvas的時候程式結束一定要有分號,不然都會出錯喔!)

CanvasRenderingContext2D.beginPath():https://tinyurl.com/ycrkznet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<script>
const canvas = document.querySelector("#draw");
let ctx = canvas.getContext('2d');
ctx.strokeStyle = `hsl(0,100%,50%)`;
ctx.lineWidth = 50;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';

let drawing = false
let x = 0, y = 0;

canvas.addEventListener("mousedown",(e)=> {
drawing = true;
[x, y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mousemove",(e)=> {
if(!drawing) return;
console.log("move");

ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
[x, y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mouseup",()=> {
drawing = false;
})
canvas.addEventListener("mouseleave",()=> {
drawing = false;
})
</script>

讓顏色會隨著紅橙黃綠藍靛紫 & 粗細會持續變化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<script>
const canvas = document.querySelector("#draw");
let ctx = canvas.getContext('2d');

colorDeg = 0;
lineWidth = 50;
dir = 1;
ctx.strokeStyle = `hsl(${colorDeg},100%,50%)`;
ctx.lineWidth = lineWidth;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';

let drawing = false
let x = 0, y = 0;

canvas.addEventListener("mousedown",(e)=> {
drawing = true;
[x, y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mousemove",(e)=> {
if(!drawing) return;
console.log("move");

ctx.beginPath();

// 讓角度持續的在 0-360 變化
colorDeg = colorDeg < 360 ? colorDeg + 1 : 0;
ctx.strokeStyle = `hsl(${colorDeg},100%,50%)`;

// 從大到變小 再到變大
if (lineWidth < 1 || lineWidth > 50) {
dir *= -1;
}
lineWidth -= dir ;

ctx.lineWidth = lineWidth;
ctx.moveTo(x, y);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
[x, y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mouseup",()=> {
drawing = false;
})
canvas.addEventListener("mouseleave",()=> {
drawing = false;
})
</script>

就大功告成啦!

JS - Final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script>
const canvas = document.querySelector("#draw");
let ctx = canvas.getContext('2d');

colorDeg = 0;
lineWidth = 50;
dir = 1;
ctx.strokeStyle = `hsl(${colorDeg},100%,50%)`;
ctx.lineWidth = lineWidth;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';

let drawing = false
let x = 0, y = 0;

canvas.addEventListener("mousedown",(e)=> {
drawing = true;
[x, y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mousemove",(e)=> {
if(!drawing) return;
console.log("move");

ctx.beginPath();
colorDeg = colorDeg < 360 ? colorDeg + 1 : 0;
ctx.strokeStyle = `hsl(${colorDeg},100%,50%)`;
if (lineWidth < 1 || lineWidth > 50) {
dir *= -1;
}
lineWidth -= dir ;
ctx.lineWidth = lineWidth;
ctx.moveTo(x, y);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
[x, y] = [e.offsetX, e.offsetY];
})
canvas.addEventListener("mouseup",()=> {
drawing = false;
})
canvas.addEventListener("mouseleave",()=> {
drawing = false;
})
</script>

本刊同步於個人網站:http://chestertang.site/

本次範例程式碼原作者來源:https://tinyurl.com/yxhsxcnl