Skip to main content

Document

Browser environment, specs

ภาษา JavaScript ถูกสร้างมาเพื่อใช้งานกับ web browsers.

ตั้งแต่นั้นเป็นต้นมา ภาษานี้ได้พัฒนาเป็นภาษาที่มีการใช้งานและแพลตฟอร์มมากมาย.

แพลตฟอร์มที่กล่าวถึง อาจเป็น Browser, web-server, host หรือตู้กาแฟอัจฉริยะ ก็สามารถใช้ JavaScript ได้. โดยแพลตฟอร์มแต่ละอัน ก็มีฟังก์ชั่นการใช้งานที่แตกต่างกันไป และ จะถูกกำหนดด้วย ข้อกำหนดของ JavaScript หรือเรียกว่า host environment

โดย host environment ก็จะจัดเตรียมฟังก์ชั่น ที่นอกเหนือจากภาษาของมัน เพื่อใช้สำหรับแพลตฟอร์มต่าง ๆ

เช่น Web browsers ก็จะมีฟังก์ชั่นที่ใช้ควบคุมหน้าเว็บ, Node.js ก็จะมีฟังก์ชั่นสำหรับ Server และอื่น ๆ

และนี่คือมุมมองจากด้านบน เวลาเรามอง JavaScript run ใน Web-browser

image.png

JavaScript จะมี Object ที่เป็นพื้นฐานของทุกอย่าง เรียกว่า window มีหน้าที่หลักอยู่ 2 อย่างคือ

  1. เป็น Global Object ของ JavaScript
  2. จะแสดงถึง "browser window" และจะมี method เพื่อควบคุมมัน

ตัวอย่างเช่น เราสามารถใช้ฟังก์ชั่น เหมือนการเรียกใช้ method ผ่าน window ได้:

function sayHi() { 
alert("Hello");
}

// global functions are methods of the global object:
window.sayHi();

และเราสามารถใช้ window เพื่อแสดงความสูงของ Web-browser ได้

alert(window.innerHeight); // inner window height

และยังมี method อื่น ๆ อีก ที่เราจะมาสอนกันทีหลัง


DOM (Document Object Model)

Document Object Model หรือ DOM แสดงถึง Content ของหน้าทั้งหมด ออกมาในรูปแบบของ Object ที่สามารถแก้ไขได้

ตัวอย่างเช่น:

// change the background color to red 
document.body.style.background = "red";
// change it back after 1 second
setTimeout(() => document.body.style.background = "", 1000);

เราใช้ document.body.style ในการตกแต่งพื้นหลัง แต่ยังมี method หรือ property อีกมาก ที่เราจะค่อยพูดถึงกันในตอนต่อ ๆ ไป


BOM (Browser Object Model)

Browser Object Model (BOM) แสดงถึง Object เพิ่มเติมที่เบราว์เซอร์ (host environment) มอบให้สำหรับการทำงานกับทุกสิ่ง ยกเว้นกับ Document

ตัวอย่างเช่น:

  1. Object Navigator จะให้ข้อมูลพื้นหลังของ browser และ ระบบปฏิบัติการ (operating system) โดยมีหลาย Properties, แต่มี 2 ตัวที่ใช้กันมาก คือ navigator.userAgent - เกี่ยวกับ browser ที่ใช้อยู่ และ navigator.platform - บอกเกี่ยวกับ แพลตฟอร์ม (Windows/Linux/Mac etc.)
  2. Object Location อนุญาตให้เราเข้าถึง URL ปัจจุบัน และ redirect ไปที่ URL ใหม่ได้

วิธีที่เราสามารถใช้ Object location ได้:

alert(location.href); // shows current URL
if (confirm("Go to Wikipedia?")) {
location.href = "https://wikipedia.org"; // redirect the browser to another URL
}

ฟังก์ชั่น alert/confirm/prompt ก็เป็นส่วนหนึ่งของ BOM เหมือนกัน


Summary

พื้นฐานที่เรามี:

DOM specification

อธิบายเกี่ยวกับ โครงสร้างของ Document, การควบคุม และ event https://dom.spec.whatwg.org.

CSSOM specification

อธิบายเกี่ยวกับ stylesheets และ style rules, การควบคุมการตกแต่ง https://www.w3.org/TR/cssom-1/

HTML specification

อธิบายเกี่ยวกับ ภาษา HTML (tags etc) และ BOM (broswser object model) - ฟังก์ชั่นของ broswer

setTimeout, alert, location https://html.spec.whatwg.org. เราสามารถดู properties และ methods

ของ BOM ได้ที่นี่


Walking the DOM

DOM อนุญาตให้เราทำทุกสิ่งกับ element และ Contents ได้,

แต่สิ่งแรกที่เราต้องทำคือ เข้าถึงไปที่ Object DOM

ทุกการ Operations บน DOM เริ่มจาก Object document , ซึ่งเป็นจุดหลักในการเข้าถึงไปที่ DOM

จาก document เราสามารถเข้าถึงได้ทุก Node

นี่คือรูปของ link ที่เชื่อมกันระหว่าง Node:

image.png

ด้านบน: DocumentElement และ Body

Node บนสุด สามารถใช้ได้ผ่าน Object document :

<html> = document.documentElement

บนสุดของ Node document คือ document.documentElement นั่นคือ DOM node ของ แท็ก <html>

<body> = document.body

DOM Node อีกตัวหนึ่ง ที่ถูกใช้บ่อยคือ <body> เข้าถึงโดย– document.body.

<head> = document.head

แท็ก<head> เข้าถึงโดย document.head.


Children: childNodes, firstChild, lastChild

มีสองคำที่เราจะใช้ต่อจากนี้

  • Child nodes (or children) – element ที่เป็นลูกโดยตรง กล่าวอีกแบบหนึ่งพวกมันซ้อนกันอยู่ ตัวอย่างเช่น <head> และ <body> เป็นลูกขององค์ประกอบ <html>
  • Descendants – element ทั้งหมดที่ซ้อนอยู่ในองค์ประกอบที่กำหนด รวมถึง children, children ของมันและอื่น ๆ

ตัวอย่างเช่น แท็ก <body> มี children <div> และ<ul> (และข้อความ):

<html>
<body>
<div>Begin</div>

<ul>
<li>
<b>Information</b>
</li>
</ul>
</body>
</html>

และ descendants ของ <body> ไม่เพียงแต่เป็น Children โดยตรงของ <div><ul> แต่ยังรวมถึง element ที่ซ้อนกันลึกกว่าด้วย, เช่น <li> (child ของ  <ul>) และ<b> (child ของ <li>)

คอลเลกชัน childNodes แสดงรายการ Node ย่อยทั้งหมด รวมถึง Node ข้อความ ตัวอย่างด้านล่างแสดง children ของ document.body

<html>
<body>
<div>Begin</div>

<ul>
<li>Information</li>
</ul>

<div>End</div>

