0%

DOM - 操控網頁元素的必學技巧

現在的網頁講求的是不只是速度,與使用者之間的互動也非常的重要,這些互動免不了由 JavaScript 的「事件」或「監聽」來操作,而這些操作往往包含了對 HTML 標籤或網頁元素取值,增加元素或屬性來達到效果,這篇主要介紹如何透過 JavaScript 來控制或修改 DOM Tree 上面的節點元素。

DOM 是什麼?

DOM, (Document Object Model, 檔案物件模型),是網頁內容(HTML 或 XML)所建構網頁的內容結構,並提供給編程語言一套完整的操控檔案文件的 API 。

DOM Tree 樹狀結構與節點(node):網頁的結構有如樹狀般,這些相連的交會點,有如骨骼的關節,每一個節點上是網頁結構的元素所構成。這些節點(node)具有關連性,也具有父層或兄弟層的關係。分為以下三種:

  • HTML 元素節點(element nodes or element objects)
  • 文字節點(text node)
  • 註解節點(comment node)

DOM 提供了兩種節點的集合:HTML collection(element nodes) 以及 NodeList (包含以上三種)。以下圖來說,如果選擇選取 HTML collection,那只會選取到 Element 的部份,但如果使用 NodeList 的方式,就可以選取指定元素底下所有的節點(NodeList)。

例如在 HTML 的文件(Document)中,主支幹為HTML,接著分為<head><body>,在各分支下,又有許多分支節點,利用找尋這些 HTML 結構的節點,我們可以依循找到我們希望操控的節點元素,來進行操控。JavaScript 可以透過這些節點為進入點,進行修改、新增、覆蓋和刪除元素或屬性。

為什麼要瞭解 DOM?

當瀏覽器首次執行 HTML 文件時,會一行一行的解析 HTML 的標籤,並解析成一個個的節點,每個節點之間產生父元素與子元素的關係,如同以線相連接,整個文件最終形稱一個稱為 DOM 樹的樹狀結構,接著瀏覽器再依照這個樹狀結構去呈現頁面。

DOM 的樹狀結構在瀏覽器環境也是存在的(BOM),瀏覽器的一部分就是在做執行 JavaScript 的事,所以透過 JavaScript 來對 HTML 的節點進行修改或操控,也變得輕而易舉。DOM 的節點物件有如 JS 的物件,容易操控方法和屬性,瀏覽器會「動態的」即時監看跟蹤這些 DOM 節點的變化,如果節點有變化(通常是透過 JavaScript),瀏覽器也會做出相對應的變化更新,進而重新且局部的渲染被修改或操控的部分元素。

瞭解 document 的重要性

‵document‵是 DOM Tree 的起始點,當我們在瀏覽器的 DevTool 工具的 Console輸入document時,回傳的即是 HTML 物件,並可看到 HTML 的結構:

1
2
3
4
5
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>...</body>
</html>

或許你會猜到,這也是為什麼我們使用 JavaScript 來選取指定的 DOM 時的語法 querySelector是以document為開頭,如:document.querySelector('#wrap');

DOM 環境配置與 JavaScript 應放位置

因為瀏覽器是一行行解析 HTML 的,所以如果把有對 DOM 做監聽或操控的程式碼寫在 Head裡,也等於這部分的 JavaScript 會先被解析,但是想要操控的元素都在 HTML 裡,以致於瀏覽器解析程式碼時,會有讀取不到 JS 要操控的 DOM 的部分,也就是因為解析的先後順序而產生問題,這也是為什麼我們通常會把 JS 或外部的 JS ,在 Body 的關閉標籤 </body> 前才引入或寫入 JS 的原因。

DOM 的選擇器

如果要操控某一個元素,當然也必須先把此元素選取出來才能進一步的更改它,選取 DOM 有好幾種選擇器,我們只列舉兩個最常用的。

可選取單個指定元素:querySelector(selectors)

無論是 HTML 標籤元素或是 Class 與 ID,querySelector()都可以很輕易的選取到指定的元素。例如可以直接選取 HTML 元素、可以選取帶有 id 或 class 的元素,甚直可以用類似 CSS 的撰寫方式選取,不過需注意的是,querySelector()只會回傳第一個符合選取條件的元素(element objects),且querySelector()是以深度優先搜尋演算法,所以越下層的節點會先被找倒。
以下為常用語法:

1
2
3
4
5
6
// 選取 HTML 標籤元素 h1
const h1 = document.querySelector('h1');
// 選取 class class="site-header"
const siteHeader = document.querySelector('.site-header');
// 選取 id id="wrap"
const wrap = document.querySelector('#wrap');

querySelector()也可以讓我們多層選取,例如想要選取<h1> 標籤裡的 <a>連結

1
2
3
4
5
6
7
<h1 class="site-title">
<a href="/blog/"> 前端 </a>
</h1>
<script>
const myLike = document.querySelector('h1 a');
const like = document.querySelector('.site-title > a');
</script>

