[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

所以利用此屬性,來抓取 keyCode 值:
```javascript!
window.addEventListener("keydown", (keyboardInput) => {
console.log(keyboardInput.keyCode);
});
```

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 標籤且屬性有符合的

如果沒有對應元素,就會跳出 `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`

### querySelectorAll
按完後要讓黃框消失,監聽動畫結束,讓每個按鍵都會被監聽,使用 `querySelectorAll`
```javascript!
const keys = document.querySelectorAll(".key");
console.log(keys);
```
`querySelectorAll` 拿到是一個 NodeList 節點,是==array-like==,展開`[[Prototype]]`查看,可以知道 NodeList 無法使用所有的陣列方法。

按下按鍵會跳出6個TransitionEvent,作者找最後一個`propertyName:"transform"`

```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` 可以查到有關日期和時間共用的實例方法:

- 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 屬性,如下圖劃線處

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

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

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

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

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"`的位置

先選取 `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")];
```

最後把`<a>`裡面文字取出,並用字串方法 `includes()` 篩選文字中有包含 `de` 的
```javascript!
const linkTexts = links
.map((link) => link.textContent)
.filter((streetName) => streetName.includes("de"));
```

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);
```
比較排序會從第一個字母開始,如相同就比第二個

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;
}
```
設定完會長這樣~

再來設定 上下的文字預設在框外,加上 `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% 位置,所以看不到他們

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

再來進入到設置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雙數個就會當機,效果被抵銷掉)

```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,長的如下圖

```javascript!
fetch(endpoint)
.then((blob) => console.log(blob);
```
用 `.then` 來看看回傳的blob是什麼
可以看到是一個 `Response` ,但還無法使用
所以使用 `[[Prototype]]` 裡面的 `json()` 方法轉換資料,拿到資料後再把資料印出

```javascript!
fetch(endpoint)
.then((blob) => blob.json()) // 用json方法轉換資料
.then((data) => console.log(data)); // 取得資料
```
這樣才能拿到資料

展開看看

[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得到的資料是回傳一個陣列

在 String 的方法:search、match、replace、split 等,也有支援正規表達式。
[十五分鐘認識正規表達式,解決所有文字難題](https://5xcampus.com/posts/15min-regular-expression.html?srsltid=AfmBOoro37cghD6rK8R59rIFPZd8b28I8MHFGFBCBcutjzAB6y10tJa8)
---
再來要監聽 <input>,當有改變 (change) 或是輸入內容 (keyup) 會觸發 displayMatches 函式,讓篩選後的資料可以呈現在頁面。

```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>` 內,才能更新頁面內容

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


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>
`
```
逗號出現了

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

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

## 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事件的資料

再判斷滑鼠點擊並移動時才繪圖,如滑鼠放開只是移動不能畫
```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));
```

調整畫筆粗細&製作顏色
```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++; // 色相增加
}
```
有彩色了!

再來要讓畫筆顏色都在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");
```

- warning!
```javascript!
console.warn("警告!");
```

- error!
```javascript!
console.error("錯誤!");
```

- Info
```javascript!
console.info("資訊呈現");
```
- testing
Assertion 斷言只會出現在出現問題時
```javascript!
console.assert(1 === 2, "its' wrong!");
```

- clearing 清空
```javascript!
console.clear();
```

- Viewing DOM Elements
```javascript!
console.log(p);
console.dir(p);
```

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

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

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

- table
```javascript!
console.table(dogs);
```

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