<script>
for (let i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
</script>
...more stuff...
</body>
</html>

Properties firstChild and lastChild 

ห้เราเข้าถึง children ของตัวแรกและตัวท้ายได้ พวกมันคือ shorthands. ถ้า Node นั้นมีค่าจริง ดังนั้นมันจะเข้าถึงได้ตลอด

elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild

ฟังก์ชั่นพิเศษ elem.hasChildNodes() เพื่อตรวจสอบว่ามี child nodes อยู่หรือไม่


DOM collections

อย่างที่เราเห็น childNodes เป็นเหมือนกัน Array แต่จริง ๆ แล้วมันไม่ใช่ Array แต่มันเป็น collection เป็น array แบบพิเศษ (iterable object)

มีผลกระทบที่สำคัญสองประการ:

  1. เราสามารถใช้ for..of เพื่อลูบเช็คได้

    for (let node of document.body.childNodes) {
    alert(node); // shows all nodes from the collection
    }

    เพราะว่ามันเป็น iterable

  2. Array methods ไม่สามารถใช้งานได้ เพราะว่ามันไม่ใช่ Array

    alert(document.body.childNodes.filter); // undefined (there's no filter method!)

    เพราะเราสามารถใช้ Array.from เพื่อสร้าง Array ของจริงได้จากคอลเลกชันได้ ถ้าเราต้องการใช้ Array methods

    alert( Array.from(document.body.childNodes).filter ); // function

Siblings and the parent

Siblings คือ Node ที่มี children เดียวกัน

ตัวอย่างเช่น, <head> และ <body> เป็น siblings:

<html>
<head>...</head><body>...</body>
</html>
  • <body> ให้พูดคือ “ถัดไป” หรือ “ขวา” sibling ของ <head>,
  • <head> ให้พูดคือ “ก่อน” or “ซ้าย” sibling ของ<body>.

sibling ถัดไปจะอยู่ใน property nextSibling และ sibling ก่อนหน้า จะอยู่ใน previousSibling

และ parent จะเรียกเข้าถึงได้โดยใช้ parentNode

ตัวอย่างเช่น:

// parent of <body> is <html>
alert( document.body.parentNode === document.documentElement ); // true

// after <head> goes <body>
alert( document.head.nextSibling ); // HTMLBodyElement

// before <body> goes <head>
alert( document.body.previousSibling ); // HTMLHeadElement

More links: tables

จนถึงตอนนี้เราได้อธิบายคุณสมบัติ navigation ขั้นพื้นฐานแล้ว

elements DOM บางประเภทอาจให้ properties เพิ่มเติมตามประเภทเพื่อความสะดวก

Tables เป็นตัวอย่างที่ดีในเรื่องนี้ :

element <Table> ซัพพอร์ต properties พวกนี้

  • table.rows – collection ของ elements  <tr> ใน table.
  • table.caption/tHead/tFoot – อ้างอิงถึง elements <caption><thead><tfoot>.
  • table.tBodies – collection ของ element <tbody

<tr>:

  • tr.cells – collection ของ <td> และ <th>  ภายใน element <tr>.
  • tr.sectionRowIndex – ตำแหน่งของ (index) <tr> ภายใน <thead>/<tbody>/<tfoot>.
  • tr.rowIndex – จำนวนของ <tr> ทั้งหมดใน table (รวม table rows ทั้งหมด).

<td> และ<th>:

  • td.cellIndex – จำนวนทั้งหมดของ<td> และ <th> ภายใน  <tr>.

ตัวอย่างการใช้งาน:

<table id="table">
<tr>
<td>one</td><td>two</td>
</tr>
<tr>
<td>three</td><td>four</td>
</tr>
</table>

<script>
// get td with "two" (first row, second column)
let td = table.rows[0].cells[1];
td.style.backgroundColor = "red"; // highlight it
</script>

Summary

ด้วย Node DOM เราสามารถไปที่ neighbors ใกล้เคียงได้โดยใช้ navigation properties

มี 2 ส่วนหลัก ๆ:

  • สำหรับ Node ทั้งหมด: parentNodechildNodesfirstChildlastChildpreviousSiblingnextSibling.
  • สำหรับ element Node: parentElementchildrenfirstElementChildlastElementChildpreviousElementSiblingnextElementSibling.

Searching: getElement, querySelector**

DOM navigation properties ใช้งานได้ดีก็ต่อเมื่อ element อยู่ใกล้ ๆ กัน แต่ถ้าไม่เราจะสามารถเข้าถึง element ตัวนั้นได้อย่างไร


document.getElementById or just id

ถ้า element นั้น มี id attribute เราสามารถใช้ method document.getElementById(id) ไม่สำคัญว่ามันจะอยู่ที่ตรงไหน

ตัวอย่างเช่น:


<div id="elem">
<div id="elem-content">Element</div>
</div>

<script>
// get the element
let elem = document.getElementById('elem');

// make its background red
elem.style.background = 'red';
</script>

นอกจากนี้ยังมีตัวแปร global ที่ตั้งชื่อตาม id ที่อ้างอิงถึง element

<div id="elem">
<div id="elem-content">Element</div>
</div>

<script>
// elem is a reference to DOM-element with id="elem"
elem.style.background = 'red';

// id="elem-content" has a hyphen inside, so it can't be a variable name
// ...but we can access it using square brackets: window['elem-content']
</script>

แต่ถ้าเราประกาศตัวแปรไว้ด้วยชื่อเดียวกัน มันจะเอาตัวที่ประกาศไว้

<div id="elem"></div>

<script>
let elem = 5; // now elem is 5, not a reference to <div id="elem">

alert(elem); // 5
</script>

querySelectorAll

method, elem.querySelectorAll(css) จะรีเทิร์น element ทั้งหมดภายใน elem ที่ macth กับ CSS selector

ตัวอย่าง เรากำลังมองหา <li> ทั้งหมดที่เป็น last children:

<ul>
<li>The</li>
<li>test</li>
</ul>
<ul>
<li>has</li>
<li>passed</li>
</ul>
<script>
let elements = document.querySelectorAll('ul > li:last-child');

for (let elem of elements) {
alert(elem.innerHTML); // "test", "passed"
}
</script>

querySelector

elem.querySelector(css) คือค่า element ตัวแรกที่ตรงตามกับ CSS selector

อีกนัยหนึ่ง คือ ผลลัพธ์จะเหมือนกับ elem.querySelectorAll(css)[0], แต่ method นี้จะหา element ทั้งหมดออกมา และเลือกแค่อันเดียว ในขณะที่ elem.querySelector(css) จะแค่ตัวเดียว ดังนั้นการใช้ elem.querySelector(css) จะเร็วกว่า


matches

method ก่อนหน้านี้ใช้หา DOM แต่ elem.matches(css) ไม่ได้หาอะไรเลย แต่ว่าใช้เช็คว่า element นั้น มี CSS-selector ตรงกันหรือไม่ และจะรีเทิร์นค่า true หรือ false มา

ตัวอย่างเช่น:

<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
// can be any collection instead of document.body.children
for (let elem of document.body.children) {
if (elem.matches('a[href$="zip"]')) {
alert("The archive reference: " + elem.href );
}
}
</script>

closest

Ancestors ของ element คือ: parent, parent ของ parent, และ ต่อไป

method elem.closest(css) จะมองหา Ancestors ที่ใกล้ที่สุด ที่ตรงกับ CSS-selector. elemก็รวมอยู่ในการ search ด้วย

method closest จะขึ้นไปเรื่อย ๆ และเช็ค parents แต่ละตัว ถ้ามันตรงกับ selector, ancestor จะ return ออกมา

ตัวอย่างเช่น:

<h1>Contents</h1>

<div class="contents">
<ul class="book">
<li class="chapter">Chapter 1</li>
<li class="chapter">Chapter 2</li>
</ul>
</div>

<script>
let chapter = document.querySelector('.chapter'); // LI

alert(chapter.closest('.book')); // UL
alert(chapter.closest('.contents')); // DIV

alert(chapter.closest('h1')); // null (because h1 is not an ancestor)
</script>

getElementsBy*

ยังมี methods อื่น ๆ อีก ที่หา nodes โดยการใช้ tag, class, etc.

เช่น:

  • elem.getElementsByTagName(tag) มองหา element ที่มี tag ที่ให้ไว้ และรีเทิร์น collection ที่ให้ไว้, tag parameter สามารถใช้ star * ได้ เพื่อเลือกแท็กอะไรก็ได้
  • elem.getElementsByClassName(className) จะรีเทิร์น element ที่ให้ตาม CSS class
  • document.getElementsByName(name) รีเทิร์น element ที่มี attribute name เดียวกับที่ให้ไป
// get all divs in the document
let divs = document.getElementsByTagName('div');

เรามาหา tag input ภายใน table กันเถอะ

<table id="table">
<tr>
<td>Your age:</td>

<td>
<label>
<input type="radio" name="age" value="young" checked> less than 18
</label>
<label>
<input type="radio" name="age" value="mature"> from 18 to 50
</label>
<label>
<input type="radio" name="age" value="senior"> more than 60
</label>
</td>
</tr>
</table>

<script>
let inputs = table.getElementsByTagName('input');

for (let input of inputs) {
alert( input.value + ': ' + input.checked );
}
</script>

การหา .article element:

<form name="my-form">
<div class="article">Article</div>
<div class="long article">Long article</div>
</form>

<script>
// find by name attribute
let form = document.getElementsByName('my-form')[0];

// find by class inside the form
let articles = form.getElementsByClassName('article');
alert(articles.length); // 2, found two elements with class "article"
</script>

Live collections

ทุก method ที่เป็น "getElementBy*" จะรีเทิร์น live collection โดย collection ดังกล่าว reflect ถึงสถานะปัจจุบันของ document และ "auto-update" เสมอเมื่อมีการเปลี่ยนแปลง

ในตัวอย่างข้างล่าง จะมี 2 script

  1. สคริปต์แรก จะการอ้างอิงไปถึง collection <div> เมื่อเพิ่ม <div> ค่า length ไม่เปลี่ยน
  2. สคริปต์ที่สอง เมื่อรันแล้วเจอ <div> อีกตัว ค่า length จะเปลี่ยน
<div>First div</div>

<script>
let divs = document.getElementsByTagName('div');
alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
alert(divs.length); // 2
</script>

ในทางตรงกันข้าม querySelectorAll  จะคืนค่า static collection เหมือนกัน Array ที่ไม่เปลี่ยนแปลง

<div>First div</div>

<script>
let divs = document.querySelectorAll('div');
alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
alert(divs.length); // 1
</script>

Summary

มี 6 method หลักที่ใช้ค้นหา node ใน DOM

Method


querySelector

querySelectorAll

getElementById

getElementsByName

getElementsByTagName

getElementsByClassName

Searches by…


CSS-selector

CSS-selector

id

name

tag or '*'

class

Can call on an element?


Live?


เรามากล่าวถึง method สุดท้ายกันใช้สำหรับเช็คความสัมพันธ์ child-parent

  • elemA.contains(elemB) จะรีเทิร์น true ถ้า  elemB อยู่ใน element elemA หรือเมื่อ  elemA==elemB.

Node properties: type, tag and contents

มาดู Node DOM ในเชิงลึกมากขึ้นกัน ในบทนี้เราจะมาดูกันว่ามันคืออะไรและเรียนรู้ properties ที่ใช้บ่อยที่สุด


DOM node classes

Node DOM ที่แตกต่างกันอาจมี properties ที่แตกต่างกัน ตัวอย่างเช่น Node element ที่สอดคล้องกับแท็ก

<a> มี properties ที่เกี่ยวข้องกับ link เป็นต้น, และอันที่สอดคล้องกับ <input> มี properties ที่เกี่ยวข้องกับ input เป็นต้น

Node DOM แต่ละตัวก็จะมี built-in class อยู่ภายใน

รากของลำดับชั้นทั้งหมดคือ  EventTarget ที่สามารถเข้าถึงได้จาก Node

รูปสำหรับคำอธิบาย:

image.png

Class หลัก ๆ:

  • EventTarget - เป็น root “abstract” class ของทุกอย่าง

    Object ของ Class นี้ไม่ได้ถูกสร้าง มันทำหน้าที่เป็นฐาน ดังนั้น Node DOM ทั้งหมดจึงรองรับสิ่งที่เรียกว่า "Events" เราจะศึกษามันในภายหลัง

  • Node - ก็เป็น “abstract” class เหมือนกัน ทำหน้าที่เป็นฐานสำหรับ Node DOM

    มันมีฟังก์ชันการทำงานหลักของมันเช่น parentNodenextSiblingchildNodes

    Object ของ Class Node ไม่ได้ถูกสร้างขึ้น แต่มี Class อื่นที่สืบทอดมาจากมัน

  • Document, สำหรับเหตุผลทางประวัติศาสตร์ มันจะเข้าถึงโดยใช้ HTMLDocument

    document ที่เป็น global Object ก็อยู่ใน Class นี้เหมือนกัน, มันทำหน้าที่ เข้าถึง DOM

  • CharacterData – เป็น “abstract” Class, ถูกสืบทอดโดย:

    • Text – class นี้ แสดงถึง text ที่อยู่ข้างใน เช่น  Hello in <p>Hello</p>.
    • Comment – class นี้ใช้กับ comment. พวกมันจะไม่ถูกแสดง, แต่ comment แต่ละอัน จะมาเป็นสมาชิกของ DOM
  • Element – เป็นฐานของ DOM elements มันมีหน้าที่ navigate ไปถึง element อื่น ๆ เช่น nextElementSiblingchildren และ searching method เช่นgetElementsByTagNamequerySelector

  • สุดท้าย HTMLElement เป็นพื้นฐานของ element ทั้งหมด มันสืบทอดมาจาก element HTML

มีแท็กอื่นๆ อีกมากมายที่มี class เป็นของตัวเองซึ่งอาจมี properties และ method เฉพาะ ในขณะที่ element บางอย่าง เช่น <span>, <section>, <article> ไม่มี properties เฉพาะใดๆ

ตัวอย่างเช่น, ลองมาพิจารณา DOM object สำหรับ <input> element.

เฉลย มันอยู่กับ class HTMLInputElement .

วิธีที่จะดูชื่อของ DOM node class เราสามารถเรียกใช้ Object ที่มี Constructor property ที่อ้างอิงไปถึง class contructor และ ชื่อของมันคือ constructor.name

alert( document.body.constructor.name ); // HTMLBodyElement

หรือเราสามารถใช้ toString 

alert( document.body ); // [object HTMLBodyElement]

หรือเราสามารถใช้ instanceof  เพื่อเช็คการสืบทอด

alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true

นอกจากนี้เรายังสามารถดู Output ของ element โดยการใช้ console.dir(elem) ใน Browser.

โดย Output ของมันจะมี HTMLElement.prototypeElement.prototype และอื่น ๆ


The “nodeType” property

nodeType property เป็นอีก 1 วิธีที่ ล้าสมัย เพื่อที่จะดึงค่าประเภทของ DOM node

มันเป็นค่าตัวเลข

  • elem.nodeType == 1 สำหรับ element node,
  • elem.nodeType == 3 สำหรับ text node,
  • elem.nodeType == 9 สำหรับ document Object,
  • มีค่าอื่น ๆ อีกใน the specification.

ตัวอย่างเช่น:

<body>
<script>
let elem = document.body;

// let's examine: what type of node is in elem?
alert(elem.nodeType); // 1 => element

// and its first child is...
alert(elem.firstChild.nodeType); // 3 => text

// for the document object, the type is 9
alert( document.nodeType ); // 9
</script>
</body>

ใน script สมัยใหม่ เราสามารถใช้ instanceof และ class-based อื่น ๆ ในการหา Node type

เราสามารถทำได้แค่อ่าน Node type และไม่สามารถเปลี่ยนมันได้


Tag: nodeName and tagName

DOM node ที่เราให้ไป เราสามารถอ่าน Tag name ผ่าน nodeName หรือ tagName property ได้

ตัวอย่างเช่น:

alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY

แล้วมีข้อแตกต่างระหว่าง tagName และ nodeName หรือไม่? แน่นอนว่ามี ข้อแตกต่างคือ การใช้กับชนิดของ Node

  • tagName ใช้สำหรับแค่ Element node
  • nodeName ใช้ได้กับทุก Node
    • สำหรับ element จะใช้เหมือกับ tagName
    • สำหรับ node type อื่น ๆ (text/comment etc.) จะมี string ที่เป็นประเภทของ Node

กล่าวง่าย ๆ คือ tagName สามารถใช้ได้แค่กับ element แต่ nodeName ใช้กับกับ Node ทุกประเภท

ตัวอย่าง:

<body><!-- comment -->

<script>
// for comment
alert( document.body.firstChild.tagName ); // undefined (not an element)
alert( document.body.firstChild.nodeName ); // #comment

// for document
alert( document.tagName ); // undefined (not an element)
alert( document.nodeName ); // #document
</script>
</body>

innerHTML: the contents

innerHTML property อนุญาตให้เราเข้าถึง HTML ข้างใน element ได้ โดยออกมาเป็น String

และเราสามารถดัดแปลงแก้ไขมันได้ด้วย ดังนั้นมันจึงเป็นทางที่ค่อนข้างมีประสิทธิภาพในการเปลี่ยน content ของ page

ตัวอย่าง:

<body>
<p>A paragraph</p>
<div>A div</div>

<script>
alert( document.body.innerHTML ); // read the current contents
document.body.innerHTML = 'The new BODY!'; // replace it
</script>

</body>

เราสามารถ ใส่ invalid HTML ได้ browser จะทำการแก้ไขให้

<body>

<script>
document.body.innerHTML = '<b>test'; // forgot to close the tag
alert( document.body.innerHTML ); // <b>test</b> (fixed)
</script>

</body>

Beware: “innerHTML+=” does a full overwrite

เราสามารถเพิ่ม HTML เข้าไปใน elementได้ โดยใช้ elem.innerHTML+="more html".


chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "How goes?";

แต่เราควรระวังในการใช้มันอย่างมาก เพราะว่า สิ่งที่เกิดขึ้นไม่ใช่การเพิ่มเข้าไปแต่เป็นการ overwrite มันทั้งหมด

ในทางเทคนิค สองบรรทัดนี้ทำงานเหมือนกัน

elem.innerHTML += "...";
// is a shorter way to write:
elem.innerHTML = elem.innerHTML + "..."

ในทางกลับกัน innerHTML += ทำแบบนี้

  1. content อันเก่าถูกลบออก
  2. innerHTML อันใหม่ ถูกเขียนลงไปแทน (เป็นการต่อข้อมูลกันระหว่าง อันเก่าและอันใหม่)

outerHTML: full HTML of the element

outerHTML property เก็บ HTML ทั้งหมดของ element ไว้ เหมือนกับ innerHTML แต่มันจะรวมตัวเองไปด้วย

<div id="elem">Hello <b>World</b></div>

<script>
alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>

Beware: ไม่เหมือนกับinnerHTML, เขียน outerHTML ไม่ได้เปลี่ยน element แต่เป็นการแทนที่ใน DOM

<div>Hello, world!</div>

<script>
let div = document.querySelector('div');

// replace div.outerHTML with <p>...</p>
div.outerHTML = '<p>A new element</p>'; // (*)

// Wow! 'div' is still the same!
alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>

ดูแปลกจริงๆ ใช่ไหม?

เราแทนที่ <div> ด้วย <p>A new element</p> เห็นได้ว่า ค่าของ <div> เก่าไม่ได้เปลี่ยน

outerHTML  การกำหนดด้านบนไม่ได้แก้ไข element แต่ลบออกจาก DOM และแทรก HTML ใหม่เข้าไป

ดังนั้นสิ่งที่เกิดขึ้นคือ:

  • div ถูกลบออกจาก document
  • HTML อีกชิ้น ถูกแทนที่ <p>A new element</p> ถูกแทรกเข้ามา
  • div ยังคงเป็นค่าเก่าอยู่ และ HTML ใหม่ไม่ได้ถูกบันทึกไปที่ตัวแปรไหนเลย

มันมีโอกาสสูงมากที่จะเกิด Error ขึ้นได้ การแปลง div.outerHTML และทำงานต่อไปกับ div ถ้ามันมี content ใหม่เข้ามา มันได้ สิ่งนี้ถูกต้องสำหรับ innerHTML แต่ไม่ใช่สำหรับ outerHTML


nodeValue/data: text node content

property innerHTML จะสามารถใช้ได้แค่กับ element node

Node type อื่น ๆ เช่น text node ก็จะมี properties ของมันเอง คือ nodeType และ data สองตัวนี้ เมื่อใช้จริงแล้ว มีหน้าที่คล้าย ๆ กัน มันจะมีความแตกต่างแค่เล็กน้อยเท่านั้น ดังนั้นเราจะใช้ data เพราะว่ามันจะสั้นกว่า

ตัวอย่างของการอ่าน text node และ comment:

<body>
Hello
<!-- Comment -->
<script>
let text = document.body.firstChild;
alert(text.data); // Hello

let comment = text.nextSibling;
alert(comment.data); // Comment
</script>
</body>

สำหรับ text node เราสามารถจิตนาการได้ ถึงการอ่านค่า หรือ แก้ไข ค่าของ text แต่ทำไม comment ล่ะ

developer บางคนฝังข้อมูลหรือ template เข้าไปใน HTML แบบนี้

<!-- if isAdmin -->
<div>Welcome, Admin!</div>
<!-- /if -->

ดังนั้น JavaScript สามารถอ่านค่าจาก data property ได้


textContent: pure text

textContent เป็นตัวที่ทำให้เราเข้าถึง ข้อความ ข้างใน element และไม่มี <tag> เข้ามารวม

ตัวอย่างเช่น:

<div id="news">
<h1>Headline!</h1>
<p>Martians attack people!</p>
</div>

<script>
// Headline! Martians attack people!
alert(news.textContent);
</script>

อย่างที่เราเห็น มีแค่ ข้อความ เท่านั้นถูกรีเทิร์นออกมา และ <tag> ถูกตัดออกทั้งหมด

ในทางปฏิบัติ การอ่านข้อความ ไม่ได้ใช้มากเท่าไหร่

การใช้ textContent มีประโยชน์มากกว่า เพราะว่ามันทำให้เราเขียนได้ปลอดภัยขึ้น:

  • การใช้ innerHTML เป็นการแทนค่า HTML เข้าไปทั้งหมด
  • การใช้ textContent เป็นการแทนค่า ข้อความ เข้าไป

เปรียบเทียบทั้งสองตัว:

<div id="elem1"></div>
<div id="elem2"></div>

<script>
let name = prompt("What's your name?", "<b>Winnie-the-Pooh!</b>");

elem1.innerHTML = name;
elem2.textContent = name;
</script>
  1. <div> ได้รับ input เข้ามาเป็น HTML ดังนั้นมันจะแปลง <b> ให้เป็น HTML
  2. <div> ได้รับ input เข้ามาเป็น ข้อควาาม ดังนั้น output ที่เราจะเห็น จะออกมาเป็น   <b>Winnie-the-Pooh!</b>.

The “hidden” property

“hidden” attribute และ DOM property จะเป็นตัวกำหนดว่า element นั้น จะปรากฏหรือไม่

เราสามารถใช้ใน HTML หรือ กำหนดค่ามันใน JavaScript ได้ แบบนี้:

<div>Both divs below are hidden</div>

<div hidden>With the attribute "hidden"</div>

<div id="elem">JavaScript assigned the property "hidden"</div>

<script>
elem.hidden = true;
</script>

ในทางเทคนิต hidden ทำหน้าที่เหมือนกับ style = "display: none;" แต่มันนสั้นกว่าเวลาเราเขียน

นี่คือ element ที่กระพริบได้:

<div id="elem">A blinking element</div>

<script>
setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>

More properties

DOM elements ก็มี properties เพิ่มเติมเหมือนกัน ในทางปฏิบัติแล้ว มันขึ้นอยู่กับ Class:

  • value คือค่าของ <input>, <select> และ <textarea> (HTMLInputElementHTMLSelectElement…)
  • href ใช้กับ แท็ก <a href="..." > (HTMLAnchorElement)

ตัวอย่างเช่น:


Summary

DOM node แต่ละตัว ก็จะมี Class ของมันเอง

DOM node properties หลัก ๆ:

nodeType เราใช้เพื่อดูว่า Node นั้น เป็นประเภทอะไร เช่น ค่า 1 เป็น element Node, 3 สำหรับ text Node

nodeName/tagName สำหรับ element ใช้ tagName สำหรับตัวที่ไม่ใช่ element ใช้ nodeName เพื่ออธิบาย ว่ามันคืออะไร

innerHTML ใช้อ่าน content ของ element สามารถดัดแปลงแก้ไขได้

outerHTML การใช้ outerHTML ในการแทนค่า จะแทนค่าทั้งหมดลงไปเลย

nodeValue/data การใช้ดู content ของ text หรือ comment ซึ่งจะให้ data มากกว่า

textContent ใช้อ่านข้อความข้างใน element จะไม่มี tag เข้ามา, การใช้มัน เป็นการใส่ข้อความเข้าไปใน element, special characters และ tag จะถูกทำให้เป็นเหมือนข้อความ

hidden เมื่อเรากำหนดให้เป็น true จะทำหน้าที่เหหมือนกับ display:none

DOM nodes แต่ละตัว มี properties ต่างกัน ขึ้นอยู่กับว่าอยู่ Class ไหน เช่น <input> element จะ support value ,type ในขณะที่ <a> element จะ support href และอื่น ๆ


Attributes and properties

เมื่อ Browser โหลดหน้าเพจ มันอ่าน HTML และ generates DOM objects ออกมา.

สำหรับ element node นั้น attribute ของ HTML ส่วนใหญ่ จะมาเป็น property ของ DOM Object

ตัวอย่างเช่น <body id="page"> ตัวของ DOM object จะมี body.id = “page”

แต่การจับคู่ของ attribute-property ไม่ใช่การจับคู่แบบ 1 ต่อ 1 ในบทนี้เราจะมาให้ความสนใจกับ หลักการทำงานของมัน และดูว่าเราจะทำงานกับมันได้อย่างไรบ้าง ตอนไหนมันจะเหมือนกัน ตอนไหนมันจะต่างกัน


DOM properties

เราเห็น Built-in DOM properties หลาย ๆ ตัวไปแล้ว แต่ว่า มันไม่มีอะไรมากำหนดว่าให้ใช้ได้แค่ Built-in เรายังสามารถเพิ่ม attribute ของเราเองได้

DOM nodes เป็นเหมือน Object ของ JavaScript ธรรมดาเลย. เราสามารถเปลี่ยนแปลงแก้ไขมันได้

ตัวอย่างเช่น เราจะสร้าง properties ใหม่ใน document.body

 document.body.myData = {
name: 'Caesar',
title: 'Imperator'
};

alert(document.body.myData.title); // Imperator

เราสามารถสร้าง method ได้เหมือนกัน

document.body.sayTagName = function() {
alert(this.tagName);
};

document.body.sayTagName(); // BODY (the value of "this" in the method is document.body)

เราสามารถ แก้ไข Built-in prototypes เช่น Element.prototype และเพิ่ม method เข้าไปใน element ได้

Element.prototype.sayHi = function() {
alert(`Hello, I'm ${this.tagName}`);
};

document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY

ดังนั้น DOM properties และ method จะทำตัวเหมือนกับ JavaScript Object ธรรมดาเลย

  • มันสาารถมีค่าใดก็ได้
  • พวกมันเป็น case-sensitive (เช่น ต้องเขียน elem.nodeType, ไม่ใช่ elem.NoDeTyPe)

HTML attributes

ใน HTML, บาง tag อาจมี attribute อยู่ เมื่อ Browser วิเคราะห์ HTML เพื่อที่จะสร้าง DOM object สำหรับ tag มันจะสร้าง standard attribute และ สร้าง DOM properties ให้กับมัน

ตัวอย่างเช่น

<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// non-standard attribute does not yield a property
alert(document.body.something); // undefined
</script>
</body>

โปรดจำไว้ว่า standard attribute สำหรับ element หนึ่ง อาจจะไม่มีค่าในอีก element หนึ่ง เช่น "type" เป็น standard สำหรับ <input> แต่ไม่มีใน <body>

<body id="body" type="...">
<input id="input" type="text">
<script>
alert(input.type); // text
alert(body.type); // undefined: DOM property not created, because it's non-standard
</script>
</body>

ดังนั้น ถ้า attribute นั้น ไม่ใช่ standard attribute แล้ว มันจะไม่ถูกสร้างใน DOM-property สำหรับมัน.

มันมีทางที่จะเข้าถึง attribute ได้หรือไม่. แน่นอน attribute ทุกตัววสามารถเข้าถึงได้ โดยการใช้ method พวกนี้

  • elem.hasAttribute(name) – ใช้ตรวจสอบว่ามี attribute ที่ตรงกับ name หรือไม่.
  • elem.getAttribute(name) – ใช้ดึงค่าจาก attribute ตามชื่อที่กำหนด.
  • elem.setAttribute(name, value) – กำหนดค่าให้กับ attribute ที่กำหนด.
  • elem.removeAttribute(name) – ลบ attribute.

method พวกนี้ทำหน้าที่เหมือนกับ สิ่งที่เราเขียนลงไปใน HTML เลย

ตัวอย่างในการอ่านข้อมูลที่เป็น non-standard properties:

<body something="non-standard">
<script>
alert(document.body.getAttribute('something')); // non-standard
</script>
</body>

HTML attributes มี features ตามนี้:

  • ชื่อของพวกมัน เป็น case-insensitive (id เหมือนกับ ID).
  • ค่าของพวกมันจะเป็น String เสมอ

ตัวอย่างการทำงานกับ attribute

<body>
<div id="elem" about="Elephant"></div>

<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant', reading

elem.setAttribute('Test', 123); // (2), writing

alert( elem.outerHTML ); // (3), see if the attribute is in HTML (yes)

for (let attr of elem.attributes) { // (4) list all
alert( `${attr.name} = ${attr.value}` );
}
</script>
</body>

Please note:

  • getAttribute('About') - ตัวแรกเป็นตัวพิมพ์ใหญ่ แต่ใน HTML เป็น lowwercase ทั้งหมด แต่มันไม่สำคัญ เพราะ attribute name เป็น case-insensitive
  • เราสามารถกำหนดอะไรก็ได้ลงไปใน attribute แต่ว่าค่าของมันจะเป็น String ดังนั้นเราจะได้ "123"
  • attribute ทั้งหมด เราจะสามารถดูได้ใน outerHTML
  • collection attributes เป็น iterable และ attribute ของมันทั้งหมด จะมาในรูปแบบของ Object โดยมี name และ value เป็น properties

Property-attribute synchronization

เมื่อ standard attribute เปลี่ยน มันจะ update โดยอัตโนมัติ ไปที่ property

ในตัวอย่างข้างล่าง id ถูกทำให้เป็น attribute และอย่างที่เห็น property ก็ถูกเปลี่ยนด้วยเช่นกัน

<input>

<script>
let input = document.querySelector('input');

// attribute => property
input.setAttribute('id', 'id');
alert(input.id); // id (updated)

// property => attribute
input.id = 'newId';
alert(input.getAttribute('id')); // newId (updated)
</script>

แต่ว่า มีข้อยกเว้นกับ input.value มันจะเชื่อมแค่ attribute → property เท่านั้น ดังนั้นการเปลี่ยนค่า ที่ property จะไม่เปลี่ยนค่าของมัน

<input>

<script>
let input = document.querySelector('input');

// attribute => property
input.setAttribute('value', 'text');
alert(input.value); // text

// NOT property => attribute
input.value = 'newValue';
alert(input.getAttribute('value')); // text (not updated!)
</script>

ในตัวอย่างด้านบน:

  • เปลี่ยนค่าของ attribute value มันไป update ค่าของ property.
  • เปลี่ยนที่ property มันไม่ส่งผลไปที่ attribute

DOM properties are typed

DOM properties ไม่ได้เป็น String เสมอไป ตัวอย่างเช่น input.check property (ใช้กับ Checkboxes) เป็น boolean

<input id="input" type="checkbox" checked> checkbox

<script>
alert(input.getAttribute('checked')); // the attribute value is: empty string
alert(input.checked); // the property value is: true
</script>

อีกตัวอย่าง. style attribute เป็น string แต่ style properties เป็น Object

<div id="div" style="color:red;font-size:120%">Hello</div>

<script>
// string
alert(div.getAttribute('style')); // color:red;font-size:120%

// object
alert(div.style); // [object CSSStyleDeclaration]
alert(div.style.color); // red
</script>

แต่ยังไงก็ตาม ส่วนใหญ่ก็เป็น string อยู่ดี

แม้ว่า DOM property เป็น string แต่ก็อาจเก็บค่าแตกต่างจาก attribute ตัวอย่างเช่น href  DOM property จะเป็น URL แบบเต็ม ถึงแม้ attribute จะเก็บแค่ #hash ตัวอย่างเช่น:


<a id="a" href="#hello">link</a>
<script>
// attribute
alert(a.getAttribute('href')); // #hello

// property
alert(a.href); // full URL in the form http://site.com/page#hello
</script>

Non-standard attributes, dataset

เมื่อเราเขียน HTML แล้ว เราใช้ standard attribute มากมาย แต่ non-standard attribute ล่ะ

เรามาดูกันว่า มันสำคัญหรือไม่ และใช้เพื่ออะไร

บางที non-standard attributes ใช้เพื่อส่ง custom data จาก HTML ไปที่ JavaScript หรือ “mark” HTML element สำหรับ JavaScipt

<!-- mark the div to show "name" here -->
<div show-info="name"></div>
<!-- and age here -->
<div show-info="age"></div>

<script>
// the code finds an element with the mark and shows what's requested
let user = {
name: "Pete",
age: 25
};

for(let div of document.querySelectorAll('[show-info]')) {
// insert the corresponding info into the field
let field = div.getAttribute('show-info');
div.innerHTML = user[field]; // first Pete into "name", then 25 into "age"
}
</script>

และพวกมันยังสามารถเพื่อตกแต่ง element ได้อีกด้วย

<style>
/* styles rely on the custom attribute "order-state" */
.order[order-state="new"] {
color: green;
}

.order[order-state="pending"] {
color: blue;
}

.order[order-state="canceled"] {
color: red;
}
</style>

<div class="order" order-state="new">
A new order.
</div>

<div class="order" order-state="pending">
A pending order.
</div>

<div class="order" order-state="canceled">
A canceled order.
</div>

ทำไมเราถึงควรใช้ attribute แทนการใช้ class เช่น .order-state-new.order-state-pending.order-state-canceled ?

เพราะว่า attribute สามารถจัดการได้ง่ายกว่า, state สามารถเปลี่ยนได้ง่าย

// a bit simpler than removing old/adding a new class
div.setAttribute('order-state', 'canceled');

attribute ทั้งหมดที่เริ่มด้วย “data-” สงวนไว้ให้กับ programmer ใช้. พวกมันสามารถใช้ได้ที่ dataset. property

<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>

attribute ที่ใช้หลายคำเช่น data-order-state จะมาเป็น camel-case: dataset.orderState. ตัวอย่างเช่น:


<style>
.order[data-order-state="new"] {
color: green;
}

.order[data-order-state="pending"] {
color: blue;
}

.order[data-order-state="canceled"] {
color: red;
}
</style>

<div id="order" class="order" data-order-state="new">
A new order.
</div>

<script>
// read
alert(order.dataset.orderState); // new

// modify
order.dataset.orderState = "pending"; // (*)
</script>

การใช้ data- attributes เป็นวิธีที่ปลอดภัย สำหรับการส่งค่าไปที่ JavaScript

และการใช้ data- เราไม่ได้ทำได้แค่อ่านมัน เรายังสามารถ custom data-attributes


Summary

  • Attributes – คือสิ่งที่เขียนใน HTML
  • Properties – คือสิ่งที่อยู่ใน DOM object

type

Name

Properties


สามารถเป็นได้ทุก type

name เป็น case-sensitive

Attributes


เป็น String

name ไม่เป็น case-sensitive

Method ที่ใช้ทำงานร่วมกับ attribute คือ:

  • elem.hasAttribute(name) – ใช้ตรวจสอบว่ามี attribute ที่ตรงกับ name หรือไม่.
  • elem.getAttribute(name) – ใช้ดึงค่าจาก attribute ตามชื่อที่กำหนด.
  • elem.setAttribute(name, value) – กำหนดค่าให้กับ attribute ที่กำหนด.
  • elem.removeAttribute(name) – ลบ attribute.
  • elem.attributes คืนค่า attribute ทั้งหมดที่มี.

ในหลายสถานการณ์ เรานิยมใช้ DOM properties มากกว่า เราควรใช้ attribute แค่ตอนเมื่อ DOM properties ไม่เหมาะกับสถานการณ์นั้น เราต้องการใช้ attribute จริง ๆ ก็ต่อเมื่อ

  • เราต้องการใช้ non-standard attribute
  • เราต้องการอ่านค่าที่เราเขียนใน HTML ตัวอย่างเช่น href  ที่เราพูดถึงไป

Modifying the document

DOM modification เป็นกุญแจสำคัญที่ทำให้สามารถสร้าง page ที่อัปเดตได้ตลอดเวลา

Example: show a message

มาสาธิตโดยใช้ตัวอย่างกัน เราจะเพิ่มข้อความบนหน้าเว็บที่ดูดีกว่าการใช้ alert

ตัวอย่างเช่น:

<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>

<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>

image.png

นั่นคือการใช้ HTML. ตอนนี้เราสร้าง <div> ตัวเดิม โดยใช้ JavaScript กันเถอะ


Creating an element

ในการสร้าง DOM node เราจะใช้ 2 method ด้วยกัน

document.createElement(tag) สร้าง element โดยการให้ชื่อแท็ก

let div = document.createElement('div');

document.createTextNode(text) สร้าง text node โดยการใส่ข้อความที่ต้องการ

let textNode = document.createTextNode('Here I am');

ส่วนใหญ่เราต้องการสร้าง element node เช่น <div> เพื่อแสดงข้อความ


Creating the message

สร้าง <div> ที่มีข้อความ ใช้ขั้นตอน 3 วิธี:

// 1. Create <div> element
let div = document.createElement('div');

// 2. Set its class to "alert"
div.className = "alert";

// 3. Fill it with the content
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

ตอนนี้เราสร้าง Node element แล้ว แต่ว่าเรายังเก็บไว้ในตัวแปร div อยู่ และยังไม่ได้นำไปแสดง แปลว่าเรายังไม่เห็นมัน


Insertion methods

วิธีทำให้ div ขึ้นไปแสดงผลนั้น เราต้องแทรกเข้าไปซักที่ใน document ตัวอย่างเช่น ใส่เข้าไปใน <body> ที่จะอ้างอิงไปถึง document.body และเราสามารถใช้ method append เพิ่มแทรก div เข้าไปได้ document.body.append(div) :

<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>

<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

document.body.append(div);
</script>

เราเรียกใช้ append บน document.body แต่เราก็สามารถเรียกใช้ append ได้บน element อื่นเหมือนกัน

เช่น เราต้องการเพิ่มบางอย่างเข้าไปใน <div> ก็เขียนแค่ว่า div.append(anotherElement)

และยังมี method อีกหลายตัวที่ใช้แทรก element เข้าไปได้อีก

  • node.append(...nodes or strings) – เพิ่มเข้าไปข้างในและอยู่ตรงท้ายของ node,
  • node.prepend(...nodes or strings) – เพิ่มเข้าไปข้างในและอยู่ตรงข้างหน้าของ node,
  • node.before(...nodes or strings) –- เพิ่มไปก่อน  node,
  • node.after(...nodes or strings) –- เพิ่มไปหลัง node,
  • node.replaceWith(...nodes or strings) –- แทนที่ node กับ Node ที่ให้มา

เรามาดูวิธีใช้กัน

<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>

<script>
ol.before('before'); // insert string "before" before <ol>
ol.after('after'); // insert string "after" after <ol>

let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // insert liFirst at the beginning of <ol>

let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // insert liLast at the end of <ol>
</script>

image.png

ภาพหลักการทำงานของ method

image.png

ดังนั้น สุดท้ายแล้วจะให้ผลลัพธ์ใน HTML แบบนี้

before
<ol id="ol">
<li>prepend</li>
<li>0</li>
<li>1</li>
<li>2</li>
<li>append</li>
</ol>
after

อย่างที่พูดไป เราสามารถแทรก Node ได้มากกว่า 1 ตัวได้ภายในการเรียกใช้ method ครั้งเดียว

ตัวอย่างเช่น:

<div id="div"></div>
<script>
div.before('<p>Hello</p>', document.createElement('hr'));
</script>

โปรดจำไว้ว่า มันแทรกข้อความ ไม่ใช่ HTML และด้วย proper escaping ของตัวอักษรดังนั้น โค้ดที่ออกมา หน้าตาจะเป็นแบบนี้

&lt;p&gt;Hello&lt;/p&gt;
<hr>
<div id="div"></div>

ในอีกความหมายคือ มันจะดีกว่า ถ้าเราใช้แทรกข้อความ เหมือนที่ elem.textContent ทำ

แต่ถ้าเราต้องการที่จะแทรก HTML เข้าไปล่ะ เราสามารถทำอย่างไรได้บ้าง


insertAdjacentHTML/Text/Element

สำหรับสิ่งนั้น เราสามารถใช้ method อื่น ๆ ได้ elem.insertAdjacentHTML(where, html)

parameter ตัวแรกเป็นคำเฉพาะที่ถูกล็อกมาไว้แล้ว ว่าต้องใช้แค่คำพวกนี้

  • "beforebegin" – แทรก html ไปข้างหน้าของ elem,
  • "afterbegin" – แทรก html เข้าไปใน elem, ตรงจุดแรก
  • "beforeend" – แทรก html เข้าไปในelem, ตรงจุดสุดท้าย
  • "afterend" – แทรก html ไปด้านท้ายของ elem.

parameter ตัวที่สอง คือ HTML ที่เป็น string ที่ถูกแทรกเข้าไปในรูปแบบของ HTML

ตัวอย่างเช่น:

<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>

โค้ด HTML หน้าตาจะเป็นแบบนี้

<p>Hello</p>
<div id="div"></div>
<p>Bye</p>

รูปภาพของการทำงาน

image.png

สังเกตได้ว่า รูปภาพนี้ คล้ายกับรูปภาพข้างบน แตกต่างกันที่ว่า มันแทรก HTML เข้าไป

และมันยังมี method ที่คล้ายกันอีก 2 ตัว

  • elem.insertAdjacentText(where, text) ใช้ syntax เหมือนกัน แต่ text จะแทรกเป็น String เข้าไป
  • elem.insertAdjacentElement(where, elem) – ใช้ syntax เหมือนกัน แต่ จะแทรกเป็น element เข้าไป

จริง ๆ แล้วพวกมันมีอยู่เพื่อทำให้ syntax "เหมือนกัน" เป็นหลัก ในทางปฏิบัติแล้ว insertAdjacentHTML จะถูกใช้มากกว่า เพราะว่าสำหรับ text และ element แล้ว เรามี method append/prepend/before/after

ให้ใช้ และสั้นกว่าเมื่อเราใช้

นี่เป็นอีกทางเลือกหนึ่งในการแสดงข้อความ:

<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>

<script>
document.body.insertAdjacentHTML("afterbegin", `<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>`);
</script>

Node removal

ในการลบ Node เราสามารถใช้ method node.remove() ได้

เรามาลองทำให้ข้อความของเรา หายไปหลังจาก 1 วินาทีกัน

<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>

<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

document.body.append(div);
setTimeout(() => div.remove(), 1000);
</script>

Note: ถ้าเราต้องการที่จะย้าย element ไปที่อีกที่หนึ่ง เราไม่จำเป็นต้องลบมันออกจากอันเก่า

insertion method ทั้งหมดจะทำการลบไปให้อัติโนมัติ

<div id="first">First</div>
<div id="second">Second</div>
<script>
// no need to call remove
second.after(first); // take #second and after it insert #first
</script>

Cloning nodes: cloneNode

เราจะเพิ่มข้อความที่เหมือนกันได้อย่างไร

เราสามารถสร้างฟังก์ชั่นและใส่โค้ดเข้าไป แต่เรายังมีทางเลือกอื่นที่จะ Clone div และแก้ไขดัดแปลงมัน(ถ้าต้องการ)

ในกรณีที่เรามี element ที่ใหญ่ นี่อาจเป็นวิธีที่เร็วกว่าและง่ายกว่า

  • การเรียกใช้ elem.cloneNode(true) จะสร้าง element ที่หน้าตาเหมือนกันกับ elem – ที่มีทั้ง attributes และ element ย่อย.
  • ถ้าเรียกใช้ elem.cloneNode(false), มันจะ clone element มาโดยไม่มี child element

ตัวอย่างการ clone:

<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>

<div class="alert" id="div">
<strong>Hi there!</strong> You've read an important message.
</div>

<script>
let div2 = div.cloneNode(true); // clone the message
div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone

div.after(div2); // show the clone after the existing div
</script>

DocumentFragment

DocumentFragment เป็น DOM node แบบพิเศษ ทำหน้าที่หุ้มและส่ง list ของ Node ออกไป

เราสามารถ append ใส่ node ต่าง ๆ เข้าไปได้ แต่เมื่อเรา insert เข้าไปซักที่ content ของมันจะถูก insert เข้าไปแทน

ตัวอย่างเช่น getListContent ทำหน้าที่ generates <li> ออกมา และใส่เข้าไปที่ <ul>

<ul id="ul"></ul>

<script>
function getListContent() {
let fragment = new DocumentFragment();

for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
fragment.append(li);
}

return fragment;
}

