0%

JS地下城7F-畫版

作品

畫版

畫板

規則

  1. 繪圖區請使用 Canvas 來設計,上方的控制列與下方的畫筆調整可不用
  2. SAVE :點擊後可直接下載轉出的 PNG 圖片
  3. CLEAR ALL:清除畫版樣式
  4. UNDO、REDO:上一步、下一步
  5. 點擊箭頭時,功能列介面皆可進行收闔
  6. 【擴充功能】請再自行增加「兩個功能」

規劃

  • 了解 canvas 基礎運作模式
  • 取得滑鼠當前座標接著執行以下動作後並繪製
    • 滑鼠點擊
    • 滑鼠移動
    • 滑鼠放開
  • 設計上方功能列功能
    • undo
    • redo
    • clearAll
    • save
  • 設計下方工具列功能
    • 畫筆繪製顏色選擇
    • 畫筆粗細
  • 設計功能列與工具列摺疊效果
  • 設計額外功能
    • 取得 input type="color" 數值,讓畫筆顏色有更多選擇
    • 取得 input type="range" 數值,讓畫筆粗細可調整
    • 取得視窗大小,讓畫版大小自動調整
    • 繪製九宮格對照功能

Canvas

  • 為網頁標籤之一,具有結束標籤 <canvas></canvas>
  • 只有寬高屬性,而圖片多了 src 與 alt 屬性
  • 原點 (0, 0) 在左上角,x 軸正數向右,y 軸正數向下

繪製方式

如同取用網頁元素一樣,先取得該元素 :

  • 暫時設定 ID 名稱為 draw document.getElementById('draw')
  • 該元素其中一個方法為 getContext()
  • 其繪製的圖形為點陣圖,MDN 建議直接使用內建寬高屬性而非利用 CSS 來設定寬高,否則可能會造成扭曲

接著,讓我們來畫個正方形,它可以幫我們了解原點與相對的位置。

