JavaScript - DOM 與瀏覽器 API
主題:修改屬性與樣式
簡介
在前端開發中,DOM(Document Object Model) 是連結 HTML、CSS 與 JavaScript 的橋樑。透過 DOM,我們可以即時讀取、變更或刪除頁面上的元素,進而打造互動性十足的使用者體驗。
其中最常見的操作就是修改屬性(attributes)與樣式(styles)。無論是切換按鈕的狀態、動態加入圖片、或是根據使用者行為改變排版,這兩項技術都是必備功力。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步掌握「修改屬性與樣式」的要領,適合 初學者 到 中階開發者 使用。
核心概念
1. 取得與操作 DOM 元素
在修改屬性或樣式之前,我們必須先取得目標元素。常見的選取方式包括:
| 方法 | 說明 | 範例 |
|---|---|---|
document.getElementById(id) |
依 ID 取得單一元素 | const btn = document.getElementById('submitBtn'); |
document.querySelector(selector) |
使用 CSS 選擇器,回傳第一個符合的元素 | const firstImg = document.querySelector('img'); |
document.querySelectorAll(selector) |
使用 CSS 選擇器,回傳所有符合的元素(NodeList) | const items = document.querySelectorAll('.list-item'); |
Tip:
querySelector與querySelectorAll支援完整的 CSS 選擇器,彈性最高。
2. 修改屬性(Attributes)
HTML 屬性分為 全局屬性(如 class、id、title)與 特定屬性(如 src、href、disabled)。JavaScript 提供了兩組 API 來操作它們:
| 方法 | 用途 | 範例 |
|---|---|---|
element.getAttribute(name) |
取得屬性值(字串) | let src = img.getAttribute('src'); |
element.setAttribute(name, value) |
設定或新增屬性 | img.setAttribute('alt', '風景圖'); |
element.removeAttribute(name) |
移除屬性 | link.removeAttribute('target'); |
element.hasAttribute(name) |
判斷屬性是否存在 | if (btn.hasAttribute('disabled')) … |
注意:對於布林屬性(如
disabled、checked),直接設定屬性值為字串"true"或"false"會產生預期外的行為,建議使用 屬性對應的屬性(property)方式(element.disabled = true)。
範例 1:切換按鈕的 disabled 屬性
const submitBtn = document.getElementById('submitBtn');
/**
* 依照表單驗證結果啟用或禁用送出按鈕
* @param {boolean} isValid 表單是否有效
*/
function toggleSubmit(isValid) {
// 使用屬性(property)方式設定布林值
submitBtn.disabled = !isValid;
// 同步更新 HTML 標籤的 disabled 屬性(可省略,瀏覽器會自動反映)
// submitBtn.setAttribute('disabled', !isValid);
}
3. 操作樣式(Styles)
樣式分為 行內樣式(inline style)與 外部/內嵌樣式表。在 JavaScript 中,我們最常使用 element.style 物件直接設定行內樣式,或透過 classList 操作 CSS class。
| 方法 | 用途 | 範例 |
|---|---|---|
element.style.property = value |
設定單一行內樣式 | box.style.backgroundColor = '#ffeb3b'; |
element.style.cssText = '...' |
一次設定多個行內樣式(字串) | box.style.cssText = 'width:100px;height:100px;'; |
element.classList.add(className) |
加入 CSS class | card.classList.add('is-active'); |
element.classList.remove(className) |
移除 CSS class | card.classList.remove('is-hidden'); |
element.classList.toggle(className, force?) |
切換 class(可強制) | menu.classList.toggle('open', isOpen); |
element.classList.contains(className) |
判斷是否包含 class | if (nav.classList.contains('sticky')) … |
範例 2:使用 classList 動態切換暗黑模式
const toggleBtn = document.getElementById('themeToggle');
const htmlRoot = document.documentElement; // <html> 元素
toggleBtn.addEventListener('click', () => {
// 切換 .dark-theme class,配合 CSS 完成樣式切換
htmlRoot.classList.toggle('dark-theme');
// 儲存使用者偏好到 localStorage,刷新後仍保留
const isDark = htmlRoot.classList.contains('dark-theme');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
/* 初始化:讀取先前儲存的偏好 */
document.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
htmlRoot.classList.add('dark-theme');
}
});
4. 取得計算後的樣式(Computed Style)
有時我們需要知道元素在瀏覽器渲染後的真實尺寸或顏色,這時可以使用 window.getComputedStyle(element)。
const box = document.querySelector('.box');
const styles = window.getComputedStyle(box);
// 取得最終計算出的寬度(包含單位)
const width = styles.width; // 例: "200px"
console.log('Box 的實際寬度:', width);
提醒:
getComputedStyle會回傳 只讀 的 CSSStyleDeclaration,不能直接修改,僅供讀取。
5. 動態建立與插入元素
修改屬性與樣式往往伴隨 DOM 增刪。以下示範如何用 JavaScript 建立新元素、設定屬性與樣式,最後插入到文件中。
// 建立 <img> 標籤
const newImg = document.createElement('img');
// 設定屬性
newImg.setAttribute('src', 'https://picsum.photos/300/200');
newImg.setAttribute('alt', '隨機風景圖');
newImg.width = 300; // 直接使用屬性(property)設定寬度
// 設定行內樣式
newImg.style.borderRadius = '8px';
newImg.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
// 插入到指定容器的最後面
const gallery = document.getElementById('gallery');
gallery.appendChild(newImg);
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 正確做法 |
|---|---|---|
直接寫 element.style = "color:red" |
會覆蓋掉已有的所有行內樣式,導致其他樣式遺失 | 使用 element.style.color = 'red' 或 element.style.cssText += 'color:red;' |
使用 setAttribute('class', ...) 取代 classList |
會一次性覆蓋全部 class,易造成樣式衝突 | 盡量使用 classList.add/remove/toggle 來操作單一 class |
對布林屬性使用字串 "true" / "false" |
網頁仍會把屬性視為「存在」而啟用(即使值是 "false") |
使用屬性對應的 property:element.disabled = true/false |
| 忘記在動態新增的元素上綁定事件 | 點擊不觸發預期行為 | 使用 事件委派(parent.addEventListener('click', e => { … }))或在插入後立即 addEventListener |
| 頻繁改變大量元素的 style | 造成重排(reflow)與重繪(repaint),導致畫面卡頓 | 使用 CSS class 切換、DocumentFragment 批次插入,或利用 requestAnimationFrame 調度更新 |
最佳實踐
盡量以 class 切換取代大量行內樣式
- CSS 負責樣式,JS 負責狀態。這樣可以保持樣式集中管理,降低維護成本。
使用
const/let明確宣告變數- 防止因全域變數污染而產生不可預期的錯誤。
善用
dataset儲存自訂資料const btn = document.querySelector('button'); btn.dataset.action = 'save'; // <button data-action="save"> console.log(btn.dataset.action); // "save"在需要讀取計算後樣式時,先確保元素已渲染
- 可放在
requestAnimationFrame、setTimeout(...,0)或DOMContentLoaded之後。
- 可放在
保持程式碼可讀性:
- 把重複的樣式變更抽成函式,例如
setDisabled(element, flag)。
- 把重複的樣式變更抽成函式,例如
實際應用場景
1. 表單即時驗證
在使用者輸入時即時檢查欄位是否合法,若不符合則加入 error class 並顯示錯誤訊息。
const emailInput = document.getElementById('email');
const errorMsg = document.getElementById('emailError');
emailInput.addEventListener('input', () => {
const isValid = /^\S+@\S+\.\S+$/.test(emailInput.value);
emailInput.classList.toggle('invalid', !isValid);
errorMsg.style.display = isValid ? 'none' : 'block';
});
2. 圖庫懶載入(Lazy Load)
當使用者捲動到圖片位置時,才把 src 設為實際圖片路徑,減少初始載入流量。
const lazyImgs = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 設定真正的 src
img.removeAttribute('data-src');
obs.unobserve(img);
}
});
});
lazyImgs.forEach(img => observer.observe(img));
3. 動態主題切換(Dark / Light)
結合 classList.toggle、localStorage 與 CSS 變數,讓使用者能在暗黑與亮色模式間自由切換。
/* style.css */
:root {
--bg-color: #ffffff;
--text-color: #222222;
}
.dark-theme {
--bg-color: #121212;
--text-color: #e0e0e0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
// JavaScript 如前面的範例
4. 動態生成卡片(Card)列表
從 API 取得資料後,依據每筆資料產生卡片,並使用 setAttribute、classList 設定圖片、標題與按鈕的屬性與樣式。
async function renderPosts() {
const resp = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');
const posts = await resp.json();
const container = document.getElementById('postList');
posts.forEach(post => {
const card = document.createElement('article');
card.classList.add('post-card');
const title = document.createElement('h3');
title.textContent = post.title;
card.appendChild(title);
const body = document.createElement('p');
body.textContent = post.body;
card.appendChild(body);
const readBtn = document.createElement('button');
readBtn.textContent = '閱讀全文';
readBtn.setAttribute('data-id', post.id);
readBtn.classList.add('read-more');
card.appendChild(readBtn);
container.appendChild(card);
});
}
renderPosts();
總結
- 取得元素:
getElementById、querySelector、querySelectorAll為基礎。 - 屬性操作:
getAttribute、setAttribute、removeAttribute,布林屬性優先使用 property(如disabled)。 - 樣式調整:
element.style直接設定行內樣式,建議以classList切換 CSS class 為主,保持樣式與行為分離。 - 計算樣式:
window.getComputedStyle用於取得渲染後的真實樣式。 - 動態建立:
document.createElement+appendChild(或append)完成元素的新增,別忘了同時設定屬性與樣式。
掌握這些技巧後,你就能在 DOM 中自由操控元素的 屬性 與 樣式,從簡單的表單驗證到複雜的 UI 動態切換,都不在話下。持續練習、遵守最佳實踐,未來在更大型的前端專案中,你將得心應手,寫出既易維護又效能佳的程式碼。祝開發順利! 🚀