ul.append(getListContent()); // (*)
</script>

ผลลัพธ์โครงสร้างคือ

<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

DocumentFragment ไม่ค่อยได้ใช้งาน, ทำไมเราต้องใช้ Node ชนิดพิเศษ ในเมื่อเราสามารถ รีเทิร์น Array ของ Node ออกไปได้:

<ul id="ul"></ul>

<script>
function getListContent() {
let result = [];

for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
result.push(li);
}

return result;
}

ul.append(...getListContent()); // append + "..." operator = friends!
</script>

A word about “document.write”

อีกหนึ่งวิธีที่ค่อนข้างเก่า ในการเพิ่มบางอย่างเข้าไปใน web-page โดยการใช้ document.write

<p>Somewhere in the page...</p>
<script>
document.write('<b>Hello from JS</b>');
</script>
<p>The end</p>

การใช้ document.write จะสามารถใช้ได้แค่ตอนที่ page กำลัง load อยู่เท่านั้น เช่น:

<p>After one second the contents of this page will be replaced...</p>
<script>
// document.write after 1 second
// that's after the page loaded, so it erases the existing content
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>

Summary

  • Methods ที่ใช้สร้าง Node:

    • document.createElement(tag) – สร้าง Node จากแท็กที่ให้,
    • document.createTextNode(value) – สร้าง Node ข้อความ,
    • elem.cloneNode(deep) – clones element, ถ้า deep==true มันจะ clone child element มาด้วย.
  • แทรกและลบ:

    • node.append(...nodes or strings) – แทรกเข้าไปที่ node, ที่ข้างหลัง,
    • node.prepend(...nodes or strings) – แทรกเข้าไปที่ node, ที่ข้างหน้า,
    • node.before(...nodes or strings) –- แทรกไปที่ข้างหน้า node,
    • node.after(...nodes or strings) –- แทรกไปที่ข้างหลัง node,
    • node.replaceWith(...nodes or strings) –- แทนที่node.
    • node.remove() –- ลบ node.

    ข้อความถูกแทรกเข้าไปในรูปของ “text”.

  • htmlelem.insertAdjacentHTML(where, html) ตำแหน่งของมันขึ้นอยู่กับ where:

    • "beforebegin" – แทรก html ก่อน elem,
    • "afterbegin" – แทรก html เข้าไปที่ elem, ตรงข้างหน้า
    • "beforeend" – แทรก html เข้าไปที่elem, ตรงข้างหลัง
    • "afterend" – แทรก html หลัง elem.

