Maple楓
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # JavaScript DOM 操作 <!-- ## 目錄 - [DOM 選擇器](#DOM-選擇器) - [DOM 事件](#DOM事件) - [DOM Scroll 事件](#DOM-Scroll-事件) - [DOM 建立與移除元素](#DOM-建立與移除元素) - [DOM 修改與設置屬性](#DOM-修改與設置屬性) - [DOM 節點查找](#DOM-節點查找) - [表單操作](#表單操作) - [實用範例](#實用範例) - [效能優化建議](#效能優化建議) - [常見問題](#常見問題) --- --> ## DOM 選擇器 ### 單一元素選擇 ```javascript // 選擇 ID document.getElementById('myId') // 選擇第一個符合的元素 document.querySelector('#myId') // ID document.querySelector('.myClass') // Class document.querySelector('div') // 標籤 document.querySelector('[data-id="123"]') // 屬性 ``` ### 多個元素選擇 ```javascript // 選擇多個元素 (返回 HTMLCollection) document.getElementsByTagName('div') // 👉 回傳 HTMLCollection(3) [div, div, div] document.getElementsByClassName('myClass') // 👉 回傳 HTMLCollection(2) [div.myClass, p.myClass] // 選擇多個元素 (返回 NodeList) document.querySelectorAll('div') // 👉 回傳 NodeList(3) [div, div, div] document.querySelectorAll('.myClass') // 👉 回傳 NodeList(3) [div.myClass, span.myClass, p.myClass] ``` **比較:** | 方法 | 返回類型 | 是否即時更新 | 支援 CSS 選擇器 | |------|---------|-------------|----------------| | `getElementsBy...` | HTMLCollection | ✅ 是 | ❌ 否 | | `querySelectorAll` | NodeList | ❌ 否 | ✅ 是 | --- ## DOM事件 ### DOM 0 vs DOM 2 事件綁定 **DOM 0 (舊式):** - 語法: `obj.onclick = function() {...}` - 同一事件只能綁定一個處理函數,後者會覆蓋前者 **DOM 2 (推薦):** - 語法: `obj.addEventListener('click', function() {...})` - 同一事件可綁定多個處理函數,依序執行 - 可以移除事件監聽器 --- ### 常用事件列表 #### 頁面載入事件 ```javascript // DOM 結構載入完成 (不等待圖片、CSS) document.addEventListener('DOMContentLoaded', function() { console.log('DOM 已準備好') }) // 頁面完全載入 (包含圖片、CSS) window.addEventListener('load', function() { console.log('頁面完全載入') }) ``` #### 滑鼠事件 ```javascript const element = document.querySelector('.box') // 點擊 element.addEventListener('click', function() {}) // 雙擊 element.addEventListener('dblclick', function() {}) // 按下滑鼠 element.addEventListener('mousedown', function() {}) // 放開滑鼠 element.addEventListener('mouseup', function() {}) // 滑鼠移動 element.addEventListener('mousemove', function() {}) // 滑鼠移入 (不包含子元素) element.addEventListener('mouseenter', function() {}) // 滑鼠移入 (包含子元素) element.addEventListener('mouseover', function() {}) // 滑鼠移出 (不包含子元素) element.addEventListener('mouseleave', function() {}) // 滑鼠移出 (包含子元素) element.addEventListener('mouseout', function() {}) ``` **mouseenter vs mouseover 差異:** - `mouseenter`: 只在進入元素本身時觸發 - `mouseover`: 進入元素或其子元素都會觸發 #### 鍵盤事件 ```javascript // 按下鍵盤 element.addEventListener('keydown', function(e) { console.log('按下:', e.key) }) // 放開鍵盤 element.addEventListener('keyup', function(e) { console.log('放開:', e.key) }) ``` #### 表單事件 ```javascript const input = document.querySelector('input') // 值改變時 (失去焦點後) input.addEventListener('change', function() {}) // 值改變時 (即時) input.addEventListener('input', function() {}) // 獲得焦點 input.addEventListener('focus', function() {}) // 失去焦點 input.addEventListener('blur', function() {}) ``` #### 視窗事件 ```javascript // 視窗大小改變 window.addEventListener('resize', function() { console.log('視窗寬度:', window.innerWidth) }) // 滾動事件 window.addEventListener('scroll', function() { console.log('滾動位置:', window.scrollY) }) ``` --- ### 事件進階操作 #### 阻止預設行為 ```javascript // 阻止連結跳轉 link.addEventListener('click', function(event) { event.preventDefault() }) // 阻止表單提交 form.addEventListener('submit', function(event) { event.preventDefault() }) ``` #### 停止事件冒泡 ```javascript element.addEventListener('click', function(e) { e.stopPropagation() // 防止事件向上傳遞 }) ``` #### 事件委派 (動態綁定) ```javascript // 適用於動態新增的元素 document.addEventListener('click', function(e) { // 判斷點擊的元素是否有特定 class if (e.target.classList.contains('btn')) { console.log('按鈕被點擊') } }) ``` --- ## DOM Scroll 事件 ### 滾動相關屬性 ```javascript // 頁面滾動位置 window.scrollY // 垂直滾動距離 window.scrollX // 水平滾動距離 // 視窗可視高度 window.innerHeight // 相當於 100vh window.innerWidth // 視窗寬度 ``` ### 元素尺寸屬性 | 屬性 | 說明 | 包含內容 | |------|------|---------| | `offsetWidth` / `offsetHeight` | 元素可見寬高 | content + padding + border + scrollbar | | `clientWidth` / `clientHeight` | 元素內部寬高 | content + padding | | `scrollWidth` / `scrollHeight` | 元素完整寬高 | content + padding + 溢出內容 | **圖解:** ![DOM_Scroll](https://hackmd.io/_uploads/SkUL3MQ0lx.jpg) ### 元素位置屬性 ```javascript const element = document.querySelector('.box') // 相對於父元素的距離 (父元素需設定 position 為 relative/absolute) element.offsetTop // 上方距離 element.offsetLeft // 左方距離 // 邊框寬度 element.clientTop // 上邊框寬度 element.clientLeft // 左邊框寬度 // 滾動距離 element.scrollTop // 元素內容向上滾動的距離 element.scrollLeft // 元素內容向左滾動的距離 ``` ### 取得元素位置資訊 ```javascript const rect = element.getBoundingClientRect() console.log(rect.top) // 元素頂部距離視窗頂部 console.log(rect.left) // 元素左側距離視窗左側 console.log(rect.bottom) // 元素底部距離視窗頂部 console.log(rect.right) // 元素右側距離視窗左側 console.log(rect.width) // 元素寬度 console.log(rect.height) // 元素高度 ``` ### 滾動到指定位置 ```javascript // 滾動到座標 window.scrollTo(0, 500) // 平滑滾動 window.scrollTo({ top: 500, behavior: 'smooth' }) // 滾動到元素位置 element.scrollIntoView({ behavior: 'smooth' }) ``` ### 滑鼠位置 ```javascript document.addEventListener('mousemove', function(e) { console.log('相對於視窗:', e.clientX, e.clientY) console.log('相對於頁面:', e.pageX, e.pageY) console.log('相對於元素:', e.offsetX, e.offsetY) }) ``` --- ## DOM 建立與移除元素 ### 建立元素 ```javascript // 建立元素節點 const div = document.createElement('div') // 建立文字節點 const text = document.createTextNode('Hello World') ``` ### 新增元素 ```javascript const parent = document.querySelector('.parent') const newElement = document.createElement('div') // 新增到最後 parent.appendChild(newElement) // 返回新增的節點 parent.append(newElement) // 無返回值,可接受多個參數 // 新增到最前 parent.prepend(newElement) // 插入到特定位置 parent.insertAdjacentHTML('beforebegin', '<div>元素之前</div>') parent.insertAdjacentHTML('afterbegin', '<div>第一個子元素之前</div>') parent.insertAdjacentHTML('beforeend', '<div>最後一個子元素之後</div>') parent.insertAdjacentHTML('afterend', '<div>元素之後</div>') ``` **insertAdjacentHTML 位置示意:** ```html <!-- beforebegin --> <div class="parent"> <!-- afterbegin --> <p>子元素</p> <!-- beforeend --> </div> <!-- afterend --> ``` ### 移除元素 ```javascript // 移除自己 element.remove() // 移除子元素 parent.removeChild(child) // 清空所有子元素 parent.innerHTML = '' ``` --- ## DOM 修改與設置屬性 ### 取得/設定內容 ```javascript const element = document.querySelector('.box') // 純文字內容 (保留空白) element.textContent = 'Hello' console.log(element.textContent) // 純文字內容 (過濾多餘空白) element.innerText = 'Hello' console.log(element.innerText) // HTML 內容 (內部) element.innerHTML = '<span>Hello</span>' console.log(element.innerHTML) // HTML 內容 (包含元素本身) element.outerHTML = '<div class="new">Hello</div>' console.log(element.outerHTML) ``` **差異比較:** | 屬性 | 讀取內容 | 設定內容 | 效能 | 常見用途 | |------|---------|---------|------|---------| | `textContent` | 所有文字(含隱藏) | 純文字 | ⚡ 快 | 純文字操作 | | `innerText` | 可見文字 | 純文字 | 🐢 慢 | 顯示文字 | | `innerHTML` | HTML 標籤 | HTML | ⚡ 中 | 插入 HTML | | `outerHTML` | 含元素本身 | 替換元素 | ⚡ 中 | 替換整個元素 | ### 屬性操作 ```javascript // 取得屬性 element.getAttribute('data-id') element.id // 直接存取 element.className // 取得 class 字串 element.classList // 取得 class 列表 // 設定屬性 element.setAttribute('data-id', '123') element.id = 'myId' // 移除屬性 element.removeAttribute('data-id') // 判斷是否有屬性 element.hasAttribute('data-id') // 返回 boolean ``` ### Class 操作 ```javascript // 取得 class element.className // 字串格式 element.classList // DOMTokenList 格式 // 新增 class element.classList.add('active') element.classList.add('class1', 'class2') // 多個 // 移除 class element.classList.remove('active') // 切換 class (有就移除,沒有就新增) element.classList.toggle('active') // 判斷是否有特定 class element.classList.contains('active') // 返回 boolean // 替換 class element.classList.replace('old', 'new') ``` ### 樣式操作 ```javascript // 設定單一樣式 element.style.color = 'red' element.style.backgroundColor = 'blue' // 駝峰式命名 // 設定多個樣式 element.style.cssText = 'color: red; background: blue;' // 設定樣式 (含權重) element.style.setProperty('color', 'red', 'important') // 取得樣式 element.style.color // 取得計算後的樣式 (包含 CSS 檔案的樣式) const styles = window.getComputedStyle(element) console.log(styles.color) ``` ### 常用屬性快捷操作 ```javascript const img = document.querySelector('img') // 圖片 img.src = 'image.jpg' img.alt = '圖片說明' // 連結 link.href = 'https://example.com' link.target = '_blank' // 標題 element.title = '提示文字' ``` --- ## DOM 節點查找 ### 向上查找 (父層) ```javascript // 找到最近的父元素 element.parentElement // 找到符合選擇器的最近父元素 element.closest('.parent') // 往上找到第一個符合的元素 ``` ### 向下查找 (子層) ```javascript // 找所有子元素 (只找第一層) element.children // 返回 HTMLCollection // 找第一個子元素 element.firstElementChild // 找最後一個子元素 element.lastElementChild // 用選擇器找 (找所有子孫層) element.querySelector('.child') element.querySelectorAll('.child') ``` ### 橫向查找 (兄弟元素) ```javascript // 前一個兄弟元素 element.previousElementSibling // 後一個兄弟元素 element.nextElementSibling ``` **節點關係圖:** ``` parentElement ↑ | previousSibling ← element → nextSibling | ↓ children ``` --- ## 表單操作 ### 取得表單值 #### 文字輸入框 ```javascript const input = document.querySelector('input[type="text"]') // 取得值 console.log(input.value) // 設定值 input.value = 'Hello' ``` #### 文字區域 ```javascript const textarea = document.querySelector('textarea') // 取得值 console.log(textarea.value) // 設定值 (換行使用 \n) textarea.value = '第一行\n第二行' ``` #### 下拉選單 ```javascript const select = document.querySelector('select') // 取得選中的值 console.log(select.value) // 設定選中項 (透過 value) select.value = 'option2' // 取得選中的文字 const selectedOption = select.options[select.selectedIndex] console.log(selectedOption.text) ``` #### 單選框 (Radio) ```javascript // 取得選中的值 const selected = document.querySelector('input[name="gender"]:checked') console.log(selected ? selected.value : null) // 設定選中 const radio = document.querySelector('input[value="male"]') radio.checked = true ``` #### 複選框 (Checkbox) ```javascript // 取得所有選中的值 const checkboxes = document.querySelectorAll('input[name="hobby"]:checked') const values = Array.from(checkboxes).map(cb => cb.value) console.log(values) // 設定勾選 const checkbox = document.querySelector('input[value="reading"]') checkbox.checked = true // 判斷是否勾選 console.log(checkbox.checked) // true 或 false ``` ### 表單驗證 ```javascript const form = document.querySelector('form') form.addEventListener('submit', function(e) { e.preventDefault() // 阻止提交 const input = document.querySelector('input[name="email"]') // 檢查是否為空 if (!input.value.trim()) { alert('請輸入 Email') return } // Email 驗證 const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!emailPattern.test(input.value)) { alert('Email 格式錯誤') return } // 通過驗證,可以提交 console.log('驗證通過') }) ``` ### 表單狀態控制 ```javascript const input = document.querySelector('input') // 禁用 input.disabled = true // 啟用 input.disabled = false // 唯讀 input.readOnly = true // 必填 input.required = true ``` --- ## 實用範例 ### 範例 1: 動態新增待辦事項 ```javascript const input = document.querySelector('#todo-input') const btn = document.querySelector('#add-btn') const list = document.querySelector('#todo-list') btn.addEventListener('click', function() { if (!input.value.trim()) return // 建立新項目 const li = document.createElement('li') li.textContent = input.value // 建立刪除按鈕 const deleteBtn = document.createElement('button') deleteBtn.textContent = '刪除' deleteBtn.addEventListener('click', function() { li.remove() }) li.appendChild(deleteBtn) list.appendChild(li) // 清空輸入框 input.value = '' }) ``` ### 範例 2: 滾動到頂部按鈕 ```javascript const scrollBtn = document.querySelector('#scroll-to-top') // 滾動時顯示/隱藏按鈕 window.addEventListener('scroll', function() { if (window.scrollY > 300) { scrollBtn.style.display = 'block' } else { scrollBtn.style.display = 'none' } }) // 點擊回到頂部 scrollBtn.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }) }) ``` ### 範例 3: 圖片懶加載 ```javascript const images = document.querySelectorAll('img[data-src]') const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target img.src = img.dataset.src img.removeAttribute('data-src') imageObserver.unobserve(img) } }) }) images.forEach(img => imageObserver.observe(img)) ``` --- ## 效能優化建議 1. **減少 DOM 操作次數** ```javascript // ❌ 不好 - 多次操作 DOM for (let i = 0; i < 1000; i++) { list.innerHTML += `<li>${i}</li>` } // ✅ 好 - 一次操作 let html = '' for (let i = 0; i < 1000; i++) { html += `<li>${i}</li>` } list.innerHTML = html ``` 2. **使用事件委派** ```javascript // ❌ 不好 - 為每個按鈕綁定事件 buttons.forEach(btn => { btn.addEventListener('click', handler) }) // ✅ 好 - 只綁定一次 container.addEventListener('click', function(e) { if (e.target.matches('button')) { handler(e) } }) ``` 3. **快取 DOM 查詢** ```javascript // ❌ 不好 - 重複查詢 document.querySelector('.box').style.color = 'red' document.querySelector('.box').style.background = 'blue' // ✅ 好 - 快取元素 const box = document.querySelector('.box') box.style.color = 'red' box.style.background = 'blue' ``` --- ## 常見問題 ### Q: `querySelector` vs `getElementById` 哪個快? A: `getElementById` 較快,但差異很小。現代開發建議統一使用 `querySelector` 系列方法,因為更靈活。 ### Q: 什麼時候用 `innerHTML` vs `textContent`? A: - 插入 HTML 標籤時用 `innerHTML` - 插入純文字時用 `textContent` (更安全,防止 XSS 攻擊) ### Q: `addEventListener` 可以重複綁定嗎? A: 可以,同一事件可以綁定多個不同的處理函數,會依序執行。 --- ## 參考資源 - [MDN - DOM 文檔](https://developer.mozilla.org/zh-TW/docs/Web/API/Document_Object_Model) - [MDN - Event 參考](https://developer.mozilla.org/zh-TW/docs/Web/Events) - [JavaScript.info - DOM](https://javascript.info/document)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully