---
# System prepended metadata

title: F_JS30 01-10
tags: [DOM]

---

[TOC]

[JS30 影片連結](https://youtube.com/playlist?list=PLu8EoSxDXHP6CGK4YVJhL_VWetA865GOH&si=01oY9gemI7BCC9Ex)
[JS30 github教材下載](https://github.com/wesbos/JavaScript30)
## 01 Make a JavaScript Drum Kit
做什麼事：
1. 當按下每個鍵時，就會找到對應的元素並播放音檔
2. 按下需要有transition動畫

### keycode
按下鍵盤的鍵，會有對應的數字
[網站](https://www.toptal.com/developers/keycode)
### html 上的 data-* attribute 可以自己設定？
資料屬性可以額外儲存在 html 的標籤裡，不會影響樣式，以 `data-` 為前綴，並透過 JS 方便存取。

範例：
html有一個 button ， data attribute 自定義為 `data-user-id="12345"` 和 `data-user-role="admin"`。須注意 data- 後面不能大寫字母，一律小寫
```html!
<button id="loginButton" data-user-id="12345" data-user-role="admin">
  Login
</button>
```
用JS存取：
```javascript!
// 1. 用dataset
// 須將自定義名稱改成駝峰命名
const button = document.getElementById("loginButton");

console.log(button.dataset.userId); // "12345"
console.log(button.dataset.userRole); // "admin"

// 2. 用getAttribute讀取
console.log(button.getAttribute("data-user-id")); // "12345"

// 3. 如要設置，用setAttribute
button.setAttribute("data-user-role", "editor");
console.log(button.getAttribute("data-user-role")); // "editor"
```
[MDN - Using data attributes](https://developer.mozilla.org/en-US/docs/Learn_web_development/Howto/Solve_HTML_problems/Use_data_attributes)
[什麼是 HTML 5 中的資料屬性（data-* attribute）](https://pjchender.dev/html/html-data-attribute/)
### addEventListener

#### keydown 事件

```javascript!
 window.addEventListener("keydown", (keyboardInput) => {
        console.log(keyboardInput);
      });
```
按下 g 鍵，會印出 keyboardEvent 物件，裡面有此事件的資料，找到`keyCode:71`，就是 g 鍵的對應數字，屬性`key:"G"`代表按大寫 G
![image](https://hackmd.io/_uploads/Sye2hFJ8yl.png)


所以利用此屬性，來抓取 keyCode 值：
```javascript!
 window.addEventListener("keydown", (keyboardInput) => {
        console.log(keyboardInput.keyCode);
      });
```
![image](https://hackmd.io/_uploads/BJ6TnKy8kg.png)




JS有幾種事件種類，參考資料如下連結：
[MDN - Event reference](https://developer.mozilla.org/en-US/docs/Web/Events)
[重新認識 JavaScript: Day 16 那些你知道與不知道的事件們](https://ithelp.ithome.com.tw/articles/10192175)

### querySelector 

```javascript!
window.addEventListener("keydown", (keyboardInput) => {
  // 取得 `<audio>`且屬性為[data-key="按下的鍵的keyCode"]  
  const audio = document.querySelector(
    `audio[data-key="${keyboardInput.keyCode}"]`
  );
  console.log(audio);
});

```
印出結果：
取得 audio 標籤且屬性有符合的
![image](https://hackmd.io/_uploads/BkJkatJU1l.png)

如果沒有對應元素，就會跳出 `null`

### currentTime
為什麼連點很多下按鍵才播放一次，因為音檔還在播放中，此時又再按，他就不會再播放，所以在`audio.play()`之前，先加上`audio.currentTime = 0`，讓音檔從頭開始播放。
```javascript!
window.addEventListener("keydown", (keyboardInput) => {
    const audio = document.querySelector(
      `audio[data-key="${keyboardInput.keyCode}"]`
    );
    if (!audio) return; // 如果為null就結束
    audio.currentTime = 0; // 讓音檔從頭開始
    audio.play();
    });
```

### classList
為了按下後會有動畫，取得 class 為"key"的標籤
```javascript!
const key = document.querySelector(
  `.key[data-key="${keyboardInput.keyCode}"]`
);
```
在這個標籤上，新增 class 屬性 `"playing"`
`.playing`是原先就寫好的css
```javascript!
key.classList.add("playing");
```
就會在此標籤的 class 上新增 `playing` 
![image](https://hackmd.io/_uploads/BJfeptkUJx.png)


### querySelectorAll
按完後要讓黃框消失，監聽動畫結束，讓每個按鍵都會被監聽，使用 `querySelectorAll`


```javascript!
const keys = document.querySelectorAll(".key");
  console.log(keys);
```
`querySelectorAll` 拿到是一個 NodeList 節點，是==array-like==，展開`[[Prototype]]`查看，可以知道 NodeList 無法使用所有的陣列方法。
![image](https://hackmd.io/_uploads/HyBb6tyIke.png)


按下按鍵會跳出6個TransitionEvent，作者找最後一個`propertyName:"transform"`
![image](https://hackmd.io/_uploads/ryxzatJIJl.png)

```javascript!
function removeTransition(e) {
    if (e.propertyName !== "transform") return;
    // this就是forEach的key
    // 把playing移除
    this.classList.remove("playing"); 
  }
  // 監聽動畫結束後，執行移除transition
    const keys = document.querySelectorAll(".key");
    keys.forEach((key) => {
    key.addEventListener("transitionend", removeTransition);
      });
```

完整程式碼：
```javascript!
function playSound(keyboardInput) {
  const audio = document.querySelector(
    `audio[data-key="${keyboardInput.keyCode}"]`
  );
  const key = document.querySelector(
    `.key[data-key="${keyboardInput.keyCode}"]`
  );
  if (!audio) return;
  audio.currentTime = 0;
  audio.play();

  key.classList.add("playing");
}

function removeTransition(e) {
  if (e.propertyName !== "transform") return;
  this.classList.remove("playing"); // this就是forEach的key
}

// 監聽動畫結束後，執行移除transtion
const keys = document.querySelectorAll(".key");
keys.forEach((key) => {
  key.addEventListener("transitionend", removeTransition);
});

window.addEventListener("keydown", playSound);
```

## 02 We build a CSS + JS Clock

### transform-origin
透過 `transform-origin` 設定中心點位置，一般起始點為物件的中心。
### transition-timing-function
漸變函式(transition timing function)漸變函式可用來定義轉場發生的時間曲線。 其規範方式是以四個參數的貝茲曲線代表。
### Date
Date 作為建構函式呼叫後，會回傳一個新的 Date 物件，會回傳當前日期和時間的**字串**。 [MDN - Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)

在 `Date.prototype` 可以查到有關日期和時間共用的實例方法：
![image](https://hackmd.io/_uploads/S1XRiCJIyg.png)


- getSeconds(): 根據當地時間傳回指定日期的對應秒數
- getMinutes(): 根據當地時間傳回指定日期的對應分鐘
- getHours(): 根據當地時間傳回指定日期的對應小時
```javascript!
const now = new Date();
const hour = now.getHours();
const minute = now.getMinutes();
const second = now.getSeconds();

console.log(hour); // 16
console.log(minute); // 54
console.log(second); // 30
```


所以作者要做出時鐘秒針的動作，設置以下：
```javascript!
 function setTime() {
    const now = new Date(); // 現在時間
    const seconds = now.getSeconds(); // now是一個實例，就可以調用getSeconds方法來取得現在秒數
    }
setInterval(setTime, 1000);
```
設置秒數佔的角度的百分比
因為原本指向9，所以要把角度指回12，所以再加上90度
```javascript!
const secondsDegrees = (seconds / 60) * 360 + 90;
```

最後選取元素，設置此元素的 `style.transform`
```javascript!
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
```
分針、時針也都跟秒針一樣的寫法

完整程式碼：
```javascript!
const secondHand = document.querySelector(".second-hand");
const minuteHand = document.querySelector(".min-hand");
const hourHand = document.querySelector(".hour-hand");

function setTime() {
  const now = new Date(); // 現在時間
  const seconds = now.getSeconds(); // now是一個實例，就可以調用getSeconds方法
  const secondsDegrees = (seconds / 60) * 360 + 90;
  secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
  console.log(seconds);

  const minutes = now.getMinutes();
  const minutesDegrees = (minutes / 60) * 360 + 90;
  minuteHand.style.transform = `rotate(${minutesDegrees}deg)`;

  const hours = now.getHours();
  const hourDegrees = (hours / 12) * 360 + 90;
  hourHand.style.transform = `rotate(${hourDegrees}deg)`;
}
setInterval(setTime, 1000);
```
但會發現秒針到60秒時候會倒轉歸零，所以計算的時間要累加上去，而不是60秒又歸零重跑。(參考大家的筆記，還不是很懂，6 30是什麼？)
```javascript!
const secondHand = document.querySelector(".second-hand");
const minuteHand = document.querySelector(".min-hand");
const hourHand = document.querySelector(".hour-hand");

function setTime() {
  const now = new Date(); // 現在時間
  const seconds = now.getSeconds(); 
  const secondsDegrees = (seconds / 60) * 360 + 90;
  secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
  console.log(seconds);

  const minutes = now.getMinutes();
  const minutesDegrees = (minutes / 60) * 360 + (seconds / 60) * 6 + 90;
  minuteHand.style.transform = `rotate(${minutesDegrees}deg)`;

  const hours = now.getHours();
  const hourDegrees = (hours / 12) * 360 + (minutes / 60) * 30 + 90;
  hourHand.style.transform = `rotate(${hourDegrees}deg)`;
}
setInterval(setTime, 1000);
```

## 03 Woah! CSS Variables?!
透過 JS 來調整網頁上的元素參數

### 設定css 變數
作者先在 style 裡面設定 `:root` 變數，並套用到 `.hl` 和 `img` 上
```css!
 :root {
        --base: #ffc600;
        --spacing: 30px;
        --blur: 5px;
      }
      .hl {
        color: var(--base);
      }
      img {
        background: var(--base);
        padding: var(--spacing);
        filter: blur(var(--blur));
      }
```

### dataset
是一個物件，會取得元素上有 `data-*`屬性的所有內容
範例：

```html!
<div class="controls">
  <input type="text" value="Test" data-sizing = "px" data-type = "spacing">
</div>
```
input 自定義了 `data-sizing = "px"` ` data-type = "spacing"`
```javascript!
const data = document.querySelector(".controls input");
const input = data.dataset;
console.log(input); 

// { "sizing": "px", "type": "spacing"}
```
選取到 input 元素後，使用 dataset 把此 input 元素上的 data-* 屬性回傳成一個物件。

如需要某 data-* 屬性上的資料：
```javascript!
const data = document.querySelector(".controls input");
const sizing = data.dataset.sizing; // 用物件方法取值
console.log(sizing); // "px"
```

[MDN - HTMLElement: dataset property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset)

作者為了要取得 suffix 後綴，也就是 px ，所以在 html 的 input 標籤上自定義了 `data-sizing="px"`
```javascript!
const inputs = document.querySelectorAll(".controls input");

function handleUpdate() {
  const suffix = this.dataset.sizing || ""; 
}

inputs.forEach((input) => input.addEventListener("change", handleUpdate));

inputs.forEach((input) => input.addEventListener("mousemove", handleUpdate));
```
 `this.dataset.sizing || ""` 為什麼是 或為空？
因為顏色他不需要取得 px，所以設定為空

那 `this.dataset` 的 this 是指向誰？
this 指向哪個對象取決於 handleUpdate 函式是如何被調用的。
當 change 事件被觸發時，handleUpdate 函式會被執行，且 this 會指向觸發該事件的 `<input> 元素`。
### document.documentElement & style.setProperty
最後設定 `<html>` 的 style 屬性
```javascript!
  document.documentElement.style.setProperty(
    `--${this.name}`,
    this.value + suffix
  );
```
- `document.documentElement` 是專門用來操作 `根元素 <html>`，如果要操作其他元素就用`document.querySelector()`或 `document.querySelectorAll()`之類的。


- `style.setProperty` 用來設定元素的 inline style 的方法
```javascript!
element.style.setProperty(propertyName, value);
```
也就是當滑鼠移動 input 就會把當下的值加在`<html>` 的 style 屬性，如下圖劃線處
![image](https://hackmd.io/_uploads/rJyFvXZUyx.png)


## 04 Array Cardio Practice - Day 1
練習陣列的方法~
### Array.prototype.filter()
1. Filter the list of inventors for those who were born in the 1500's
```javascript!
const fiften  = inventors.filter(
  (inventor) => inventor.year >= 1500 && inventor.year < 1600
);
console.table(fiften); 
```
![image](https://hackmd.io/_uploads/SJxBhEb8Jg.png)


### Array.prototype.map()
2. Give us an array of the inventors first and last names

```javascript!
const fullName = inventors.map(
  (inventor) => `${inventor.first} ${inventor.last}`
);
console.log(fullName);
```
![image](https://hackmd.io/_uploads/r1WxyBZ8yl.png)

### Array.prototype.sort()
3. Sort the inventors by birthdate, oldest to youngest
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)

```javascript!
const ordered = inventors.sort((a, b) => (a.year > b.year ? 1 : -1));
console.log(ordered);
```
![image](https://hackmd.io/_uploads/S13ekrbI1l.png)

### Array.prototype.reduce()
4. How many years did all the inventors live all together?

```javascript!
const totalYears = inventors.reduce((accumulator, currentValue) => {
  return accumulator + (currentValue.passed - currentValue.year);
}, 0);
console.log(totalYears); // 861
```
totalYears 跟影片算出來不一樣，看了大家筆記，原來是 inventors 數量不同

5. Sort the inventors by years lived

```javascript!
  const oldest = inventors.sort((a, b) => {
  const lastLive = a.passed - a.year;
  const nextLive = b.passed - b.year;
  return lastLive - nextLive;
});

console.table(oldest);
```
![image](https://hackmd.io/_uploads/HkDW1SWUyg.png)

6. create a list of Boulevards in Paris that contain 'de' anywhere in the name.
[操作網站](https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris)
這題需要在此網站用devTools操作，所有項目都放在 `class="mw-category"`的位置
![image](https://hackmd.io/_uploads/S1riaam8Je.png)


先選取 `class="mw-category"`，再選取category裡面所有的 `<a>`
```javascript!
const category = document.querySelector(".mw-category");
const links = category.querySelectorAll("a");
```
因為 `querySelectorAll`結果為 arry-like ，所以透過 `Array.from()` 或是 `展開運算子` 將arry-like轉成陣列。
```javascript!
// 使用Array.from()
const links = Array.from(category.querySelectorAll("a"));

// 使用展開運算子
const links = [...category.querySelectorAll("a")];
```
![image](https://hackmd.io/_uploads/BJN26TmU1e.png)


最後把`<a>`裡面文字取出，並用字串方法 `includes()` 篩選文字中有包含 `de` 的
```javascript!
const linkTexts = links
  .map((link) => link.textContent)
  .filter((streetName) => streetName.includes("de"));
```
![image](https://hackmd.io/_uploads/B1qa6aQU1l.png)



7. sort Exercise
Sort the people alphabetically by last name （用people陣列把 last name 排序）
```javascript!
const alpha = people.sort((lastOne, nextOne) => {
  const [aLast, aFirst] = lastOne.split(", ");
  const [bLast, bFirst] = nextOne.split(", ");

  return aLast > bLast ? 1 : -1;
});
console.log(alpha);
```
比較排序會從第一個字母開始，如相同就比第二個
![image](https://hackmd.io/_uploads/BJO1Rp781x.png)



8. Reduce Exercise
Sum up the instances of each of these
有一個data陣列，要計算每個項目的數量
```javascript!
const data = [
        "car",
        "car",
        "truck",
        "truck",
        "bike",
        "walk",
        "car",
        "van",
        "bike",
        "walk",
        "car",
        "van",
        "car",
        "truck",
      ];
```

```javascript
const totalItemList = data.reduce((obj, item) => {
  if (!obj[item]) { // 如果沒有此item，就新增item，且初始值設置為0
    obj[item] = 0;
  }
  obj[item]++;
  return obj;
}, {});

console.log(totalItemList); 
// {"car": 5,"truck": 3,"bike": 2,"walk": 2,"van": 2}
```
好實用的方法！！

## 05 Flexbox + JavaScript Image Gallery
先將排版排好，使用flex
```css!
  .panels {
    min-height: 100vh;
    overflow: hidden;
    display: flex;  /* 手動新增 */
  }
.panel {
    background: #6b0f9c;
    box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1);
    color: white;
    text-align: center;
    align-items: center;
    /* Safari transitionend event.propertyName === flex */
    /* Chrome + FF transitionend event.propertyName === flex-grow */
    transition: font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
      flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), background 0.2s;
    font-size: 20px;
    background-size: cover;
    background-position: center;
    
    /* 以下手動新增 */
    display: flex; 
    flex-direction: column;
    flex: 1;
    justify-content: center;
  }


.panel > * {
    margin: 0;
    width: 100%;
    transition: transform 0.5s;
    
    /* 以下手動新增 */
    display: flex;
    justify-content: center;
    align-items: center;
    flex: 1 0 auto;
  }
```
設定完會長這樣～
![image](https://hackmd.io/_uploads/HJMRMJ481x.png)


再來設定 上下的文字預設在框外，加上 `open-active` 樣式再進來框內
```css!
  .panel > *:first-child {
    transform: translateY(-100%);
  }
  .panel > *:last-child {
    transform: translateY(100%);
  }
  .panel.open-active > *:first-child {
    transform: translateY(0);
  }
  .panel.open-active > *:last-child {
    transform: translateY(0);
  }
```
上面的字在Y軸 -100% 位置，下面的字Y軸 100% 位置，所以看不到他們

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


因為要點擊時候讓那圖片框和字體變大，在 `.panel.open` 的css設置：
```css!
  .panel.open {
    flex: 5;
    font-size: 40px;
  }
```

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


再來進入到設置JS，設計點擊時候，自動新增或刪除class

完整程式碼：

```javascript!
  const panels = document.querySelectorAll(".panel");

  function toggleOpen() {
    this.classList.toggle("open");
  }

  function toggleActive(e) {
    console.log(e);
    if (e.propertyName.includes("flex")) {
      this.classList.toggle("open-active");
    }
  }
  
panels.forEach((panel) => panel.addEventListener("click", toggleOpen));
  
panels.forEach((panel) =>
    panel.addEventListener("transitionend", toggleActive)
  );
```
當click時，觸發toggleOpen這個函式，裡面做的事情toggle open 這個class

再來等動畫結束後，需要把原先放在外面的字進入到框內。

為什麼直接寫 `this.classList.toggle("open-active");`就不能動? 還要加上if判斷？

在 toggleActive 函式印出 `e.propertyName`，可以看到 `flex-grow` 和 `font-size`會有transition變化，在這裡只要監聽 `flex-grow`變化就好。
(其他筆記是說 觸發transition雙數個就會當機，效果被抵銷掉)
![image](https://hackmd.io/_uploads/rJ0CY_ILkl.png)


```javascript!
e.propertyName.includes("flex")
```
加上if判斷，是因為作者說到，在其他瀏覽器像是 safari 他就不叫做flex-grow ，而是flex的關係。

[classList property](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)

## 06 Ajax Type Ahead with fetch()

### fetch
```javascript!
const endpoint =
  "https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";

const result = fetch(endpoint);
console.log(result); // 會回傳Promise
```
fetch回傳是一個Promise，長的如下圖
![image](https://hackmd.io/_uploads/SyXpv_5Iye.png)
```javascript!
  fetch(endpoint)
    .then((blob) => console.log(blob);
```
用 `.then` 來看看回傳的blob是什麼

可以看到是一個 `Response` ，但還無法使用
所以使用 `[[Prototype]]` 裡面的 `json()` 方法轉換資料，拿到資料後再把資料印出
![image](https://hackmd.io/_uploads/SkDRw_qLke.png)

```javascript!
  fetch(endpoint)
    .then((blob) => blob.json()) // 用json方法轉換資料
    .then((data) => console.log(data)); // 取得資料
```
這樣才能拿到資料
![image](https://hackmd.io/_uploads/H1PkddqIyl.png)
展開看看
![image](https://hackmd.io/_uploads/r1MeOdcLJe.png)
[MDN - fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)


```javascript!
function findMatches(wordToMatch, cities) {
    return cities.filter((place) => {
      const regex = new RegExp(wordToMatch, "gi");
      return place.city.match(regex) || place.state.match(regex);
    });
  }
```
做一個 findMatches 函式，主要來篩選輸入的內容(wordToMatch)。
作者這邊使用 `正則表達式` 篩選每個物件中的 city 屬性值和 state 屬性值，如有符合則會回傳。

### Regex

`i` 是「忽略大小寫」（case-insensitive）的標誌，讓正則表達式忽略大小寫差異。

`g` 是「全域匹配」（global match）的標誌。它的作用是讓正則表達式在字串中查找所有匹配項目，而不是在找到第一個匹配後就停止。
```javascript!
// const regex = new RegExp("apple", "gi"); // 也可以這樣寫
const regex3 = /apple/gi;
const sentence = "apple banana orange APPle";
console.log(sentence.match(regex3)); // ["apple","APPle"]
```


```javascript!
const regex5 = /orange/;
const sentence5 = "apple banana orange APPle";
console.log(sentence5.match(regex5)); // ["orange",index:13....]如下截圖
```
match得到的資料是回傳一個陣列
![image](https://hackmd.io/_uploads/r1p-dOcIJl.png)

在 String 的方法：search、match、replace、split 等，也有支援正規表達式。

[十五分鐘認識正規表達式，解決所有文字難題](https://5xcampus.com/posts/15min-regular-expression.html?srsltid=AfmBOoro37cghD6rK8R59rIFPZd8b28I8MHFGFBCBcutjzAB6y10tJa8)

---
再來要監聽 <input>，當有改變 (change) 或是輸入內容 (keyup) 會觸發 displayMatches 函式，讓篩選後的資料可以呈現在頁面。
![image](https://hackmd.io/_uploads/Hk3Qdd9IJg.png)

```javascript!
// input標籤
  const searchInput = document.querySelector(".search");
// ul標籤
  const suggestions = document.querySelector(".suggestions");

  searchInput.addEventListener("change", displayMatches);
  searchInput.addEventListener("keyup", displayMatches);
```

```javascript!
 function displayMatches() {
    const matchArray = findMatches(this.value, cities);
     // value是input的屬性，讀取輸入的內容
     
const html = matchArray
      .map(
        (place) => `
      <li>
        <span class = name>${place.city},${place.state}</span>
        <span class = population>${place.population}</span>
      </li>
      `
      )
      .join("");
    suggestions.innerHTML = html;
```
matchArray.map 出來是 `["<li><span>XXX</span></li>", "<li><span>XXX</span></li>", "<li><span>XXX</span></li>"]`，加上join("")改成字串。

這邊主要是為了將篩選完的資料，用innerHTML 把 `<li>` 塞入到 `<ul>` 內，才能更新頁面內容
![image](https://hackmd.io/_uploads/B1JSdd5Lyl.png)
### innerHTML
- 用來動態更新頁面內容
- 會完全替換目標元素==內部的所有內容==

---
最後要把輸入的字突顯，要做 highlight 
作者有引入style.css，裡面已有 `.hl` 的 class 設定。

在跑map時，先把匹配到的字抓出來，再用 `replace` 把匹配到的字(regex)，替換成 `<span class="hl">${this.value}</span>` ，那些字就會加上顏色

[String.prototype.replace()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)

```javascript!
const regex = new RegExp(this.value, "gi");
    const cityName = place.city.replace(
      regex,
      `<span class="hl">${this.value}</span>`
    );
    const stateName = place.state.replace(
      regex,
      `<span class="hl">${this.value}</span>`
    );
```
![image](https://hackmd.io/_uploads/BJJUudcLkl.png)
![image](https://hackmd.io/_uploads/HyVL_d5Ukg.png)

displayMatches 調整後的程式碼：
```javascript!
function displayMatches() {
    const matchArray = findMatches(this.value, cities);

    const html = matchArray
      .map((place) => {
        // 將輸入的字有highlight
        const regex = new RegExp(this.value, "gi");
        const cityName = place.city.replace(
          regex,
          `<span class="hl">${this.value}</span>`
        );
        const stateName = place.state.replace(
          regex,
          `<span class="hl">${this.value}</span>`
        );
        return `
      <li>
        <span class = name>${cityName},${stateName}</span>
        <span class = population>${place.population}</span>
      </li>
      `;
      })
      .join("");
    suggestions.innerHTML = html;
  }
```
---
population 人口數需要加上數字逗號
作者說直接上網找
```javascript!
  // 數字加上逗號
  function numberWithCommas(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  }
```
`<li class = population>`內容要改成`${numberWithCommas(
              place.population
            )}`
```javascript!
 `
  <li>
    <span class = name>${cityName},${stateName}</span>
    <span class = population>${numberWithCommas(
      place.population
    )}</span>
  </li>
  `
```
逗號出現了
![image](https://hackmd.io/_uploads/H1dPOu5L1l.png)

## 07 .some(), .every(), .find() and [...SPREADS] — Array Cardio Day 2


```javascript!
  const people = [
    { name: "Wes", year: 1988 },
    { name: "Kait", year: 1986 },
    { name: "Irv", year: 1970 },
    { name: "Lux", year: 2015 },
  ];
```

### Array.prototype.some()
至少一個元素符合條件，就回傳true
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)

Q: is at least one person 19 or older?
```javascript!
  const isAudlt = people.some((person) => {
    const currentYear = new Date().getFullYear();
    if (currentYear - person.year >= 19) return true;
  });
  console.log(isAudlt); // true


  // 更簡化寫法
  const isAudlt = people.some(
    (person) => new Date().getFullYear() - person.year >= 19
  );
  console.log(isAudlt);
```

### Array.prototype.every()
全部元素符合條件，就回傳true 
[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/every)

Q: is everyone 19 or older?
```javascript!
  const allAudlt = people.every(
    (person) => new Date().getFullYear() - person.year >= 19
  );
  console.log(allAudlt); // false
```

### Array.prototype.find()
回傳第一個找到的元素值，非回傳陣列！
[MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/find)

Q: Find the comment with the ID of 823423
```javascript!
  const comments = [
    { text: "Love this!", id: 523423 },
    { text: "Super good", id: 823423 },
    { text: "You are the best", id: 2039842 },
    { text: "Ramen is my fav food ever", id: 123523 },
    { text: "Nice Nice Nice!", id: 542328 },
  ];
```
```javascript!
  const findValue = comments.find((item) => item.id === 823423);
  console.log(findValue);
```
![image](https://hackmd.io/_uploads/SyRV0vjIyx.png)

### Array.prototype.findIndex()
回傳第一個找到的元素值的索引值，非回傳陣列！
[MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)

Q: delete the comment with the ID of 823423
```javascript!
  const index = comments.findIndex((item) => item.id === 823423);

  // 刪除元素方法1
  comments.splice(index, 1);
  console.log(comments);

  // 刪除元素方法2
  const newComments = [
    ...comments.slice(0, index),
    ...comments.slice(index + 1),
  ];

  console.table(newComments);
```
![image](https://hackmd.io/_uploads/BJqrCviU1e.png)

## 08 Let's build something fun with HTML5 Canvas


[MDN - canvas](https://developer.mozilla.org/zh-TW/docs/Web/API/Canvas_API/Tutorial/Basic_usage)

先渲染環境(rendering context)
2d 繪圖就輸入 `2d`
```javascript!
  const canvas = document.querySelector("#draw");
  const ctx = canvas.getContext("2d");
```

進行任何繪圖前，調整畫布大小
```javascript!
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
```

再對一些樣式基本設定
```javascript!
  // 樣式設定
  ctx.strokeStyle = "#BADASS";
  ctx.lineJoin = "round";
  ctx.lineCap = "round";
```

找出滑鼠座標
```javascript!
  let isDrawing = false;

  let lastX = 0; // 設定X軸
  let lastY = 0; // 設定Y軸

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

  canvas.addEventListener("mousemove", draw);
```
只要在畫布移動，就會抓取到mousemove事件的資料
![image](https://hackmd.io/_uploads/B15yL238yl.png)
再判斷滑鼠點擊並移動時才繪圖，如滑鼠放開只是移動不能畫
```javascript!
  function draw(e) {
    // 意思是isDrawing為true，表示滑鼠未按下
    if (!isDrawing) return; 
    console.log(e);
  }

  canvas.addEventListener("mousemove", draw);
  canvas.addEventListener("mousedown", () => (isDrawing = true));
  canvas.addEventListener("mouseup", () => (isDrawing = false));
  canvas.addEventListener("mouseout", () => (isDrawing = false));

```

會有兩個問題，
一個是都會從座標 (0,0) 開始畫
一個是畫的時候會從剛才結束的位置開始，變成線都會連續

調整程式碼：
```javascript!
function draw(e) {
    if (!isDrawing) return; 

    ctx.beginPath();
    // 開始
    ctx.moveTo(lastX, lastY);
    // go to
    ctx.lineTo(e.offsetX, e.offsetY);

    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
  }

  canvas.addEventListener("mousemove", draw);
  canvas.addEventListener("mousedown", (e) => {
    isDrawing = true;
    [lastX, lastY] = [e.offsetX, e.offsetY]; // 滑鼠從當前位置
  });
  canvas.addEventListener("mouseup", () => (isDrawing = false));
  canvas.addEventListener("mouseout", () => (isDrawing = false));
```
![image](https://hackmd.io/_uploads/BykW8h2Uke.png)

調整畫筆粗細＆製作顏色
```javascript!
  ctx.lineWidth = 15; // 調整畫筆的粗細

  let hue = 0; // 色相預設為0

  function draw(e) {
    if (!isDrawing) return; 
    console.log(e);
    ctx.strokeStyle = `hsl(${hue},100%,50%)`; // 加hsl
    ctx.beginPath();
    // 開始
    ctx.moveTo(lastX, lastY);
    // go to
    ctx.lineTo(e.offsetX, e.offsetY);

    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];

    hue++; // 色相增加
  }
```
有彩色了！
![image](https://hackmd.io/_uploads/rkXz8hhLJe.png)
再來要讓畫筆顏色都在360內跑，和畫筆顏色在一定寬度內跑
```javascript!
let direction = true; // 用來判斷畫筆要變寬還是變細

// 在 draw 函式內加上
if (hue >= 360) {
  hue = 0; // 重置為0，讓顏色都在360以內跑
}

// 畫筆粗細動態調整
if (ctx.lineWidth >= 100 || ctx.lineWidth <= 1) {
  direction = !direction;
}
if (direction) {
  ctx.lineWidth++;
} else {
  ctx.lineWidth--;
}
```
如想要有photoshop的混合模式
```javascript!
ctx.globalCompositeOperation = "multiply";
```
其他的模式：[globalCompositeOperation property](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)

## 09 Must Know Chrome Dev Tools Tricks

- Regular
```javascript!
console.log("hello");
```

- Interpolated 插入
在第一個值加上 `%s` ，第二個值插入到第一個值
```javascript!
console.log("have a %s day!", "nice");

// 印出
have a nice day!
```
- Styled 加上樣式
在第一個值加上 `%c` ，第二個值設定的樣式會套用到第一個值
```javascript!
console.log("%cwarning", "font-size:24px;background:red");
```
![image](https://hackmd.io/_uploads/S1388p3U1g.png)

- warning!
```javascript!
console.warn("警告！");
```
![image](https://hackmd.io/_uploads/ryYvL6nI1x.png)
- error!
```javascript!
 console.error("錯誤！");
```
![image](https://hackmd.io/_uploads/H1KuLp3I1l.png)

- Info
```javascript!
console.info("資訊呈現");
```
- testing
Assertion 斷言只會出現在出現問題時
```javascript!
console.assert(1 === 2, "its' wrong!");
```
![image](https://hackmd.io/_uploads/ByYKUahL1l.png)

- clearing 清空
```javascript!
console.clear();
```
![image](https://hackmd.io/_uploads/Sk85LphI1x.png)

- Viewing DOM Elements
```javascript!
 console.log(p);
 console.dir(p);
```
![image](https://hackmd.io/_uploads/ryIoUT3Lkx.png)

- Grouping together 組群組
```javascript!
  dogs.forEach((dog) => {
    console.group(`${dog.name}`);
    console.log(`${dog.name}`);
    console.log(`${dog.name} is ${dog.age} years old`);
    console.groupEnd(`${dog.name}`);
  });
```
![image](https://hackmd.io/_uploads/SkB3IT38yg.png)
- counting
會自動計算數量
```javascript!
  console.count("dog");
  console.count("cat");
  console.count("bird");
  console.count("cat");
  console.count("bird");
  console.count("dog");
  console.count("dog");
  console.count("dog");
  console.count("cat");
  console.count("bird");
```
![image](https://hackmd.io/_uploads/SkP68ThUJg.png)
- timing 計時器
例如：可以計算拿到資料花了多少時間
```javascript!
  console.time("fetching data");
  fetch("https://api.github.com/users/wesbos")
    .then((res) => res.json())
    .then((data) => {
      console.timeEnd("fetching data");
      console.log(data);
    });
```
![image](https://hackmd.io/_uploads/BymCIp2Uye.png)

- table
```javascript!
console.table(dogs);
```
![image](https://hackmd.io/_uploads/SkekPp2IJl.png)

## 10 JS Checkbox Challenge!
要製作 checkbox 可以按`shift鍵`部份勾選，且反向選取也可以，應用像是在gmail勾選信件等設計。

先選取到所有的checkbox，並且監聽click事件
```javascript!
const checkboxes = document.querySelectorAll(".inbox input[type='checkbox']");

let lastChecked; // 用來紀錄現在勾選的是哪個input

function handleCheck(e) {
  lastChecked = this;
}
checkboxes.forEach((checkbox) =>
  checkbox.addEventListener("click", handleCheck)
);
```

再來設計按下shift鍵的邏輯
```javascript!
let inBetween = false;
    // 是否按住 Shift 並點擊勾選框
    if (e.shiftKey && this.checked) {
      checkboxes.forEach((checkbox) => {
        // 遍歷勾選框，當遇到 this 當前勾選框，就變為true，遇到lastChecked，切換為false
        if (checkbox === this || checkbox === lastChecked) {
          inBetween = !inBetween;
        }
        if (inBetween) {
          checkbox.checked = true;
        }
      });
    }
    lastChecked = this; // 紀錄當前點擊的勾選框
```
這裡有點難
1. 先判斷當使用者按住 Shift 並勾選當前的勾選框時，進行裡面的程式碼
2. 遍歷到 `當前勾選框 (this)` 或 `上次勾選框 (lastChecked)` 時，切換 inBetween 狀態。
因為inBetween 初始為 false，當遇到 this 時變為 true，表示進入範圍，遇到 lastChecked 時再切換為 false，表示範圍結束。
3. 最後把 `lastChecked = this` ; 更新並紀錄上一次點擊的勾選框

這樣設計就能任意方向勾選（例如：先點擊後面的勾選框，再點擊前面的）。

完整程式碼：
```javascript!
const checkboxes = document.querySelectorAll(".inbox input[type='checkbox']");
let lastChecked; 

function handleCheck(e) {
  let inBetween = false;
  // 是否按住 Shift 並點擊勾選框
  if (e.shiftKey && this.checked) {
    checkboxes.forEach((checkbox) => {
      // 遍歷勾選框，當遇到 this 當前勾選框，就變為true，遇到lastChecked，切換為false
      if (checkbox === this || checkbox === lastChecked) {
        inBetween = !inBetween;
      }
      if (inBetween) {
        checkbox.checked = true;
      }
    });
  }
  lastChecked = this; // 紀錄上一次被點擊的勾選框
}
checkboxes.forEach((checkbox) =>
  checkbox.addEventListener("click", handleCheck)
);
```
![image](https://hackmd.io/_uploads/rJnrFzT81l.png)

