[TOC]
## D1
查詢keycode
https://www.toptal.com/developers/keycode
```html
<div class="keys">
<!-- data-key後面的數字是keycode -->
<div data-key="65" class="key">
<kbd>A</kbd>
<span class="sound">clap</span>
</div>
</div>
```
:question:為什麼不把data-key放在`<kbd>`?
> 看起來如果放在`<kbd>`那在取得元素的時候就會取到`<kbd data-key="65">A</kbd>`,這樣在加入屬性`playing`時就只會加到A這個字上而不是整個框框,不知道我有沒有針對到問題討論哈哈哈
> [name=Jeremy]
### addEventListener(type, listener)
type: event type
listener: function
各類型的Events內有不同的小event
[MDN](https://developer.mozilla.org/en-US/docs/Web/Events)
```javascript
window.addEventListener("keydown", (keyBoardInput) => {
console.log(keyBoardInput);
});
// 這邊的keyBoardInput會是描述每次鍵盤輸入的細節的物件
```
例如說我按了向上鍵
就會在console中顯示這個向上鍵的物件

```javascript
window.addEventListener("keydown", (keyBoardInput) => {
// 選擇<audio>且其data-key屬性的值等於65
const audio = document.querySelector('audio[data-key="65"]')
});
window.addEventListener("keydown", (keyBoardInput) => {
// 選擇<audio>且其data-key屬性的值等於我用鍵盤按的那個鍵
const audio = document.querySelector(`audio[data-key="${keyBoardInput.keyCode}"]`);
console.log(audio);
});
```
### DOM提供的屬性
想要用`Object.keys(audio)`去查我擷取到的`<audio>`到底有哪些屬性失敗了QQ

:heavy_check_mark:因為他是Element不是Object?
> 我嘗試使用`console.dir`將他視為物件展開:

但前提是我在全域環境有取得到`<audio>`這個元素,而不是在function裡面:
```javascript
const audio = document.querySelector(`audio[data-key='65']`);
```
> 但我覺得他是DOM的特殊物件所以應該沒辦法當成一般物件為所欲為
> 正常印出來長這樣

> [name=Jeremy]
結果是在原型的原型上面:clap:

但可以在devTool去看

看到a播放完之後currentTime變成播放完的時間點0.483991
:question:`Element.play()`為什麼不會從頭開始播放??
> media.play()
Sets the paused attribute to false, loading the media resource and beginning playback **if necessary.**
**If the playback had ended, will restart it from the start.**
[規範](https://html.spec.whatwg.org/multipage/media.html#dom-media-play-dev)
已經播放完畢的定義:
1. 目前時間點在結束的點,且播放方向是向前播放,且沒有loop屬性
或是
2. 目前時間點在開始的點,且播放方向是倒帶播放
When the play() method on a media element is invoked
=> 1., 2., 3., 4. Run the internal play steps for the media element.
internal play steps
1. 跳過
2. 如果已經播放完畢,則從頭開始播放
3. 如果 .paused = true
4. 如果 .readyState = 3(HAVE_FUTURE_DATA)或4(HAVE_ENOUGH_DATA),通知這個element正在播放
> take pending play promises and queue a media element task given the media element to resolve pending play promises with the result. :question::question::question::question:
> The media element is already playing. However, it's possible that promise will be rejected before the queued task is run.
5. 跳過
### 監聽所有事件
原本想說先增加class => 播放媒體 => 刪掉class
```javascript
keyDiv.classList.add("playing");
audio.currentTime = 0;
audio.play();
keyDiv.classList.remove("playing");
```
結果沒有反應XD
所以要去監聽這個key的css變化(transition)是不是結束了
[events about CSS transition](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions#detecting_the_start_and_completion_of_a_transition)
```javascript
const keys = document.querySelectorAll(".key"); // return Array
keys.forEach((key) =>
key.addEventListener("transitionend", removeTransition)
);
function removeTransition(transitionEvent) {
console.log(transitionEvent);
}
// 回傳的是transitionEvent
```
transitionEvent

:question:為什麼要過濾events?
```javascript
function removeTransition(transitionEvent) {
// 影片中多加的這行,結果連點就會當掉???
if (transitionEvent.propertyName !== "transform"){
return;
}
}
```
要怎麼得到這個key element呢?
用this!!!!
看看忍者說什麼
> The event-handling system of the browser defines the context of the invocation to be the target element of the event, which causes the context to be the `<button>` element, not the button object.
也就是說,**瀏覽器**會把`addEventListener`這類型處理事件的東西(?)會把function context定義為這個事件的目標element,在這裡的例子就是指key
### 被棄用的keyCode屬性
我把程式碼移到一個新的.js後VSCode跟我說keyCode這個屬性已經被棄用了

[MDN](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode)也有標註這件事
被棄用的原因:KeyboardEvent.keyCode單指實體的按鍵,無視實際按出來的字到底是什麼,例如小寫a和大寫A的keyCode會是一樣的
那目前要使用什麼比較好?
- KeyboardEvent.code
直接回傳實際按出來的字,缺點是會出現符號,可能在寫程式碼上會有問題?
- KeyboardEvent.key
表示實體的按鍵,就算按的是符號,回傳的也是英文字串
> [參考](https://www.reddit.com/r/learnjavascript/comments/zciru6/comment/iyyt3ch/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)
## D2
### 自己調transition曲線

### 分檔發生語法問題
我分了一個tools.js,然後import到main.js
結果發生以下錯誤
> Uncaught SyntaxError: Cannot use import statement outside a module
解決方法:在html引入script的地方加上屬性`type="module"`

### 從59秒跳回0秒的時候會亂跳
因為角度會從354度到0度,所以transition偵測到的是transform中倒轉的變化
目前的解決方法是讓角度一直往上加,而不是從0~360度之間一直重複
```javascript
// 用+=
secondDegree += getSecondDegree(secondDifference);
minuteDegree += getMinuteDegree(minuteDifference, secondDifference);
hourDegree += getHourDegree(hourDifference, minuteDifference);
rotateHand(minuteHand, minuteDegree);
rotateHand(secondHand, secondDegree);
rotateHand(hourHand, hourDegree);
// functions
export function getSecondDegree(second) {
return (second / 60) * 360;
}
export function getMinuteDegree(minute, second) {
return (minute / 60) * 360 + (second * 6) / 60;
}
export function getHourDegree(hour, minute) {
return (hour / 12) * 360 + (minute * 30) / 60;
}
export function rotateHand(hand, degree) {
hand.style.transform = `rotate(${degree}deg)`;
}
```
考慮到如果單純讓setInterval跑一秒加一次角度可能會有誤差,所以我用當下時間點扣掉上次轉的時間點的時間差去計算角度
不過不曉得這樣到底有沒有解決誤差的問題XD
## D3
```html
<h2>Update CSS Variables with <span class='hl'>JS</span></h2>
```
:heavy_check_mark: 為什麼h2裡面的span的class是h1???什麼怪命名
原來是highlight的hl啊🙄
### NodeList
和Array不一樣,也沒有那麼多[].methods可以用
```javascript
const inputs = document.querySelectorAll(".controls input");
// 回傳的是NodeList
```

### 滑動滑桿(?)要跟著變化
這兩個要一起設定
addEventListener("change", );
addEventListener("mousemove", );
:heavy_check_mark:為什麼測試的時候,滑鼠在上面移動會印出東西,但實際運作的時候滑鼠移動不會更改變數數值,一定要點擊拉動滑桿才會?
> 因為單純mousemove不會改變input的value
> [name=Jeremy]
影片有人留言說可以改用input
試起來只需要input就可以達到change和mousemove的效果
> The **input** event is fired every time the value of the element changes. This is unlike the **change** event, which only fires when the value is committed. [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event)
### DOM.dataset是啥
會在elements上加一些前綴為data-的屬性,這些屬性會被存在dataset裡面
```html
<input
id="spacing"
type="range"
name="spacing"
min="10"
max="200"
value="10"
data-sizing="px"
data-range="20-100"
data-happy="yes"
/>
```

### documentElement表示root
```javascript
document.documentElement.style.setProperty(
`--${this.name}`, this.value + suffix
);
```

順帶一提,我很無聊加了恢復預設值的按鈕
暴力解決
如果有更好的寫法歡迎跟我說🥹
```javascript
const reset = document.querySelector(".reset input");
function resetValues() {
document.documentElement.style.setProperty("--spacing", "10px");
document.documentElement.style.setProperty("--base", "#ffc600");
document.documentElement.style.setProperty("--blur", "0px");
}
reset.addEventListener("click", resetValues);
```
## D4
### Q6 微爬蟲
題目是從網頁上的清單過濾出'de'的

不過教學是教兩段式選取
:question: 那我一次選完可以嗎?
```javascript
const category = document.querySelector(".mw-category")
const links = category.querySelectorAll("a")
// querySelectorAll的主詞不一定要是document
```
把NodeList轉成Array
```javascript
// 使用Array.from
const boulevardsName = Array.from(boulevards).map(
(elememt) => elememt.textContent
);
// 或是使用展開運算子
const boulevardsName = [...boulevards].map(
(elememt) => elememt.textContent
);
```
:question: 為什麼要取textContent? 取text呢? 其他的呢?



### Q7
字串比大小
```javascript
const peopleSortedByLast = people.sort((personA, personB) => {
const [personALast, personAFirst] = personA.split(", ");
const [personBLast, personBFirst] = personB.split(", ");
return personALast > personBLast ? -1 : 1;
// 字串不能直接相減,會變成NaN,要用比較的
});
```
## D5
flex忘光光中 余光中
> flex: <‘flex-grow’> <‘flex-shrink’> <‘flex-basis’>
> 三個的預設值為1, 1, 0
> 但flex本身的預設值0 1 auto
> [規範](https://www.w3.org/TR/css-flexbox-1/#flex-property)
設了兩個class,`.open`和`.open-active`
### 箭頭函式沒有this!!!
```javascript
// 原本寫這樣
// 結果因為this是window所以報錯
panels.forEach((panel) =>
panel.addEventListener("click", () => {
this.classList.add("open");
})
);
// 後來改成這樣就好了
panels.forEach((panel) =>
panel.addEventListener("click", function () {
this.classList.add("open");
})
);
```
:question:結果我還是不懂QwQ
> [先端上MDN的說明](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#the_value_of_this_within_the_handler)
```javascript
my_element.addEventListener("click", function (e) {
console.log(this.className); // logs the className of my_element
console.log(e.currentTarget === this); // logs `true`
});
my_element.addEventListener("click", (e) => {
console.log(this.className); // WARNING: `this` is not `my_element`
console.log(e.currentTarget === this); // logs `false`
});
```
### Element.classList.toggle
本來以為要加class就跟以前一樣用.add結果這次給我用.toggle
> 補充
> [classList的methods](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)
```javascript
// if visible is set remove it, otherwise add it
// 如果有這個class就刪掉,沒有就加入
div.classList.toggle("visible");
```
:question:open-active如果不篩選transition就不能動
> 好像是因為任何`transition`都會觸發事件,只要超過一種`transition`他就會當機,所以才只篩選他有`flex`出現的時候觸發。
> 我有做另外一個實驗,將`.panel.open`的`font-size`樣式拿掉,讓他只剩一個`flex`,就不需要篩選就會看到效果了。
> [name=Jeremy]
```javascript
// 這樣動不了
function toggleOpenActive(event) {
this.classList.toggle("open-active");
}
// 一定要篩選才可以動
function toggleOpenActive(event) {
// if (event.propertyName === "flex") { // 因為瀏覽器不一樣
if (event.propertyName.includes("flex")) {
this.classList.toggle("open-active");
}
}
```
## D6
### fetch
是瀏覽器API,回傳一個promise
> 會讓它發生 reject 的只有網路錯誤或其他會中斷 request 的情況。
> [MDN](https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API/Using_Fetch)
第一個參數是資料,第二個參數是用來設定request(還不是很確定要怎麼用)
```javascript
// MDN的範例
// 這裡要使用 fetch 透過網路取得 json 然後印出在 console,
// 只需要一個參數: 資料的 URI
// fetch 會回傳一個包含 response 的 promise
fetch("http://example.com/movies.json")
.then(function (response) {
return response.json();
})
.then(function (myJson) {
console.log(myJson);
});
```
回傳的response長什麼樣子
```javascript
const endpoint =
"https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";
fetch(endpoint).then(console.log);
```

利用.json把這個response解析成JSON

```javascript
fetch(endpoint)
.then((response) => response.json()); // 得到長度為1000的Array
const fetchedData = [];
// 白痴作法==
fetch(endpoint)
.then((response) => response.json())
.then((data) => {
data.forEach((cityData) => fetchedData.push(cityData));
});
// 請善用展開運算子
fetch(endpoint)
.then((response) => response.json())
.then((data) => fetchedData.push(...data));
```

### RegExp
該來的還是躲不過嗎QQ
```javascript
// 用RegExp object才能把變數放進去
const regex = new RegExp(wordToMatch, "gi"); // g是global, i是case-insensitive
return place.city.match(regex) || place.state.match(regex);
```
### 邊打字邊觸發
```javascript
// 影片使用組合技
searchInput.addEventListener("change", showValue);
searchInput.addEventListener("keyup", showValue);
// 用之前查到的input可以作到
searchInput.addEventListener("input", showValue);
```
### 直接把陣列作為html丟進去innerHTML發生的事
```javascript
function showMatches() {
const matchArray = findMatches(this.value, fetchedData);
const newElement = matchArray.map((place) => {
return `
<li>
<span class="name">${place.city}, ${place.state}</span>
<span class="name">${place.population}</span>
</li>
`;
});
// newElement會是一個陣列
// ul下的li顯示找到的物件
suggestions.innerHTML = newElement;
}
```

所以要先把陣列變成串起來的字串
### highlight效果
使用"".replace()
```javascript
const newElement = matchArray
.map((place) => {
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="name">${place.population}</span>
</li>
`;
})
.join("");
```
:heavy_check_mark:為什麼輸入的是小寫但標注的顯示大寫

因為css = = 太白痴ㄌ
```css
.suggestions li {
text-transform: capitalize;
}
```
阿那個加千位數逗點的是怎麼回事...
為了逃避正則(?)自己寫了一個奇怪的function哈哈哈
```javascript
function separateThousands(input) {
// input是字串
// ["1", "2", "3", "4"]
// i=4 3 2 1
return [...input].reduceRight(
(result, currentFigure, currentIndex, input) => {
const backwardIndex = input.length - currentIndex;
if (backwardIndex % 3 === 1) {
// i = 4, 7,...
return currentFigure.concat(",", result);
}
return currentFigure.concat("", result);
}
);
}
// ...
`<span class="name">${separateThousands(place.population)}</span>`
```
## D7
### Q1
何不使用Date物件來取得今年年份
```javascript
const someAdults = people.some((person) => {
const thisYear = 2024; // 我偷懶的寫法
const thisYear = (new Date()).getFullYear(); // 影片教學
// BUT!!!!! prettier直接給我把new Date()外面的()拿掉
// 還是可以跑
const thisYear = new Date().getFullYear();
return thisYear - person.year >= 19;
});
console.log({someAdults})
// 回傳一個物件,屬性是變數名稱,值是變數的值
```

阿他是在叫什麼笑鼠
> 效果做很足
> [name=Jerey]

### Q4
用slice切兩段出來再組合到新的array
```javascript
const newComments = [
...comments.slice(0, targetIndex),
...comments.slice(targetIndex + 1),
];
```
## D8
:question:為什麼要用js來重新設定大小?
```javascript
const canvas = document.querySelector("#draw");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// <canvas id="draw" width="678" height="798"></canvas>
```
> [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D)
> **CanvasRenderingContext2D.strokeStyle**
Color or style to use for the lines around shapes. Default #000 (black).
> **CanvasRenderingContext2D.lineJoin**
Defines the type of corners where two lines meet. Possible values: round, bevel, miter (default).
> **CanvasRenderingContext2D.lineCap**
Type of endings on the end of lines. Possible values: butt (default), round, square.
滑鼠點擊時繪製
```javascript
canvas.addEventListener("mousemove", draw);
// 讓點擊時才可以繪製
canvas.addEventListener("mousedown", () => (isDrawing = true));
canvas.addEventListener("mouseup", () => (isDrawing = false));
// 如果超出範圍就停止,就算再回範圍內也不能畫
canvas.addEventListener("mouseout", () => (isDrawing = false));
```
開始繪製路線
> [CanvasRenderingContext2D: beginPath()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath) starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path.
> [CanvasRenderingContext2D: stroke()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke) strokes (outlines) the current or given path with the current stroke style. Strokes are aligned to the center of a path; in other words, half of the stroke is drawn on the inner side, and half on the outer side.
```javascript
ctx.beginPath(); // 路線開始
ctx.strokeStyle = "blue";
ctx.moveTo(20, 20); // 起點
ctx.lineTo(200, 20); // 終點
ctx.stroke(); // 繪製!
// offset是啥
// 和canvas原點(0,0)的差
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(event.offsetX, event.offsetY);
ctx.stroke();
```
> The offsetX read-only property of the MouseEvent interface provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node. [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX)
### 調整畫筆
[Mother-effing](https://mothereffinghsl.com/)
0到360度代表顏色,第二個是飽和度,第三個是亮度
```javascript
hsl(${hue}, 100%, 50%)
```
混色效果
```javascript
ctx.globalCompositeOperation = "multiply";
```
> [globalCompositeOperation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) 可以參考PS的混色效果
消除一切灰飛煙滅
```javascript
ctx.clearRect(0, 0, canvas.width, canvas.height)
```
## D9
沒什麼好寫的0.0
## D10
:question:影片中"change"和"click"的差別是什麼
好像跟鍵盤輸入有關?但我小測一下覺得沒什麼差
應該是"change"沒有shiftKey這個屬性可以用來偵測是否有鍵盤輸入shift
### 偵測鍵盤輸入
```javascript
// pointerEvent
event.shiftKey
```
> [pointer event](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/shiftKey)

input element

不去依賴DOM的結構去尋找元素跟元素之間的關係
-> 從頭開始掃描確認每個input應該要是的狀態是什麼
### 留言提到的bug
1. 同一個選項,按兩次,然後按住shift按第三次,這個選項下方的選項就會全部被選起來
按第一次

按第二次

按住shift按第三次

解法:lastChecked和現在check的不能是同一個選項
```javascript
if (
event.shiftKey === true &&
this.checked === true &&
this !== lastChecked // 加這行
)
```
2. lastChecked沒有被選起來的情況下,去按住shift點另一個選項也會把這中間的選項選起來
上一個取消選擇的是Just regular JavaScript, 按住shift

點最後一個選項也有作用

解法:要確認lastChecked是checked的狀態
```javascript
if (
event.shiftKey === true &&
this.checked === true &&
lastChecked.checked === true && // 加這行
this !== lastChecked
)
```