[Hero Of UnderGround 地下城] — 7F Canvas 畫板

成果 & 程式碼

Demo:點我

Code Source:點我

Introduction & 前言

自從接觸 JS 後一直不敢觸碰的一塊,還記得前陣子碰到朋友在做抽抽樂轉盤,那時候便是看她用 Canvas 做出來的,狹路相逢(誤),出來混總是要還的,這次的大魔王就是 Canvas 了!

7F的大魔王- Canvas畫板遊戲

Summary & 摘要

由於JS地下城每個BOSS的弱點都不一樣,每一層都要由弱點去進行攻略,本次BOSS的弱點有二 項。

  • 【特定技術】遊戲規則

繪圖區請使用 Canvas 來設計,上方的控制列與下方的畫筆調整可不用

-> a.SAVE :點擊後可直接下載轉出的 PNG 圖片

-> b.CLEAR ALL:清除畫版樣式

-> c.UNDO、REDO:上一步、下一步

-> d.點擊箭頭時,功能列介面皆可進行收闔

  • 【擴充功能】請再自行增加「兩個功能」,我相信勇者們都是很有梗的~

額外條件

  1. 你攻略此 BOSS 的攻略過程心得

  2. 你學習 Canvas 的學習過程

  3. 為什麼要用 Canvas,與一般 DOM 繪製有什麼優缺點?


原諒我字醜,重寫了三小時

萬事起頭難…..嗎?

起頭難不難,其實要先感謝 Huiyu 大大,因為很多東西都算是菜雞我第一次接觸,所以會先去爬文看看其他大佬們如何起手。這邊參考了一位國外大神的 Youtube ,教你如何創造出簡單的畫布。
這次切版也用到比較多的動畫,順便訓練一下流暢性,還有順便解決 BOSS 的第一個弱點知一 d 。但搞切版就花了半天去了,原諒我菜TAT,比較特別的部分就是前面多位大大提過的 input color 了,後面的部分會提到。

讚嘆感嘆便利的網路還有多位大神無私奉獻,現在很多東西其實不難了。

1
<canvas *id*="drawing_board" *width*='800' *height*='800'></canvas>

Canvas 可以說是一張畫布,而你想要在上面幹啥都行,別想歪。

預設的畫布大小是 300px * 150px(寬 * 高)。但你也可以透過 HTML 寬、高屬性(attribute)自訂。*點我前往文章*

1
2
const canvas = document.getElementById('drawing_board');
const ctx = canvas.getContext('2d');

這可以說是只要寫到 Canvas 就一定會寫到的,給他的一個變數名稱,然後指定你是要用 2d 還是 3d 來作畫,這邊我們用到 2d 就夠了。之後給他寬高,還有畫筆,畫筆顏色…等等

1
2
3
4
5
6
7
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 畫筆的顏色粗細等等
ctx.strokeStyle = 'black'; // 色筆顏色
ctx.lineJoin = 'round'; // 兩條線交會的地方,是圓角
ctx.lineCap = 'round'; // 繪製結束的線帽
ctx.lineWidth = 10; // 色筆大小

設定完成後我們還需要做到幾件事情,既然我們要做畫板,就必須讓筆掌握在使用者手中,所以我們必須判斷使用者何時下手何時停手畫畫,然後給使用者 一個移動的監聽事件 , 一個按下滑鼠的監聽事件 , 一個放開滑鼠的監聽事件 , 最後一個滑鼠離開的監聽事件。

1
2
3
4
5
6
7
8
9
10
11
12
// 判斷是不是點下滑鼠了
let isDrawing = false;
// 設定滑鼠座標
let lastX = 0;
let lastY = 0;

// 滑鼠移動的監聽事件
canvas.addEventListener('mousemove', draw);

function draw(e) {
console(e);
}

滑鼠監聽事件顯示出滑鼠座標


畫下第一筆

完成了鼠標的座標抓取後,我們就可以來判斷何時該畫畫了。

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
// 滑鼠按下後
canvas.addEventListener('mousedown', (e) => {
isDrawing = true; // 判斷是否正在畫畫
[lastX, lastY] = [e.offsetX, e.offsetY];
});

// 滑鼠放開後
canvas.addEventListener('mouseup', (e) => {
push(); // 跑進計算步數的 function 後面會提到
isDrawing = false; // 判斷是否正在畫畫
});

