---
title: 刮刮樂實作練習
---
## 實作開始
### Step 1: 刮刮樂原理 及 分析 HTML 架構
刮刮樂結構分解



---
### Step 2: 建立 index.html 及初步的 html 內容
1. 使用 `html:5 + TAB 鍵` 在編譯器上建立初步的 HTML
2. 引入 css `reset.min.css`
```htmlembedded=
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
```
3. 建立 `<style></style>` 標籤
```css=
<style>
* {
box-sizing: border-box;
}
</style>
```
4. 建立主架構的 html 內容
可以參考 [Emmet](https://docs.emmet.io/cheat-sheet/) 語法簡化 HTML 撰寫
```
a. 建立最一層 div(class="container")
b. 建立第二層 div(class="content")
c. .content 內建立 <img> 元素並放入圖片網址(設定寬高400px)
```
> 圖片網址:
>
`https://images.pexels.com/photos/2660348/pexels-photo-2660348.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1`
> Emment 語法:
>
`.container>.content>img[src="https://images.pexels.com/photos/2660348/pexels-photo-2660348.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" width="400" height="400"]`
>實際執行結果如下
```htmlembedded=
<div class="container">
<div class="content">
<img src="https://images.pexels.com/photos/2660348/pexels-photo-2660348.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" width="400" height="400">
</div>
</div>
```
---
### Step 3: 水平且垂直置中內容
a. 在 .content 的 div 中 水平置中圖片
b. 水平且垂直置中 class="container" 的 div
#### a. 在 .content 的 div 中 水平置中圖片
幫 .content 加上背景更好 debug
```sass=
/* 方法一 */
.content {
text-align: center;
}
/* 方法二 */
/* 多個元素的話建議採用此方法 */
.content {
display: flex; /* grid 也可以 */
justify-content: center;
align-items: center; /* 若需要垂直置中的話 */
flex-wrap: wrap; /* 多個元素需要斷行 */
}
```
#### b. 水平且垂直置中 class="container" 的 div
```sass=
/* 方法一 針對父容器設置高度 */
.content {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
/* 配上原本的 flex,需要撐高元素滿版 */
height: 100vh; /* 有延伸問題,無法讓網頁隨著內容長寬出現滾軸 */
}
/* 方法二 絕對位置 */
.container {
position: relative;
height: 100vh;
}
.content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 內容物的 width 和 height 的 50% */
}
```
---
### Step 4: 如何解決 height: 100vh 無法出現滾軸問題
#### a. 在原本架構外層建立 class="full-page-container" 的 DIV
用 `position: fixed;` 撐滿頁面
```htmlembedded=
<div class="full-page-container"></div>
```
```css=
.full-page-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
padding: .5rem;
background-color: brown;
}
```
#### b. 重新調整 .container 的樣式
``` sass=
.container {
position: relative;
width: 100%;
height: 100%;
/* 需要依內容物最小寬高撐開,影響手機版顯示 */
min-width: 400px;
min-height: 400px;
}
```
---
### Step 5: 建立 Canvas 元素 放入 content div 內
```htmlembedded=
<canvas id="draw-container" width="400" height="400"></canvas>
```
```css=
canvas {
position: absolute;
top: 0;
left: 0;
}
```
---
### Step 6: 開始實作 Cavnas 相關的函式
- Canvas 的 globalCompositeOperation 屬性:[W3C](https://www.w3schools.com/jsref/canvas_globalcompositeoperation.asp)
#### a. 建立 window.onload 函式
等圖片載入後才進行刮刮樂遊戲
```javascript=
window.onload = function () {
const canvas = document.getElementById('draw-container')
const ctx = canvas.getContext('2d')
/* css 上的 background-color 無法受到 canvas 影響 */
ctx.fillStyle = 'rgb(208,208,208)' // 灰色
ctx.fillRect(0, 0, canvas.width, canvas.height)
/* 設定後續的圖像疊合模式 */
ctx.globalCompositeOperation = 'destination-out'
}
/* 如果使用 css 上的 background-color 無法用上述方式移除 */
```
#### b. 建立 Cavnas 上的滑鼠事件 (onmousedown/onmousemove/onmouseup)
```javascript=
window.onload = function () {
...
let isDrawing = false
const handleMouseDown = (e) => {
// 開始畫圖
isDrawing = true
}
const handleMouseMove = (e) => {
// 刮除灰色區塊中
if (!isDrawing) return
}
const handleMouseUp = (e) => {
// 停止刮除
if (!isDrawing) return
isDrawing = false
}
canvas.onmousedown = handleMouseDown
canvas.onmousemove = handleMouseMove
canvas.onmouseup = handleMouseUp
}
```
#### c. 取得滑鼠畫圖時的 X/Y 座標位置 (起始點)
- getBoundingClientRect 函式:[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect)

```javascript=
/* e = mouse event */
const getRectPosition = (e) => {
const rect = canvas.getBoundingClientRect()
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
}
}
```
#### d. 移除滑鼠點擊位置的區塊
- Canvas arc 函式:[MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc)
- Canvas rect 函式:[MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rect)
```javascript=
const eraseImage = ({ x, y }) => {
const circleRadius = 20
ctx.beginPath()
ctx.arc(x, y, circleRadius, 0, Math.PI * 2) // 圓形
// ctx.rect(x, y, 40, 40) // 方形
ctx.fill()
}
```
#### e. 針對滑鼠事件判斷需要移除的區塊
```javascript=
const handleMouseDown = (e) => {
...
const { x, y } = getRectPosition(e)
eraseImage({ x, y })
}
const handleMouseMove = (e) => {
...
const { x, y } = getRectPosition(e)
eraseImage({ x, y })
}
```
## 其他資源
- ES6 入門:https://es6.ruanyifeng.com/#README
- 箭頭函式:[MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions)