[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中顯示這個向上鍵的物件 ![image](https://hackmd.io/_uploads/SJgg_VUOC.png) ```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 ![image](https://hackmd.io/_uploads/r18fqBvO0.png) :heavy_check_mark:因為他是Element不是Object? > 我嘗試使用`console.dir`將他視為物件展開: ![image](https://hackmd.io/_uploads/HyEGX6U_C.png) 但前提是我在全域環境有取得到`<audio>`這個元素,而不是在function裡面: ```javascript const audio = document.querySelector(`audio[data-key='65']`); ``` > 但我覺得他是DOM的特殊物件所以應該沒辦法當成一般物件為所欲為 > 正常印出來長這樣 ![image](https://hackmd.io/_uploads/S1N14pLO0.png) > [name=Jeremy] 結果是在原型的原型上面:clap: ![image](https://hackmd.io/_uploads/HJf5qSPO0.png) 但可以在devTool去看 ![image](https://hackmd.io/_uploads/Sy9umSLOA.png) 看到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 ![image](https://hackmd.io/_uploads/H122kDI_R.png) :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這個屬性已經被棄用了 ![image](https://hackmd.io/_uploads/HJgYWPvOC.png) [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曲線 ![image](https://hackmd.io/_uploads/r1tE0cPdR.png) ### 分檔發生語法問題 我分了一個tools.js,然後import到main.js 結果發生以下錯誤 > Uncaught SyntaxError: Cannot use import statement outside a module 解決方法:在html引入script的地方加上屬性`type="module"` ![image](https://hackmd.io/_uploads/rk8bsAOOA.png) ### 從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 ``` ![image](https://hackmd.io/_uploads/H1dsycjuR.png) ### 滑動滑桿(?)要跟著變化 這兩個要一起設定 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" /> ``` ![image](https://hackmd.io/_uploads/B1U1UqjuR.png) ### documentElement表示root ```javascript document.documentElement.style.setProperty( `--${this.name}`, this.value + suffix ); ``` ![image](https://hackmd.io/_uploads/r1-Dq5iuC.png) 順帶一提,我很無聊加了恢復預設值的按鈕 暴力解決 如果有更好的寫法歡迎跟我說🥹 ```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'的 ![image](https://hackmd.io/_uploads/HydvFonOC.png) 不過教學是教兩段式選取 :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呢? 其他的呢? ![image](https://hackmd.io/_uploads/HypgyAhuA.png) ![image](https://hackmd.io/_uploads/rkBzkAnO0.png) ![image](https://hackmd.io/_uploads/rkGXJ0ndR.png) ### 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); ``` ![image](https://hackmd.io/_uploads/Bkfjt_1Y0.png) 利用.json把這個response解析成JSON ![image](https://hackmd.io/_uploads/HJgM9dyF0.png) ```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)); ``` ![image](https://hackmd.io/_uploads/S1CB0uJFC.png) ### 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; } ``` ![image](https://hackmd.io/_uploads/Bk8jLYkYR.png) 所以要先把陣列變成串起來的字串 ### 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:為什麼輸入的是小寫但標注的顯示大寫 ![image](https://hackmd.io/_uploads/HJBdct1F0.png) 因為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}) // 回傳一個物件,屬性是變數名稱,值是變數的值 ``` ![image](https://hackmd.io/_uploads/B1peT7ZYA.png) 阿他是在叫什麼笑鼠 > 效果做很足 > [name=Jerey] ![image](https://hackmd.io/_uploads/HJYXAm-tC.png) ### 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) ![image](https://hackmd.io/_uploads/rJmcGEDF0.png) input element ![image](https://hackmd.io/_uploads/B1sL-4DtR.png) 不去依賴DOM的結構去尋找元素跟元素之間的關係 -> 從頭開始掃描確認每個input應該要是的狀態是什麼 ### 留言提到的bug 1. 同一個選項,按兩次,然後按住shift按第三次,這個選項下方的選項就會全部被選起來 按第一次 ![image](https://hackmd.io/_uploads/r1wLDLvtA.png) 按第二次 ![image](https://hackmd.io/_uploads/Hy1vvIDFA.png) 按住shift按第三次 ![image](https://hackmd.io/_uploads/rJuDPLDYR.png) 解法:lastChecked和現在check的不能是同一個選項 ```javascript if ( event.shiftKey === true && this.checked === true && this !== lastChecked // 加這行 ) ``` 2. lastChecked沒有被選起來的情況下,去按住shift點另一個選項也會把這中間的選項選起來 上一個取消選擇的是Just regular JavaScript, 按住shift ![image](https://hackmd.io/_uploads/S1jgFIDKA.png) 點最後一個選項也有作用 ![image](https://hackmd.io/_uploads/BJSHF8DYC.png) 解法:要確認lastChecked是checked的狀態 ```javascript if ( event.shiftKey === true && this.checked === true && lastChecked.checked === true && // 加這行 this !== lastChecked ) ```