Styles and classes

ก่อนที่เราจะไปต่อกับ JavaScript เรามาดูวิธีการทำงานกับ styles and classes นี่คือกฏที่สำคัญ

มันมี 2 วิธีที่เราจะ style ตัว element

  1. สร้าง class ใน CSS และเพิ่ม: <div class="...">
  2. เขียน properties ที่ directly ไปที่  style<div style="...">.

JavaScript สามารถ modify properties classes และ style ได้

ตัวอย่างเช่น เราสามารถคำนวณ style ได้ ถ้าเราต้องการที่จะคำนวณตำแหน่งของ element จาก JavaScript

let top = /* complex calculations */;
let left = /* complex calculations */;

elem.style.left = left; // e.g '123px', calculated at run-time
elem.style.top = top; // e.g '456px'

className and classList

การเปลี่ยน Class ถูกใช้บ่อยในการเขียน Script

ในสมัยก่อน มีข้อจำกัดอยู่ใน JavaScript เพราะในตอนนั้น "class" เป็น reserved word และไม่สามารถเป็น property ของ Object ได้ แต่ในตอนนี้ข้อจำกัดนั้นไม่มีแล้ว และ "class" property ยังสำคัญมากอีกด้วย

เช่น elem.class

ดังนั้น property "className" ถูกนำเสนอมา: โดย elem.className มีความเกี่ยวข้องกับ attribute "class"