// 滑鼠離開後
canvas.addEventListener('mouseout', () => isDrawing = false);

// 畫畫得 function 可以改為
function draw(e) {
if (!isDrawing) return; // 沒有點擊時不回覆座標
ctx.beginPath();
// 開始
ctx.moveTo(lastX, lastY);
// 結束
ctx.lineTo(e.offsetX, e.offsetY);
// 畫圖
ctx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
}

使用 toDataURL() 達到 UNDO REDO

1
2
let step = -1;
let userhistory = [];

首先我們必須設定兩個變數,一個用來計算步數,一個用來裝轉成 Base64 編碼的圖片字串,用陣列來放,讓我們靠 Step 就能達到上一步,下一步的功能,每畫一筆,我們就跑一次 PushfunctionStep++,並把圖片的字串放進陣列裡,讓讀取的時候能取出並蓋上畫布 ,反之上一步就是 −−

1
2
3
4
5
6
7
function push() {
step++;
if (step < userhistory.length - 1) {
userhistory.length = step + 1
}
userhistory.push(canvas.toDataURL()); // 當前影像存成 Base64 編碼的字串並放入陣列
}

這邊需要特別提到,因為一開始布數從 -1開始,為的是讓他可以符合陣列的特定,從 0開始算,這樣讀取時才能讀到資料,但是我們的上一步條件必須 Step > 0,所以一開始我們就必須讓 Step+1 ,不然會變成我一樣,苦惱好幾小時,為什麼第一筆畫都不能 UNDO …… 這邊感謝 7F 前端之碑主 陳顯然 大大的解答。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 上一步的function
function undo() {
if (step > 0) {
step--;
let canvaspic = new Image(); //建立新的 Image
canvaspic.src = userhistory[step]; //載入剛剛存放的影像
canvaspic.onload = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(canvaspic, 0, 0) //匯出影像並從座標 x:0 y:0 開始
}
}
// 最後這邊是讓上一步下一步可以在不能使用時顯示灰色及更改鼠標,可以不加
if (step < userhistory.length && step > 0) {
$('.next-step').removeClass('disable-btn');
}
}

上一步的做法跟下一步的做法相反而已,另外剛開始寫完這段的時候怎麼試都無法上一步,一直到最後加了 ctx.clearRect 後才有了反應,這邊不知道是不是有大大說的 Canvas 不吃 CSS 的問題,會再進一步探討並研究的。

1
<a class="save" href="#" download="我的偉大創作">
1
2
3
4
5
// 儲存畫布的function
function download(position) {
const dataURL = canvas.toDataURL('image/png') // 把影像轉成指定格式的 URL 字串
position.href = dataURL;
}

另外儲存的部分在 html 上使用 href 去裝我們的字串,download 則是下載後的檔案名稱。

存在字串裡的 64base 圖片字串


關於擴充功能

由於對 Canvas 是第一次接觸,所以就把各位大大做的功能拿來練手,畢竟菜雞腳程慢,到 7F 時各位大佬都已經飛天啦…這邊我選的功能是 橡皮擦、上傳圖片、顏色選擇器…

八土地下城(誤

先說說簡單的 橡皮擦 其實就是把畫筆顏色改成背景顏色,然後看起來就像是擦掉了。

顏色選擇器

Input Color 对象是 HTML5 新增的,Input Color 對象代表了使用 type=”color”属性的 HTML 元素 。

1
<input type="color" id="myColor">

隨後是我覺得超酷的功能,顏色選擇器,其實也很簡單,只是修改它的外觀比較麻煩。在 html 上加上這個之後,你會看見一個長方形的 input 但是點下去你就能選顏色了,透過 change 事件 input.value() 你就能取得使用者選擇的顏色了。然後就讓你的 ctx.strokeStyle 畫筆顏色 = input.value() 吧!

這邊非常感謝 汶穎大大

圖片上傳

其中比較麻煩的應該就是上傳功能了,不過簡單理解下,就是透過 htmlinput files 上傳後,一樣透過 toDataURL() 把它轉成字串,然後用 CanvasdrawImage 把它畫出來。

1
<input type="file" class="form-control-file photo-up rounded" style="display: none;" id="photo_up" />

必須使用Change事件

需要注意的就是必須使用 Change 事件,不然圖片還沒選到,就會跑進裡面讀你的陣列有沒有圖片,會造成錯誤。

不要像我一樣傻傻地用Click搞了好幾小時

Canvas 的學習過程

這次可以說是踩雷踩了又踩,看別人寫的 Code 像是數學老師解題一樣,自己寫出來又是一回事,不過就正式這樣才有成長,從剛開始什麼都不懂,現在至少能畫個圓形和直線出來…噗

為什麼要用 Canvas?

Canvas歷史由來- 取至維基百科

Canvas 除了能做到畫畫 還能做出非常具有特色的3D遊戲,及動畫,重點是方便且快速,所以非常多人使用,必且在大神的學習在釋出的寶貴經驗之中, Canvas 甚至被做成了非常方便的套件。

成果出爐了


03/19更新 — 熟用 fillStyle 及 fillRect 別讓背景離家出走

由於沒注意到下載後背景的顏色,造成一開始背景是透明的,讓我大驚失色。詳細顏色設定請點我

Canvas預設背景是透明的

1
2
3
4
5
6
function firstpush() {
ctx.fillStyle = '#E8E8E8'; // 讓第一次進來跑 function 的時候就加上背景顏色
ctx.fillRect(0, 0, canvas.width, canvas.height)
push();
}
// 因為 canvas.width = window.innerWidth;

還記得一開始說我們的** Step** 預設是 -1 對吧,一開始我們進來必須設定一個 function 去讓它加一,我們就可以在裡面加上背景顏色,fillStyle 其實就是設定要填滿的顏色,記得要配上 **fillRect **服用,它是 用於填充繪圖的顏色、漸變或模式,預設是黑色,你也可以設定一定的大小範圍去填滿它,例如 **ctx.fillRect(20,20,150,100)**。

明顯了許多,背景回家了


gradient 讓你的人生有起有落

配上 gradientpattern 服用,可以創造出漸層,也可以使用後者讓你的圖片填充你的畫布。

取自W3School [點我](https://www.w3schools.com/TAgs/tryit.asp?filename=tryhtml5_canvas_createlineargradient)看文章取自W3School 點我看文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 起手式
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

// 四個參數設定下方會解釋
var grd = ctx.createLinearGradient(0, 0, 170, 0);
// 起始顏色(這邊為黑色)
grd.addColorStop(0, "black");
// 結束顏色(這邊為白色)
grd.addColorStop(1, "white");
// 填充的顏色改為漸層Gradient方式
ctx.fillStyle = grd;
// 繪畫的座標及範圍大小
ctx.fillRect(20, 20, 150, 100);

ctx.createLinearGradient(漸變開始點的 x 座標,
漸變開始點的 y 座標,漸變結束點的 x 座標,漸變結束的 y 座標)

詳細說明請點我查看


Conclusion & 結論

我想這次的 7F 不算破關,是靠著眾多前人智慧以及萬能谷歌大神,讓我學到了寶貴的一課,關於 Canvas 還有很多值得我去探討,這是較不容易學會卻是非常好用的東西之一,身為一位勇者,就是必須一直嘗試跌倒在嘗試啊。
雖然每一層樓都有主要的目標 BOSS 在,但卻會常常在路上學到其他壯大自己的知識,比如這次意外學習到 ES6模板源文本(template strings ,有興趣可以點我查看,最後除了繼續努力之外還是要列出檢討的地方,然後朝下一關繼續加油,完畢。(這次真的需要好好加油呀…

  • JS

  • 邏輯

  • 文筆

  • 體重


我的其他範例

  • Hero Of UnderGround — 1F Multiplicatio 九九乘法表

    Demo:點我

    Code Source:點我

  • Hero Of UnderGround — 2F Clock 時鐘

    Demo:點我

    Code Source:點我

  • Hero Of UnderGround — 3F Calculator 計算機

    Demo:點我

    Code Source:點我

  • Hero Of UnderGround — 4F World Clock 各國時區

    Demo:點我

    Code Source:點我

    Blog:點我

  • Hero Of UnderGround — 5F AQI 全台空氣指標儀表板

    Demo:*點我

    Code Source:*點我

    Blog:點我

  • Hero Of UnderGround — 6F SECONDS CHALLENGE 倒數遊戲

    Demo:點我

    Code Source:點我

    Blog:點我


參考網站