0%

[week 7] DOM 介面、瀏覽器事件處理

本篇為 [FE102] 前端必備:JavaScript 這門課程的學習筆記。如有錯誤歡迎指正。

學習目標:

 P1 你知道 JavaScript 跑在網頁上跟跑在 Node.js 上差在哪裡
 P1 你知道 DOM 是什麼
 P1 你知道如何用 JavaScript 操控 DOM 物件
 P1 你知道如何幫一個按鈕加上 event listener

當我們運用 JavaScript 在網頁進行操作時,主要可分為下列三大面向:

  1. 介面(Interface):如何改變介面
  2. 事件(Event):如何監聽事件並做出反應
  3. 資料(Data):如何和伺服器交換資料

以下主要探討「改變介面」與「事件監聽」的部分。

JavaScript 與瀏覽器的溝通

那麼,該把 <script> 標籤放在哪呢?首先要瞭解,我們可以放在 HTML 檔案的任何位置,通常會有兩種回答:

  1. 放在 <head></head> 之間
  2. 放在 </body> 之前

由於瀏覽器是由上而下渲染,相較之下以第二種方法較佳。但更好的做法,是另外新增 js 檔,以外連檔的形式執行 JS,更有助於管理與維護。

<head>
  <title>This is title.</title>
  <script src="./main.js"></script>
</head>

執行環境

我們可以在 Node.js 或瀏覽器執行 JS。由於兩者是不同的執行環境,在語法支援度也有些微差異。

例如:

  1. require() 引入模組的語法,只能在 Node.js上執行
  2. alert() 叫出提示窗,只能在瀏覽器上執行

DOM 是什麼?

DOM,全名是 Document Object Model(文件物件模型)。

是由「瀏覽器」提供用來和「程式語言」溝通的橋樑。最常被用在網頁與 JavaScript 的溝通。

DOM 類似於把 HTML 的文件(Document)轉成物件(Object)。JavaScript 即可透過 DOM 提供的 API 來存取並操作 HTML。

DOM

如何選取 DOM 元素

document 是瀏覽器提供的特殊物件,內部放著許多 function。而我們可以透過「物件」的方式去呼叫想要的元素:

  • 根據 Tag 名稱選取:document.getElementsByTagName()
  • 根據 Class 名稱選取:document.getElementsByClassName()
  • 根據 ID 名稱選取:document.getElementById()
  • 選取 CSS 選擇器:document.querySelector()

document.getElementsByTagName('div'),選取所有 tag 名稱是 div 元素為範例:

<body>
  <div>
    This is tagName.
    <div class="box">This is className.</div>
    <div id="name">This is idName.</div>
  </div>

  <script>
    const elements = document.getElementsByTagName('div')
    console.log(elements)
  </script>
</body>

在瀏覽器運行結果,會得到類陣列如下:

Tag

若改選取 elements[1],即可選取類陣列中的元素:

類陣列

若根據 Class 和 ID 名稱選取,也是類似用法:

  • document.getElementsByClassName('box'):括弧內不需加 . 即可表示 class

Class

  • document.getElementById('name'):注意 ID 只會有一個

Id

相較於前面三種選取方式,又以 querySelector 更方便且直覺。就像寫 CSS 選擇器來選取元素:

  • 選取標籤:const element = document.querySelector('div')
  • 選取 class:const element = document.querySelector('.box')
  • 選取 id:const element = document.querySelector('#name')

querySelector() 只會選取第一個匹配的元素,舉例如下:

<body>

  <a href="#">1st</a>

  <div>
    <a href="#">2rd</a>
  </div>

  <script>
    const element = document.querySelector('a')
    console.log(element)
  </script>
</body>

結果只會選取第一個 <a>

querySelector

若想選取到所有匹配到的元素,可使用 querySelectorAll()

const element = document.querySelectorAll('a')

同樣能夠得到類陣列:

querySelectorAll

改變元素的 CSS:.style

  • 直接寫上行內樣式
const element = document.querySelector('.box')

element.style.background = 'red';
element.style.paddingTop = '10px';         // 駝峰式
element.style["padding-top"] = '10px';     // 字串
  • 但這種寫法其實不易修改,因此並不推薦。通常會寫好另外的 class,藉由「改變元素的 class」來套用該狀態,也就是下面要介紹的方法。

改變元素的 Class:.classList

  • .classList.add():增加
  • .classList.remove():移除
  • .classList.toggle():開關(有就刪、沒有就加)
  • .classList.contains():判斷是否包含該 class
<style>
.active { 
  background: red;
}
</style>
element.classList.add('active')
<!-- 將元素套用 active 這個 class 的樣式 -->

改變元素的內容

  • .innerText:只抓取標籤內的文字。最常用來改變文字內容。
<div>
  <div class="box">This is <span>class</span>.</div>
  <div id="name">This is id.</div>
</div>

<script>
  const element = document.querySelector('.box')
  console.log(element.innerText)
</script>
<!-- 印出:This is class. -->
  • .innerHTML:抓取標籤內的所有內容,包含 HTML 標籤。
<div>
  <div class="box">This is <span>class</span>.</div>
  <div id="name">This is id.</div>
</div>

<script>
  const element = document.querySelector('.box')
  console.log(element.innerHTML)
</script>
<!-- 印出:This is <span>class</span>. -->
  • .outerHTML:抓取整個元素,可用來重新設置元素本身。較少使用。
<div class="block">
  <div class="box">This is <span>class</span>.</div>
  <div id="name">This is id.</div>