ตัวอย่าง:

<body class="main page">
<script>
alert(document.body.className); // main page
</script>
</body>

ถ้าเรากำหนด elem.className มันจะแทน class ทั้งหมด บางทีเราต้องการแบบนั้น แต่บางทีเราต้องการจะ เพิ่มและลบแค่ class เดียว

ดังนั้นเราจึงมี property อีกตัวสำหรับเหตุการนั้น elem.classList

elem.classList เป็น Object ชนิดพิเศษ และมี method add/remove/toggle ใน classList เดียว

ตัวอย่างเช่น

<body class="main page">
<script>
// add a class
document.body.classList.add('article');

alert(document.body.className); // main page article
</script>
</body>

Method ของ classList

  • elem.classList.add/remove("class") – เพิ่ม/ลบ Class
  • elem.classList.toggle("class") – เพิ่ม Class ถ้ามันไม่มี กลับกันถ้ามันไม่มี มันจะเพิ่ม Class เข้าไป
  • elem.classList.contains("class") – ตรวจสอบ Class ที่ให้ไป true/false.

classList เป็น iterable ดังนั้นเราจึงสามารถ loop มัน โดยใช้ for...of แบบนี้

<body class="main page">
<script>
for (let name of document.body.classList) {
alert(name); // main, and then page
}
</script>
</body>