可重複選取多個元素:querySelectorAll(selectors)

如果希望一次選取列表裡所有的元素,或是所有相同的類別或標籤,querySelectorAll 就可以辦到。而使用querySelectorAll()來選取元素時,回傳回來的會是個靜態(No live)的 NodeList()的偽陣列(array-like object),所有找到的節點列表都會被納入這個 NodeList 的偽陣列裡。

偽陣列(array-like object)並不是一般的陣列型別,所以無法使用陣列的方法如 pop()

1
2
const myLinks = document.querySelector('a');
myLinks; // NodeList(2) [a,a] 表示找到兩個 a 節點

如果要操控第二個a節點,因為回傳的是陣列,我們就必須以陣列的方式,利用index來選取它。

1
2
3
const myLink = myLinks[1];
// 選取到就可修改屬性或其他操控
myLin.setAttribute('href', 'https://www.google.fr');

對 DOM 操控的幾個方法

一但選取到我們要修改的 DOM 節點,就可以使用以下幾個方法來操控:

在節點上寫入文字資料:textContent

  • 主要用來操控標籤、元素內的「文字內容」。
  • 動態修改節點內的文字內容。
  • 原本節點內的文字內容會被新內容覆蓋掉。
1
2
3
4
// 選取 HTML 標籤元素 h1
const h1 = document.querySelector('h1');
// 將 HTML 元素的文字內容替換成 Hello Word
h1.textContent = 'Hello Word';

在節點上加入 HTML 標籤:innerHTML

  • 主要用來操控 HTML 的「結構」標籤與元素。
  • 新增網頁 HTML 標籤與內容。
  • 原本節點內的元素與內容會被新的內容覆蓋掉。
  • 注意單、雙引號的問題,如果標籤內屬性是雙引號,賦值內容就應是單引號。
1
2
3
4
// 選取 id id="wrap"
const wrap = document.querySelector('#wrap');
// 新增一個 h1 的標籤與內容在 wrap 裡
wrap.innerHTML = '<h1>Hello word</h1>';

以變數帶入 innerHTML

有時會遇到資料常常更動的問題,如果以變數取代,也會較容易修改與維護。

1
2
3
4
const googleUrl = 'https://www.google.fr';
const wrap = document.querySelector('#wrap');

wrap.innerHTML = `<a id="wrap" href="/${googleUrl}/">首頁</a>`;

對 HTML 標籤元素增加屬性:setAttribute

對於想要修改網頁的外觀,setAttribute 會相當好用,我們可以用它來對元素新增 class 類別,或是給予特定的 ID 名稱。
語法為:setAttribute("屬性名稱","屬性內容"),可帶入兩個參數:屬性名稱與屬性內容。setAttribute("class","city")也表示在指定元素上增加一個名為city的 class 類別。

1
2
3
4
const googleUrl = 'https://www.google.fr';
const wrap = document.querySelector('#wrap');
// 增加 CSS class 屬性
wrap.setAttribute('class', 'nav-link');
  • 如果這個屬性已經存在,會被新的值覆蓋。
1
2
3
4
const myLink = document.querySelector('a');
// 修改連結
const googleUrl = 'https://www.google.fr';
myLink.setAttribute('href', `${googleUrl}`);

對元素取值的方法:.innerHTML、.textContent、.getAttribute

我們也可以使用透過 DOM 節點來取值,先把要取值的 DOM 抓出來,再利用getAttribute()來取值。常用的情境是抓取使用者在表單輸入的內容。

1
<a href="https://www.yahoo.fr" class="nav-list"><span>前端</span></a>
1
2
3
4
5
6
7
8
// 取 a 連結裡的連結`href`和`class`的值
const myLink = document.querySelector('a');
myLink.getAttribute('href');
myLink.getAttribute('class');

// 取 a 連結裡的 HTML 的 <span> 結構或文字內容
myLink.innerHTML; // <span>前端</span>
myLink.textContent; // 前端

表單元素取值的方式

在使用者輸入表單的資料時,如何取得這些表單資料?或依使用者的選取不同表單元素,而顯示不同的資料?

1
2
3
4
5
<input type="text" class="name" value="Your Name">
<select class="city">
<option value="Paris">Paris</option>
<option value="Taipei">Taipei</option>
</select>
1
2
3
4
5
6
7
8
9
10
11
// 取出表單文字欄`name`的值
const name = document.querySelector('.name');
name.value; // Your Name
// 表單元素重新賦值
name.value = 'My name';

// 取出下拉式選單的值
const city = document.querySelector('.city');
city.value; // Paris
// 把選單狀態定位在 Taipei
city.value = 'Taipei';

以上介紹幾種操控網頁元素的常用方法,如果要練習,最方便的方式是打開網頁與打開瀏覽器的開發工具,直接在 console輸入這些語法,並試著做些修改與增加,就會知道這些方法多好用了!