</div>

<script>
  const element = document.querySelector('.box')
  console.log(element.outerHTML)
</script>
<!-- 印出:<div class="box">This is <span>class</span>.</div> -->

刪除與插入元素:removechild 與 appendChild

  • .removechild():移除子元素
<div class="block">
  <div class="box">This is <span>class</span>.</div>
  <a>This is a.</a>
</div>

<script>
  const element = document.querySelector('.block')
  element.removeChild(document.querySelector('a'))
</script>
<!-- 成功刪除在 block 元素底下,標籤為 a 的元素 -->
  • .appendChild():插入元素在最後方
  1. 插入元素之前,要先建立元素或文字:
    • const newElement = document.createElement('h2')
    • const newTextElement = document.createTextNode('Hello World')
  2. 增加節點:
    • element.appendChild(newElement)
    • element.appendChild(newTextElement)

參考資料:

  1. 前端基礎JavaScript篇:JavaScript 與瀏覽器的溝通| by Hugh’s Program learning
  2. JavaScript入門系列:BOM和DOM筆記| 快樂學程式
  3. HTML DOM 元素

JavaScript 網頁事件處理

JavaScript 是以事件驅動(Event-driven)的程式語言。

也就是說,當 JS 被瀏覽器載入後並不會馬上執行,而是透過使用者操作來觸發事件,才會啟動對應程式。例如:滑鼠點擊、鍵盤輸入等。

可參考:HTML DOM Event 對象列表

監聽事件 addEventListener()

在處理事件時,通常會需要指派監聽者(Event listeners)來監聽事件觸發。

也就是監測 DOM 中的某一元素,當使用者觸發某事件時,就會執行後續動作,語法如下:

element.addEventListener('event', function, useCapture)

參數值說明

1. event 事件

  • 必須
  • 指定事件類型
  • 常見的事件類別:(詳見 MDN
    • 滑鼠相關:click、mousedown、mouseenter、mouseleave
    • 鍵盤相關:keydown、keypress、keyup
    • 瀏覽器相關:scroll、resize

2. function 功能

  • 必須
  • 指定事件觸發時執行的函式

而 function 的部分有下列兩種寫法,以最常見的 click 做舉例:

  • 直接將函式作為參數帶入:回呼函式(callback function)
const element = document.querySelector('.box')
// 先註冊一個事件
element.addEventListener('click', onClick())

// 等事件被觸發才執行 callback function
function onClick(){     
   alert('click!')
}
  • 匿名函式(anonymous)
const element = document.querySelector('.box')

element.addEventListener('click', function(){
   alert('click!')
})

3. useCapture

  • 可選
  • 布林值,指定事件在捕獲或冒泡階段執行
    1. true:補獲
    2. false:冒泡(預設值)

參考資料:

  1. 重新認識 JavaScript: Day 14 事件機制的原理
  2. HTML DOM addEventListener()方法

事件資訊 event(e)

一個事件的發生會包含各種資訊。event 資訊會放在 callback function 的第一個參數。通常取名 event 或簡寫 e,可以當作是一個「物件」,裡面放著關於此事件的參數值。

以 click 事件為例:

const element = document.querySelector('.box')

element.addEventListener('click', function(e) {
  console.log(e)
})

點擊元素後會出現下列資訊:

event

我們可以用「物件」的方式來取得需要的元素,以常用的事件資訊為例。

常用的事件資訊

click 事件:點擊 DOM 元素時觸發

  • e.target:點擊到的元素
  • e.screenX:滑鼠離視窗左邊的距離
  • e.screenY:滑鼠離視窗上邊的距離

keydown 事件:按下鍵盤時觸發

  • e.key:按鍵號碼

submit 事件:提交表單前觸發,通常用來驗證表單內容

首先建立一個簡易表單:

<form class="login-form">
  <div class="account">
    Account: <input name="account" type="text">
  </div>
  <div class="password">
    Password: <input name="password" type="password">
  </div>
  <div class="password2">
    Password again: <input name="password2" type="password">
  </div>
  <div class="submit">
    <input name="submit" type="submit" value="Submit">
  </div>
</form>

form

點選表單中的 submit 按鈕後,會以預設方法 GET 送出資料,也就是把參數帶入原網址送出。而 submit 事件是在表單送出前觸發,通常用來驗證表單內容。

以點選 submit 後彈出視窗為例:

  const element = document.querySelector('.login-form')
  element.addEventListener('submit', function () {
    alert("成功")
})

阻止預設行為 e.preventDefault()

用來阻止瀏覽器上特定元素的預設行為。以下為常見使用方式:

  • <form> 的 submit 事件:阻止送出表單
  • <a> 的 click 事件:阻止轉址
  • <input> 的 keypress 事件:阻止輸入按鍵

以上述例子來說,當 Password 跟 Password again 輸入的值不同時,就可使用 e.preventDefault() 來阻止表單送出:

<script>
  const element = document.querySelector('.login-form');

  element.addEventListener('submit', function(e) {
    const pw1 = document.querySelector('input[name="password1"]');
    const pw2 = document.querySelector('input[name="password2"]');
    // 若密碼不同,就不送出表單
    if (pw1.value !== pw2.value) {
      alert('密碼不同!')
      e.preventDefault();
    } 
  });
</script>

參考資料:

  1. [第七週] DOM - 操作 DOM 介面、事件監聽 - Yakim
  2. [第八週]DOM — 瀏覽器事件處理 - Miahsu