Element style

property elem.style เป็น Object ที่เกียวกับการเขียน style attribute เช่น กำหนด elem.style.width="100px" ทำงานเหมือนกัน attribute style

สำหรับ property ที่เป็นหลายคำ จะใช้เป็น camelCase

background-color  => elem.style.backgroundColor
z-index => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth

ตัวอย่างเช่น

document.body.style.backgroundColor = prompt('background color?', 'green');

Resetting the style property

บางครั้งเราต้องการที่จะกำหนดค่าของ property และลบมันออก

ตัวอย่างเช่น ถ้าเราจะซ่อน element เราสามารถกำหนดelem.style.display = "none". และหลังจากนั้น เราอาจต้องการที่จะลบ style.display แทนที่เราจะใช้ delete elem.style.display เราควรกำหนดค่ามันให้เป็น String เปล่าจะดีกว่า elem.style.display = ""

// if we run this code, the <body> will blink
document.body.style.display = "none"; // hide

setTimeout(() => document.body.style.display = "", 1000); // back to normal

เรามี method พิเศษ สำหรับการลบ property elem.style.removeProperty('style property') ดังนั้น เราสามารถลบ property ได้ แบบนี้

document.body.style.background = 'red'; //set background to red

setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second

Mind the units

อย่าลืมเติมหน่วยของ CSS

ตัวอย่างเช่น เราไม่ควรกำหนด elem.style.top เป็น 10 แต่ควรจะเป็น 10px แทน ในทางตรงกันข้าม มันอาจจะไม่ทำงาน

<body>
<script>
// doesn't work!
document.body.style.margin = 20;
alert(document.body.style.margin); // '' (empty string, the assignment is ignored)

// now add the CSS unit (px) - and it works
document.body.style.margin = '20px';
alert(document.body.style.margin); // 20px

alert(document.body.style.marginTop); // 20px
alert(document.body.style.marginLeft); // 20px
</script>
</body>

Computed styles: getComputedStyle

การกำหนด style เป็นเรื่องที่ค่อนข้างง่าย แต่เราจะอ่านค่ามันได้อย่าไร

ตัวอย่างเช่น เราต้องการที่จะรู้ขนาดของ size, margin หรือ สีของ element เราสามารถ จะทำมันได้อย่างไร

style property ทำงานแค่กับ style attribute

