本文 AI 產出,尚未審核

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');

TipquerySelectorquerySelectorAll 支援完整的 CSS 選擇器,彈性最高。

2. 修改屬性(Attributes)

HTML 屬性分為 全局屬性(如 classidtitle)與 特定屬性(如 srchrefdisabled)。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')) …

注意:對於布林屬性(如 disabledchecked),直接設定屬性值為字串 "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 調度更新

最佳實踐

  1. 盡量以 class 切換取代大量行內樣式

    • CSS 負責樣式,JS 負責狀態。這樣可以保持樣式集中管理,降低維護成本。
  2. 使用 const / let 明確宣告變數

    • 防止因全域變數污染而產生不可預期的錯誤。
  3. 善用 dataset 儲存自訂資料

    const btn = document.querySelector('button');
    btn.dataset.action = 'save'; // <button data-action="save">
    console.log(btn.dataset.action); // "save"
    
  4. 在需要讀取計算後樣式時,先確保元素已渲染

    • 可放在 requestAnimationFramesetTimeout(...,0)DOMContentLoaded 之後。
  5. 保持程式碼可讀性:

    • 把重複的樣式變更抽成函式,例如 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.togglelocalStorage 與 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 取得資料後,依據每筆資料產生卡片,並使用 setAttributeclassList 設定圖片、標題與按鈕的屬性與樣式。

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();

總結

  • 取得元素getElementByIdquerySelectorquerySelectorAll 為基礎。
  • 屬性操作getAttributesetAttributeremoveAttribute,布林屬性優先使用 property(如 disabled)。
  • 樣式調整element.style 直接設定行內樣式,建議classList 切換 CSS class 為主,保持樣式與行為分離。
  • 計算樣式window.getComputedStyle 用於取得渲染後的真實樣式。
  • 動態建立document.createElement + appendChild(或 append)完成元素的新增,別忘了同時設定屬性與樣式。

掌握這些技巧後,你就能在 DOM 中自由操控元素的 屬性樣式,從簡單的表單驗證到複雜的 UI 動態切換,都不在話下。持續練習、遵守最佳實踐,未來在更大型的前端專案中,你將得心應手,寫出既易維護效能佳的程式碼。祝開發順利! 🚀