[JavaScript] 操控 DOM 元素


Posted by Nicolakacha on 2020-09-06

本篇會介紹怎麼用 JavaScript 操控 DOM 元素,如何監聽不同的瀏覽器事件並做出反應

DOM (Document Oject Model)

什麼是 DOM? 簡單理解就是把 Document 的元素變成物件
舉例來說,我們可以選取該網頁文件,找到裡面的 .header,新增文字,賦予值叫 Hello World:

document.querySelector(".header").textContent = "Hello World"

選取 DOM 元素

getElement

getElementByTagName('div') 選到標籤
getElementByClassName('block') 選到該 class
getElementByID('header') 選到某 id

querySelector

可以直接用 CSS 選取器的方法來選去 DOM 元素
querySelector('div')
querySelector('.className')
querySelector('#id')

但要注意 querySelector 只會選到第一個,如果要選多個,需要使用 querySelectorAll
querySelectorAll('div') // 選到全部的 div

把 DOM 元素 存為一個變數方便每次使用

let element = document.querySelector(".header")

NODE 其他選取方式

parentNode 選到父節點
parentElement 選到父元素
childNodes 選到子節點 // 要注意它也會把空格也視為節點
children 選到子元素 // 只會選到 element
firstChild 選到第一個子節點 // 要注意也會選到空格節點
firstElementChild 選到第一個子元素
lastChild 選到最後一個子節點 // 要注意也會選到空格節點
lastElementChild 選到最後一個子元素
nextSibling 選到下一個節點 // 會選到空格節點
nextElementSibling 選到下一個元素
previousSibling 選到上一個節點 // 會選到空格節點
previousElementSibling 選到上一個元素

改變 DOM 元素

直接幫 DOM 元素改 CSS 樣式