ตัวอย่างเช่น proprety style  ไม่เห็น margin ที่ถูกกำหนดอยู่ใน tag <style>

<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>

The red text
<script>
alert(document.body.style.color); // empty
alert(document.body.style.marginTop); // empty
</script>
</body>

แต่ถ้าเราอยากบอกว่า เราต้องการเพิ่ม margin ไป 20px ล่ะ, โดยเราต้องการค่าปัจจุบันของมัน

นี่คือ method สำหรับการดึงค่ามา

getComputedStyle(element, [pseudo])

element

element ที่ต้องการจะอ่านค่า

pseudo

pseudo-element ถ้าเราต้องการจะใช้, ตัวอย่างเช่น ::before. การที่เว้นว่างไว้ หมายถึง element ตัวของมันเอง

ตัวอย่างเช่น:

<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>

<script>
let computedStyle = getComputedStyle(document.body);

// now we can read the margin and the color from it

alert( computedStyle.marginTop ); // 5px
alert( computedStyle.color ); // rgb(255, 0, 0)
</script>

</body>

Summary

ในการจัดการกับ Class เราสามารถใช้ 2 DOM properties นี้ได้

  • className – ค่าที่เป็น string, ใช้งานได้ดี เมื่อจัดการกับ ชื่อ class ทั้งหมด
  • classList – object นี้มี method add/remove/toggle/contains, ใช่เมื่อต้องการจัดการกับ class แค่บางส่วน.

ในการจัดการกับ style

  • style property เป็น Object ที่เป็น camelCased ใช้เพื่ออ่านหรือเขียน ใช้ได้เหมือนกับ attribute style ใน HTML
  • style.cssText property ใช้จัดการกับ "style" ในขนาดใหญ่ และใส่เป็น string

ในการอ่านค่า resolved (ค่าที่ถูกคำนวณมาแล้ว)

  • getComputedStyle(elem, [pseudo]) คืนค่า style ที่เป็น Object ออกมา สามารถอ่านได้อย่างเดียว

Element size and scrolling

มี properties หลายตัวที่ทำให้เราอ่าน ความกว้าง ความยาวของ element ได้

บ่อยครั้งเราต้องการใช้เมื่อเราต้องการที่จะ เคลื่อนที่ หรือ จัด position element ใน JavaScript


Sample element

เราจะยกตัวอย่างการใช้ property ในการจัด element ที่จะทำต่อไปนี้

<div id="example">
...Text...
</div>
<style>
#example {
width: 300px;
height: 200px;
border: 25px solid #E8C48F;
padding: 20px;
overflow: auto;
}
</style>

elememt ตัวนี้ จะมี border, padding และ scrolling แต่ไม่มี margin เพราะว่ามันไม่ได้เป็นส่วนของ element และมันไม่มี property สำหรับมัน

และ element จะหน้าตาเป็นแบบนี้

image.png


Geometry

นี่คือภาพรวมของ geometry properties:

image.png

ค่าของ properties พวกนี้เป็นตัวเลขที่อยู่ในหน่วยของ pixels ดังนั้น ค่าเหล่านี้เป็นค่าของ pixel


offsetParent, offsetLeft/Top

properties พวกนี้ไม่ค่อยจะได้ใช้

offsetParent คือ nearest ancestor ที่ broswser ใช้สำหรับการคำนวณพิกัดของ element ขณะที่กำลังเรนเดอร์อยู่

nearest ancestor มีดังนั้น

  1. การใช้ CSS กำหนด (position คือ absoluterelativefixed หรือ sticky), หรือ
  2. <td><th>, หรือ <table>, หรือ
  3. <body>.

Properties offsetLeft/offsetTop  เป็นตัวกำหนด พิกัด x/y ที่ยึดตาม offsetParent โดยยึดตามมุมซ้ายบ

ในตัวอย่างข้างล่าง <div> มี <main> เป็น offsetParent และoffsetLeft/offsetTop เลื่อนจาก มุมซ้ายบน ไป 180px

<main style="position: relative" id="main">
<article>
<div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
</article>
</main>
<script>
alert(example.offsetParent.id); // main
alert(example.offsetLeft); // 180 (note: a number, not a string "180px")
alert(example.offsetTop); // 180
</script>

image.png

มีหลายครั้ง ที่ offsetParent เป็น null เหตุเกิดจาก:

  1. element ไม่ถูก show (display:none หรือ ไม่ได้อยู่ใน document).
  2. offsetParentเป็น <body> และ <html>.
  3. และ element ที่เป็น position:fixed.

offsetWidth/Height

ไปต่อกันที่ตัว element กันเถอะ

properties สองตัวนี้เป็นตัวที่เป็นสิ่งที่เข้าใจง่าย มันเป็นตัวกำหนด ความยาว/ความกว้าง ภายในของ element พูดง่าย ๆ คือ เป็นขนาดที่รวมกับ borders ด้วย

image.png

สำหรับ element ที่เรายกตัวอย่าง:

  • offsetWidth = 390 ความกว้างภายนอก คำนวณได้โดย, ความกว้างภายใน (300px) รวมกับ paddings (2 * 20px) และ borders (2 * 25px).
  • offsetHeight = 290 – ความสูงภายนอก

clientTop/Left

ใน element เรามี borders

ในการวัดพวกมัน มีอยู่ 2 properties clientTop and clientLeft.

ตัวอย่างเช่น

  • clientLeft = 25 – ความกว้าง border ซ้าย
  • clientTop = 25 – ความกว้าง border บน

image.png

แต่เพื่อความแม่นยำ properties พวกนี้ไม่ใช่การกำหนด borders ของมันโดยตรง แต่เหมือนเป็นการกำหนดพิกัดของมันจากมุมภายนอก

หมายความว่าอย่างไร?

เมื่อ scrollBar มาอยู่ข้างซ้าย ดังนั้น clientLeft จะรวม scrollBar ไปด้วย

เช่น clientLeft จะไม่เป็น 25 แต่จะรวมกัน scrollBar ด้วย 25 + 16 = 41

image.png


clientWidth/Height

properties นี้จะกำหนดขนาดข้างใน border ซึ่งรวมกับ content width และ padding แต่ไม่รวมกับ scrollBar

image.png

สังเกตได้ว่า clientWidth = 324px

เรามาพิจารณา clientHeight กันก่อน

ในเมื่อแนวตั้ง ไม่มี scrollBar ดังนั้นผลรวมของ clientHeight จะรวมกับ CSS-height 200px รวมกับ padding บนและล่าง เป็น 2 * 20px รวมกันเป็น 240px

ถ้ามันไม่มี padding clientWidth/Height จะรวมกับ scrollBar ด้วย

image.png


scrollWidth/Height

properties นี้จะเป็นเหมือน clientWidth/clientHeight แต่มันจะรวมส่วนที่หายไปด้วย (ส่วนที่เกินหน้าจอออกไป)

image.png

รูปด้านบน

  • scrollHeight = 723 – เป็นความสูงทั้งหมด ที่รวมกับส่วนที่ล้นหน้าจอออกไปด้วย.
  • scrollWidth = 324 – เป็นความกว้างทั้งหมด ถ้าเราไม่มี scrollBar ในแนวนอน ดังนั้น ค่ามันจะเท่ากับclientWidth.

เราสามารถใช้ properties นี้เพื่อขยายความสูง element ออกไปได้

// expand the element to the full content height
element.style.height = `${element.scrollHeight}px`;

ความสูงที่กำหนดจาก CSS:

image.png

ความสูงที่กำหนดจาก JavaScript:

image.png


scrollLeft/scrollTop

properties scrollLeft/scrollTop คือความกว้าง/สูง ของส่วนที่ล้นจอออกไปด้านบน/ซ้าย

image.png

พูดง่าย ๆ คือ scrollTop คือ เราเลื่อนมันไปเท่าไหร่


Summary

element มี geometry properties ตามนี้

  • offsetParent – คือตัวที่เป็น nearest ancestor tdthtablebody.
  • offsetLeft/offsetTop – พิกัด จะยึดตามมุมซ้ายบนของ offsetParent.
  • offsetWidth/offsetHeight – ความกว้างความสูงภายนอก รวมกับ border
  • clientLeft/clientTop – ระยะห่างจาก มุมซ้ายบนของภายนอก ไปจนถึงมุมซ้ายบนของภายใน (content + padding).
  • clientWidth/clientHeight – ความสูง/กว้างของ content รวมกับ padding แต่ไม่รวมกับ scrollBar
  • scrollWidth/scrollHeight – ความสูง/กว้างของ content, เหมือนกับclientWidth/clientHeight, แต่รวมกับความสูง/กว้าง ของ element ที่พ้นจอออกไป.
  • scrollLeft/scrollTop – ความสูง/กว้างของ ส่วนที่ถูก scroll ขึ้นไปด้านบน เริ่มจากซ้านบนของ element

properties ทั้งหมดเป็น read-only ยกเว้น scrollLeft/scrollTop ที่ทำให้ browser scroll element ได้


Window sizes and scrolling

เราจะหาความกว้างความสูง ของ หน้าต่าง Browser ได้อย่างไร, เราจะหาความสูงและกว้างของ document ได้อย่างไร แล้วของส่วนที่ถูกเลื่อนไปล่ะ? เราจะเลื่อนหน้า page โดยใช้ JavaScript ได้อย่างไร

สำหรับข้อมูลประเภทนั้น เราสามารถใช้ document.documentElement ที่เกี่ยวข้องกับแท็ก <html> แต่มันยังมี method เพิ่มเติมอยู่อีก


Width/height of the window

ในการที่จะดึงค่าความสูงและกว้างของ Browser ได้ เราสามารถใช้ clientWidht/clientHeight ของ document.documentElement:

image.png

ตัวอย่างเช่น ถ้าเราจะแสดงค่าความสูงของหน้าต่าง Browser

alert(document.documentElement.clientHeight)

Width/height of the document

ในทางทฤษฏีแล้ว รากของ document คือ document.documentElement และมันเป็นตัวที่คลุม content ทั้งหมดไว้ เราสามาถวัดขนาดของมันได้โดยใช้ document.documentElement.scrollWidth/scrollHeight. เพื่อให้ได้ความสูงของ document ทั้งหมด เราควรใช้ค่าที่สูงสุดของ properties พวกนี้

let scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);

alert('Full document height, with scrolled out part: ' + scrollHeight);

ไม่ใช่วิธีที่ดีนัก แต่เป็นปัญหาที่มีมาทั้งแต่นานแล้ว


Get the current scroll

DOM element มีสถานะ current scroll ของมันใน scrollLeft/scrollTop  properties

สำหรับ document แล้ว document.documentElement.scrollLeft/scrollTop สามารถใช้ได้ใน ยกเว้นใน browser เวอร์ชั่นเก่า เช่น Safari (bug 5991) ที่เราควรใช้ document.body แทนที่จะเป็น document.documentElement.

แต่โชคดีหน่อย ที่เราไม่จำเป็นต้องเขียนข้อความยาว ๆ พวกนี้ เพราะเราสามมารถใช้ได้ ใน properties พิเศษ window.pageXOffset/pageYOffset

alert('Current scroll from the top: ' + window.pageYOffset);
alert('Current scroll from the left: ' + window.pageXOffset);

พวกนี้เป็น properties read-only


Scrolling: scrollTo, scrollBy, scrollIntoView

สำหรับ element ธรรมดาแล้วเราสามารถใช้ scrollTop/scrollLeft ในการเลื่อน element ได้

เราสามารถทำกับ page ได้เหมือนกัน document.documentElement.scrollTop/scrollLeft ยกเว้นกับ Safari ที่เราต้องใช้ document.body.scrollTop/Left แทน

แต่มีอีกทางหนึ่ง ที่เราจะทำได้เหมือนกัน โดยเราจะใช้ properties พิเศษ  window.scrollBy(x,y) และ window.scrollTo(pageX,pageY).

  • method scrollBy(x,y) จะเลื่อนไปตามตำแหน่งที่เรากำหนด. ตัวอย่างเช่น, scrollBy(0,10) จะเลื่อน page ลงไป 10px
  • method scrollTo(pageX,pageY) เลื่อน page ไปอย่างอิสระ, ซึ่งจะยึดมุมซ้ายบนของ document. เหมือนกับการใช้scrollLeft/scrollTop.

method พวกนี้ใช้งานได้กับทุก Browser


scrollIntoView

เพื่อความสมบูรณ์แบบ เรามาดูอีก method หนึ่งกัน elem.scrollIntoView(top). ถ้าเราเรียกใช้ elem.scrollIntoView(top) จะทำงาน scroll elem ให้เห็น

  • ถ้า top=true (เป็น default), ดังนั้น page จะเลื่อน  elem ให้ขึ้นปรากฏที่ด้านบนของหน้าต่าง browser. ส่วนขอบของด้านบน จะชิดกับด้านบนของหน้าต่าง
  • ถ้า top=false, ดังนั้น page จะเลื่อน elem ให้ขึ้นปรากฏที่ด้านล่างของหน้าต่าง browser.

Forbid the scrolling

บางทีเราต้องการที่จะทำให้ document ไม่สามารถเลื่อนได้ ตัวอย่างเช่น เรามีข้อความเด้งขึ้นมาหน้า page และเราต้องการใช้ user ทำการโต้ตอบกับข้อความนั้น เราจึงทำให้ document ไม่สามารถเลื่อนได้

วิธีที่ทำให้ document ไม่สามารถเลื่อนได้ เราสามารถใช้ document.body.style.overflow = "hidden" ได้ เพื่อทำให้ page ไม่สามารถที่จะเลื่อนได้

  • document.body.style.overflow = ‘hidden’ ใช้เพื่อทำให้ไม่สามารถเลื่อนได้
  • document.body.style.overflow = ‘’ ใช้เพื่อทำให้เลื่อนได้อีกรอบ

Summary

Geometry:

  • ความกว้างความสูง ของ document ที่สามารถมองเห็นได้ document.documentElement.clientWidth/clientHeight

  • ความกว้างความสูง ของ document ทั้งหมด

    let scrollHeight = Math.max(
    document.body.scrollHeight, document.documentElement.scrollHeight,
    document.body.offsetHeight, document.documentElement.offsetHeight,
    document.body.clientHeight, document.documentElement.clientHeight
    );

Scrolling:

  • ในการอ่าน current scroll window.pageYOffset/pageXOffset.
  • ในการเปลี่ยน current scroll
    • window.scrollTo(pageX,pageY) – เราสามารถกำหนดได้แบบ absolute,
    • window.scrollBy(x,y) – จะเลื่อนลง/ขึ้นตามตำแหน่งปัจจุบัน,
    • elem.scrollIntoView(top) – เลื่อนเพื่อให้ elem มองเห็นได้ (จัดตำแหน่ง top/bottom ของหน้าต่าง Browser).

Coordinates

ในการเคลื่อนย้าย element เราควรคุ้นเคยกับระบบพิกัด

method ส่วนใหญ่ใน JavaScript สามารถทำงานได้กับ 2 ระบบพิกัด

  1. ยึดตามหน้าต่าง Browser - คล้ายกับ position:fixed คำนวนจากมุมซ้ายบนของหน้าต่าง
    • เราจะแสดงพิกัดเหล่านี้เป็นค่า clientX/clientY
  2. ยึกตาม document - คล้ายกับ position:absolute ใน document root, คำนวนจากมุมซ้ายบนของ document
    • เราจะแสดงพิกัดเหล่านี้เป็น pageX/pageY

เมื่อเลื่อน page ไปจนบนสุด ระยะจากบนซ้ายของ window จะมีค่าเท่ากับ ระยะจากบนซ้านของ document หมายความว่าค่าระยะจะเท่ากัน แต่หลังจากเราเลื่อน page ลงไปแล้ว ค่าที่ยึดยึดตามหน้าต่าง Browser (clientX/clientY) จะเปลี่ยน แต่ค่าที่ยึดตาม document (pageX/pageY) จะไม่เปลี่ยน

รูปภาพต่อไปนี้เป็นค่าระยะของ clientX/clientY และ pageX/pageY ของก่อนเลื่อนและหลังเลื่อน

image.png

เมื่อเราเลื่อน:

  • pageY – ค่าระยะที่ยึดกับ document จะมีค่าเท่าเดิม มันนับค่าซึ่งยึดจากด้านบนของ document
  • clientY – พิกัดที่ยึดตามหน้าต่าง Browser จะมีค่าที่เปลี่ยนแปลงไป เพราะว่ามันเข้าไปใกล้ส่วนด้านบนของ หน้าต่าง Browser

Element coordinates: getBoundingClientRect

method elem.getBoundingClientRect() จะคืนค่าพิกัดของสำหรับสี่เหลี่ยมธรรมดา ที่ครอบคลุมไว้ elem ออกมาเป็น Object ของ Built-in Class DOMRect .

properties หลักของ DOMRect :

  • x/y – พิกัด x/y ของสี่เหลี่ยม ยึดตามหน้าต่าง Browser,
  • width/height – ความกว้างความสูงของสี่เหลี่ยม (ค่าสามารถติดลบได้).

properties เพิ่มเติม:

  • top/bottom – ค่าพิกัดแกน Y ของขอบสี่เหลี่ยม
  • left/right – ค่าพิกัดแกน X ของขอบสี่เหลี่ยม

รูปภาพของ elem.getBoundingClientRect() output:

image.png

อย่างที่เห็น, x/y และ width/height ถูกอธิบายด้วย. โดยสูตรการคำนวณคือ:

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

จำไว้ว่า

  • ค่าพิกัดสามารถเป็นทศนิยมได้, เช่น 10.5. นั่นปกติ, เพราะว่า Browser ใช้เศษส่วนในการคำนวณ.
  • ค่าพิกัดสามารถเป็นค่าติดลบได้. ตัวอย่างเช่น เราเลื่อน elem ขึ้นไปอยู่เหนือหน้าต่าง Browser และเมื่อเราใช้ elem.getBoundingClientRect().top ค่าจะเป็นค่าติดลบ

elementFromPoint(x, y)

ในการเรียกใช้ document.elementFromPoint(x, y) จะคืนค่า ที่ใกล้ที่สุดตามพิกัดที่กำหนดให้

let elem = document.elementFromPoint(x, y);

ตัวอย่างโค้ดข้างล่าง คือ จะเปลี่ยนสี element ที่อยู่ตรงกลางจอ

let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);

elem.style.background = "red";
alert(elem.tagName);

ยึดตามหน้าต่าง Browser, element จะเปลี่ยนไปตามที่เราเลื่อน


Using for “fixed” positioning

หลาย ๆ ครั้งเราต้องการพิกัดที่ต้องการเพื่อทำการจัดตำแหน่งบางอย่าง

ในการโชว์ซักอย่างใกล้ element ที่เราต้องการเราสามารถใช้ getBoundingClientRect ในการดึงค่าพิกัด และใช้ CSS ในการจัดตำแหน่งได้

ตัวอย่างเช่น function createMessageUnder(elem, html) ใช้เพื่อโชว์ข้อความข้างล่าง element

let elem = document.getElementById("coords-show-mark");

function createMessageUnder(elem, html) {
// create message element
let message = document.createElement('div');
// better to use a css class for the style here
message.style.cssText = "position:fixed; color: red";

// assign coordinates, don't forget "px"!
let coords = elem.getBoundingClientRect();

message.style.left = coords.left + "px";
message.style.top = coords.bottom + "px";

message.innerHTML = html;

return message;
}

// Usage:
// add it for 5 seconds in the document
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

Summary

ทุกจดใน page มีพิกัดของมัน

  1. พิกัดที่ยึดกับหน้าต่าง Browser – elem.getBoundingClientRect().
  2. พิกัดที่ยึดกับหน้าต่าง document – elem.getBoundingClientRect() รวมกับ current page scroll

ระบบพิกัดทั้งสองอย่าง มีทั้งข้อดีและข้อเสีย; มีหลายครั้งที่เราต้องการจะใช้อย่างอื่น, ซึ่งสามารถใช้ CSS กำหนดได้ position absolute and fixed


END OF DOCUMENT