---
# System prepended metadata

title: 刮刮樂實作練習

---

---
title: 刮刮樂實作練習

---

## 實作開始

### Step 1: 刮刮樂原理 及 分析 HTML 架構
刮刮樂結構分解
![image](https://hackmd.io/_uploads/HyW4PveHT.png)
![image](https://hackmd.io/_uploads/Sk07gdlS6.png)

![image](https://hackmd.io/_uploads/HJ8wT4fHp.png)



---

### 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)
![image](https://hackmd.io/_uploads/rka2UOsBp.png)


```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)