.style[padding-top"] = "10px"
.style["paddingTop"] = "10px"

改變 DOM 元素的 class

.classList.add("active") // 增加 active 這個 class
.classList.remove("active") // 移除 active 這個 class
.classList.toggle("active") // 開關 active 這個 class
.classList.contains("sample") // 有沒有這個 class

<body>
    <div id="block">Hello World!</div>
</body>
<style>
    .class {
        font-size: 14px;
    }
</style>
<script>
    const element = document.querySelector("#block");
    element.classList.add("active")
</script>

改變 DOM 元素的內容

.innertext = "hello world" // 抓 DOM 元素裡文字內容來改
.innerHTML = "hello world" // 把 DOM 元素中全部的東西拿出來
.outterHTML = "hello world" // 把 DOM 元素(含 DOM 元素本身)全部的東西抓出來
.textContent = "Hello World" // 賦值
.setAttribute("style", "color: red") // 賦屬性
.innerText 關注 style,而 textContnet 則不

增加屬性

.setAttribute('title', 'Hello Div')

插入與刪除元素

createElement 來建立一個物件
createTextNode 建立純文字
.insertBefore(newDiv, h1) 在 h1 之前插入 newDiv
.appendChild 把物件插入回 HTML 內

<body>
    <div id="block">Hello World!</div>
</body>
<script>
    const element = document.querySelector("#block");
    const item = document.createElement("div") //插入 <div>
    const item = document.createElement("123") //插入 123
    element.appendChild(item)
</script>

removeChild 把物件刪除

element.removeChild(document.querySelector("a"))
// 把 a 元素刪除

Event

e.clientX / e.clientY is from the browser 找絕對座標
e.offsetX / e.offsetY is from the target itse 找相對座標
e.altKey / e.ctrlKey / e.shiftKey 檢查這些按鍵是否按著
mouseenter / mouseover 滑鼠移進移出
mouseleave / mouseout 滑鼠移進移出

上面兩組事件看起來功能一樣,但在冒泡事件上的支援不同,mouseenter 和 mouseleave 不支援冒泡事件,舉例而言,在一個元素上的子元素上進出或離開的時候,會觸發 mouseover 和 mouseout 事件,但卻不會觸發 mouseenter 和 mouseleave 事件
體驗看看

mousemove 滑鼠移動
keydown / keyup / keypress 鍵盤輸入
focus 對焦 input 框 / blur 取消對焦
cut / paste
input 所有對 input 可以做的事都會觸發這個事件

Event Listenser

element.addEventListener("監聽的事件名稱", 要做什麼的函式)

<body>
    <div id="block">Hello World!</div>
</body>

<script>
    const element = document.querySelector("#block");
    element.addEventListener("click", function(){
        alert("click!")  // 匿名函式
    })
</script>

e 是什麼?

瀏覽器會呼叫這個 function 的時候帶的參數,利用這個參數來拿到這個事件相關的資訊

const element = document.querySelector("#block");
element.addEventListener("click", function(e){
    console.log(e) //該事件所有的參數
    console.log(e.target) // 該事件觸發的目標
}

表單處理

<script>
    const element = document.querySelector(".login-form");
    element.addEventListener("submit", function(e){
        alert("submit");
    })
</script>

e.preventDefault() 可以阻止預設行為

<script>
    const element = document.querySelector(".login-form");
    element.addEventListener("submit", function(e){
        e.preventDefault()
    })
</script>

事件傳遞機制

事件傳遞可以分為三個階段,事件捕獲 -> 目標 -> 事件冒泡,可以利用 addEventListener 的第三個參數來決定要再捕獲或冒泡階段去監聽這個事件,但當事件傳到 target 本身,沒有分捕獲跟冒泡
true 代表添加到捕獲階段;false (預設) 代表添加到冒泡階段

事件傳遞會從 DOM tree 的最外層開始向內層傳遞,直到觸發事件的元素(e.target),再往上層冒泡至 DOM tree 的最外層。當事件傳遞到觸發事件的元素(e.target)時,是沒有分冒泡或捕獲階段的,無論 addEventListener 的第三個參數是 true 還是 false,e.eventPhase 都是 AT_TARGET。

注意:當事件傳遞到點擊的真正對象,也就是 e.target 時,無論使用 addEventListener 的第三個參數是 true 還是 false,這邊的 e.eventPhase 都會變成 AT_TARGET。

preventDefault 跟 JavaScript 的事件傳遞「一點關係都沒有」,加上這一行後,事件還是會繼續往下傳遞。

捕獲:事件傳遞機制中,由 DOM tree 最外層向內逐層傳遞,觸發各節點的事件監聽。

冒泡:事件傳遞機制中由觸發事件的元素,向外逐層傳遞觸發各元素的事件監聽,直到 DOM tree 的最外層。

停止事件傳遞

停止傳遞

e.stopPropagation()

要注意同層級剩下的 listener 仍然會被執行

停止所有同層級的事件傳遞

e.stopImmediatePropagation()

阻擋預設行為

event.preventDefault():是阻擋事件的預設行為,和事件傳遞沒有關係,並不影響事件傳遞。像點擊 <a> 連結會觸發連結到某個網址,使用 event.preventDefault() 可以阻擋這個動作。

事件代理

利用事件傳遞機制的原理,去監聽外層的元素,判斷 e.target 是我們要的目標節點時,再去做後續的操作,在很多情況下就可以不用去重複監聽無數個目標物,底下有個簡單的範例:

有一個 ul,底下 1000 個 li,如果你幫每一個 li 都加上一個 eventListener,你就新建了 1000 個 function。

但我們剛剛已經知道,任何點擊 li 的事件其實都會傳到 ul 身上,因此我們可以在 ul 身上掛一個 listener 就好。

<!DOCTYPE html>
    <html>
        <body>
            <ul id="list">
                <li data-index="1">1</li>
                <li data-index="2">2</li>
                <li data-index="3">3</li>
            </ul>
    </body>
</html>
document.getElementById('list').addEventListener('click', (e) => {
  console.log(e.target.getAttribute('data-index'));
})

而這樣的好處是當你新增或是刪除一個 li 的時候,不用去處理跟那個元素相關的 listener,因為你的 listener 是放在 ul 身上。這樣透過父節點來處理子節點的事件,就叫做事件代理。

window.addEventListener('click', (e) => {
  console.log(e.target);
}, true)

利用事件傳遞機制的特性,在window上面使用捕獲,就能保證一定是第一個被執行的事件,你就可以在這個 function 裡面偵測頁面中每一個元素的點擊,可以傳回去做數據統計及分析。

資料儲存

cookie

Server 端透過 HTTP response 把資料寫到 cookie
在 header 內的 Set-Cookie 可以存放 cookie
所有的 request 都會自動把瀏覽器的 cookie 帶上去,為了讓瀏覽器可以辨識身份

local storage

const oldValue = windows.localStarge.getItem("text")
document.querySelector(".text").value = oldValue;

document.querySelector('button').addEventListener("click"), 
function() {
  const value = document.querySelector(".text").value
  window.localStorage.setUtem('text', value)
}

session storage

只在這個分頁存在的時候有效,不同的分頁也不能共享

const oldValue = windows.sessionStorage.getItem("text")
document.querySelector(".text").value = oldValue;

document.querySelector('button').addEventListener("click"), 
function() {
  const value = document.querySelector(".text").value
  window.sessionStorage.setUtem('text', value)
}

其他補充

Lazing Loading 無限捲動


#DOM #javascript







Related Posts

漫談傳輸介面-SATA

漫談傳輸介面-SATA

【單元測試的藝術】Chap 9: 在組織中導入單元測試

【單元測試的藝術】Chap 9: 在組織中導入單元測試

[Day 1] JS in Pipeline - DevOps for Local Development Environment (1)

[Day 1] JS in Pipeline - DevOps for Local Development Environment (1)



Comments