1
2
<!-- 建立畫布 -->
<canvas id="draw" class="canvas--border" width="300" height="300"></canvas>
1
2
3
4
5
6
// 加上 border 比較容易知道位置
.canvas {
&--border{
border: 1px black solid;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 抓取網頁元素 ID draw */
let draw = document.getElementById('draw')

// 繪製何種類型
let ctx = draw.getContext('2d')

// 原點在左上角

// fillStyele 使用顏色
ctx.fillStyle = "rgba(0, 0, 200, 0.5)"

// fillRect 填色矩形
// (x 軸座標, y 軸座標, x 軸方向長度, y 軸方向長度)
ctx.fillRect (250, 250, 50, 50)

// clearRect 透明無色矩形
ctx.clearRect(250, 250,25,25)

// strokeRect 不填色矩形,預設黑色邊框
ctx.strokeRect(115,115,70,70)

我們再畫一條直線試試 :

1
2
3
4
5
6
7
8
/* 延續 html 元素與 CSS */
ctx.beginPath(); // 告知路徑開始
ctx.moveTo(150,150); // 將起點移動到這個座標
ctx.lineTo(175,75); // 設定路徑到這個座標
ctx.strokeStyle = '#FFA500' // 設定顏色
ctx.lineWidth = 10 // 設定線條粗細
ctx.lineCap = 'round' // 線條前後圓潤
ctx.stroke() // 繪製

現在我們知道原點與相對位置以及線條繪製方式,而且還知道可以設定顏色與線條粗細,那麼就可以利用滑鼠座標來畫畫了。


滑鼠座標

在瀏覽器中,有許多的監聽事件,滑鼠事件也是其中之一,還可以再細分成按下時、移動時以及放開時等等事件,首先,我們先來取得目前的滑鼠座標。

取得滑鼠座標

1
2
3
4
5
6
7
/* 監聽事件 */
// 在空白區域點一下就能知道目前的滑鼠座標
window.addEventListener('mousedown', (e)=> {
console.log(
`x 軸座標為 : ${e.offsetX},y 軸座標為 ${e.offsetY}`
)
})

若要限定在畫版中才有動作,將對象 window 改為要監聽的網頁元素即可。

滑鼠連續事件

上面我們已經知道如何取得滑鼠座標,其實已經可以繪製連續的圖形了,建立以下事件 :

  • 建立事件 mousedown,取得開始座標
  • 建立事件 mousemove,取得移動座標
    • 在此事件中執行繪製函式
    • 將新的移動座標,作為開始座標
    • 原本的開始座標,作為結束座標
    • 不想建立太多暫存變數,自己將 x 與 y 座標放入一個陣列作為暫存座標
  • 建立事件 mouseup,取消 mousemove 事件

上方功能列

undo、redo、clearAll

當我們繪製圖形時,每一筆都是一個新圖形,所以可以將圖形暫存下來像是這樣 :

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
49
50
51
52
53
54
55
56
57
58
// 取得網頁元素
const drawDisplay = document.querySelector('.js-draw');

// 宣告變數,暫存我們要的資料
let drawStorage = ['畫第一筆', '畫第二筆', '畫第三筆']
let base64 = ''

// 再宣告一個變數,作為步驟索引
// -1 是為了當滑鼠放開後,會與暫存圖形之陣列索引一致
let step = -1

let ctx = drawDisplay.getContext('2d')

// 繪製函式,實際使用會在 mousemove 事件中呼叫
const canvas = () => {
// 繪製圖形
ctx.beginPath()
// ...
ctx.stroke()
base64 = drawDisplay.toDataURL()
}

// 當滑鼠移動時,就會邊繪製圖形
const mouseMove = () => {
canvas()
}

// 當滑鼠放開後
const mouseUp = () => {
step++
// 若目前的步驟數值小於暫存圖形資料長度,那麼就將後面的資料移除
// 假設目前的步驟數值已是最後一步,則無影響
// 若執行 undo 後,又新增一筆上去,就會將執行 undo 之前的圖形資料移除
if (step < drawStorage.length) {
drawStorage.length = step
}
drawStorage.push(base64)
}

const undo = () => {
let lastDraw = new Image()
if (step >= 0) {
step--
lastDraw.src = drawStorage[step]
ctx.beginPath();
// 清除畫版,若單純執行就是 clearALL
ctx.clearRect(0,0, 畫版寬, 畫版高)
// 重新讀取資料,若沒有使用 onload 就不會再次顯示
lastDraw.onload = () => {
ctx.drawImage(lastDraw, 0, 0)
}
}
}

const redo = () => {
// 功能與 redo 相反,唯一的不同是步驟增加
step++
}

save

a 連結的屬性加入 download 並設定預設檔名,點擊時就能下載

1
<a href="放入 base64 格式的圖檔" download="yourDraw"></a>

下方工具列

選擇畫筆顏色

單純將顏色填入也是可以,不過既然是畫版就想取得更多顏色,於是利用 input 的類型來取得顏色,利用函式將顏色回填至畫筆選擇區。
不過取得顏色時,遇到一個問題,就是這個類型利用 JS 取值時似乎只能取得 hex 格式,若利用 JS 寫 sass 格式的 darken ,是不會再次編譯的,也就無法取得相近色,因此暫時利用 hex 轉成 rgba 來取得相近顏色。

1
<input type="color" value="#000000" class="js-colorInput">
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
const colorInput = document.querySelector('.js-colorInput')

const colorSelect = (e) => {
colorsInit(e.target.value)
}

const colorsInit = (color) => {
// 清空暫存顏色
newColors = []
// 將 hex 格式轉成 rgb 格式
let r = parseInt(color.substr(1,2), 16)
let g = parseInt(color.substr(3,2), 16)
let b = parseInt(color.substr(5,2), 16)
let secondaryColor = `rgba(${r}, ${g}, ${b}, 0.75)`
let thridColor = `rgba(${r}, ${g}, ${b}, 0.55)`

// colorBtns 自定義,目的是取得所有網頁元素中所有畫筆選擇按紐
// 暫時將類陣列轉為陣列,以利後續擴充功能時可以使用
let newArray = Array.from(colorBtns)

// 畫面設計關係, 0 與 1 為白色與黑色
// 更改選擇按鈕的背景色
newArray[2].style.backgroundColor = color
newArray[3].style.backgroundColor = secondaryColor
newArray[4].style.backgroundColor = thridColor

// 將顏色暫存,以便畫筆選擇時取用顏色
newColors.push(color, secondaryColor , thridColor)
}

擴充功能

九宮格

疊兩個圖層,一個為畫版,一個為已繪製九宮格畫版,這裡利用 position 來做,目標是讓畫版圖層保持在上方。

  • 繪製四條直線,兩條橫,兩條直
  • 分別設定為寬高之 0.33 與 0.66
  • 設定兩個畫版背景色相同
  • 當勾選下方工具列之九宮格選項時,讓畫版圖層變更為透明

自動調整畫版大小

在全域取得視窗寬高,再利用事件 “resize” 回寫畫版寬高。

  • window.innerWidth ,視窗寬
  • window.innerHeight,視窗高
  • 利用 resize 事件回寫畫版寬高

DOM 與 Canvas

DOM 繪製圖形

直接操作網頁元素來繪製圖形,像是 CSS 與 SVG,
CSS :

1
2
3
4
5
6
h1 {
background-color: red;
}
ul {
background-color: blue;
}

SVG :

1
2
3
4
<!-- 直接在 html 檔案中插入即可 -->
<svg height="1000px" width="1000px">
<rect id="myRect" height="100px" width="100px" fill="blue"/>
</svg>

它們直接對 DOM 做圖形渲染的效果,

  • 佔用大量的記憶體空間
  • 能繪製的圖形效果有限
  • 相對的,修改效果時較為簡便

以下是一些實際運用例子 :

Canvas 繪製圖形

在 DOM 中只有一個元素,也就是 <canvas>

  • 不佔用記憶體空間
  • 能繪製許多圖形效果
  • 相對的,修改時需要了解複雜的圖形方法

以下是一些實際運用例子 :

參考來源

  1. canvasJS
  2. MDN - Canvas 基本用途
  3. MaxLee - JS地下城[7F] - Canvas畫布
  4. lb01910483 - Day 14 - Canvas 圖片保存 Part 1
  5. drawImage()方法繪製圖片不顯示的問題
  6. Elisa Chang -【JavaScript】RGB 與 HEX 色碼轉換器實作
  7. HTML5 input type Color read single RGB values
  8. Kirupa - DOM vs. Canvas
  9. SVG vs canvas: how to choose