- บทความนี้กล่าวถึงการฝึกฝนการจัดการ Document Object Model (DOM) ให้เชี่ยวชาญด้วย Vanilla JavaScript
- แม้จะมีการใช้เครื่องมือสมัยใหม่อย่าง Angular, VueJS, React, Solid และ Svelte ก็ยังเน้นย้ำถึงความสำคัญของการเข้าใจ DOM API ของเบราว์เซอร์สำหรับการพัฒนาเว็บ
- ผู้เขียนได้รวบรวมแหล่งข้อมูลที่ประกอบด้วย API พื้นฐานของเบราว์เซอร์ ตัวอย่างที่เข้าใจง่าย เดโมแบบเรียลไทม์ เคล็ดลับและแนวทางปฏิบัติที่ดี กรณีใช้งานจริง ตลอดจนความเข้ากันได้กับเบราว์เซอร์สมัยใหม่และ Internet Explorer
- แหล่งข้อมูลเหล่านี้แบ่งออกเป็น 3 ระดับ ได้แก่ พื้นฐาน ระดับกลาง และระดับสูง
- ระดับพื้นฐานครอบคลุมงานอย่างการเปลี่ยน favicon แบบไดนามิกตามค่ากำหนดโทนสีของผู้ใช้ การคำนวณเวลาอ่านของหน้าเว็บ และการอัปเดตตัวแปรรากของ CSS แบบไดนามิก
- ระดับกลางครอบคลุมงานอย่างการลบการเยื้องในพื้นที่ข้อความด้วยคีย์ผสม Shift+Tab การเข้าสู่โหมดเต็มหน้าจอ และการสร้าง spin input
- ระดับสูงครอบคลุมงานอย่างการสร้างเคอร์เซอร์แบบกำหนดเอง การ sanitize สตริง HTML และการสร้าง scrollbar แบบกำหนดเอง
- ผู้เขียนยังมีเคล็ดลับ เช่น การหลีกเลี่ยงการเลื่อนของเลย์เอาต์เมื่อเปิด modal และการหากรอบสี่เหลี่ยมขอบเขตของ text node
- บทความปิดท้ายด้วยการเชิญชวนให้สมัครรับจดหมายข่าวของผู้เขียนเกี่ยวกับคอนเทนต์เทคนิคฝั่งฟรอนต์เอนด์
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
รายการตัวอย่างยอดเยี่ยมมาก แค่ไล่ดูไม่กี่อันก็รู้สึกทึ่งกับ ความใส่ใจและรายละเอียด
เวลาเปลี่ยนจากแพลตฟอร์มอย่าง React ไปใช้การทำงานที่เล็กและมินิมอลกว่า มักจะติดอยู่บ่อย ๆ ว่าจะจัดการแพตเทิร์นซับซ้อนด้วย JS มาตรฐานอย่างไร แต่พอเห็นโค้ดจริงก็มีหลายครั้งที่คิดว่า “ง่ายกว่าการส่งพร็อพเพอร์ตีจำนวนมากเข้าไลบรารียักษ์มากเลย”
แม้แต่ JS framework แบบมินิมอล หากคำขอฟีเจอร์ที่ดูบริสุทธิ์สะสมไปเรื่อย ๆ สุดท้ายก็มักกลายเป็นสัตว์ประหลาดของ hook, event และการปะซ่อม side effect
ช่วงนี้ไม่ใช้แม้แต่ jQuery และของอย่าง ES modules ก็ใช้เพื่อจัดระเบียบโค้ด แต่ไม่จำเป็นต้องรันคำสั่ง npm แต่อย่างใด นี่ไม่ใช่ของที่ต้องดึงเข้ามาเป็น vendor แต่เป็นมาตรฐานเบราว์เซอร์แบบ global
ทุกวันนี้มอง MDN เหมือนคัมภีร์ ถ้าเชื่อแล้วเดินตามไปก็มักพาไปได้เกือบสุดทาง ส่วน framework กลับเริ่มเป็นตัวถ่วงตั้งแต่จุดหนึ่ง เมื่อ 5 ปีก่อนยังไม่ได้ขนาดนี้ แต่ browser API ได้คอนเวิร์จอย่างรวดเร็วไปสู่ primitive พื้นฐานที่ทรงพลังและสอดคล้องกัน เช่น modules, flexbox, grid
เช่น ตัวอย่างส่วนใหญ่ในต้นฉบับใกล้เคียงกับโค้ดที่ยังต้องเขียนเองแม้จะใช้ React
กำลังทำ side project ที่ยังไม่มีชื่อ คล้าย “vim สำหรับเว็บดีไซเนอร์” โดยสร้างเว็บไซต์หรือคอมโพเนนต์ในเบราว์เซอร์เหมือน Webflow แต่ทุกอย่างขับเคลื่อนด้วย ภาษาคำสั่ง
ตอนแรกตั้งใจว่าจะไม่ใช้ framework ทำงานนี้มาตั้งแต่ปี 2009 และใช้ JS มานานก่อนจะจับ framework จึงคิดว่า native DOM API ก็น่าจะพอ
แต่การย้ายข้อมูลเข้าออก DOM น่าเบื่อและเกิดข้อผิดพลาดมาก จนเพื่อสุขภาพจิตสุดท้ายก็เริ่มเขียน mini framework ใช้เอง ตอนนั้นถึงนึกออกว่าทำไมแรกเริ่มถึงมีการใช้ framework กัน สุดท้ายแล้วก็ต้องใช้ framework ในรูปแบบใดรูปแบบหนึ่งอยู่ดี ต่างกันแค่ว่าเป็นของทำเองแบบปรับเฉพาะทาง หรือมาตรฐานอุตสาหกรรมที่ชุมชนสร้าง
ถ้าเป็นไซต์ที่มี interaction น้อย native DOM ก็ดี และไซต์ส่วนใหญ่ที่ใช้ React จริง ๆ อาจไม่จำเป็นต้องใช้ด้วยซ้ำ แต่ถ้าผลิตภัณฑ์มี nuance หรือความซับซ้อนแม้แต่นิดเดียว ผมจะใช้ framework แล้วเลี่ยงความยุ่งยาก สุดท้ายย้ายไป Svelte และพอใจกว่ามาก
ถ้าเป็นเว็บไซต์ที่ต้องมี interaction การจัดการ DOM อาจเป็นตัวเลือกที่ดีกว่า เพราะใช้เทคโนโลยีที่พื้นฐานและเป็นสากลกว่า กระบวนการ build เบาหรือไม่มีเลย รันเร็ว และชัดเจน
แน่นอนว่ามีพื้นที่สีเทาเยอะ แต่กรณีนี้เข้าหมวดที่ framework ให้ประโยชน์พอดี
แค่
value.startsWith('javascript:')อย่างเดียวทำให้เกิดช่องโหว่ได้ เพราะหน้า URL อาจมี whitespace ใด ๆ ก็ได้ อย่างน้อยจึงต้องใช้value.trim().startsWith('javascript:')ถึงอย่างนั้นก็คิดว่า allowlist ดีกว่า blocklist ควรอนุญาตแค่
http:,https:และถ้าจำเป็นก็mailto:,ftp:,sftp:ประมาณนี้ ส่วน relative URL ที่ขึ้นต้นด้วย/ก็อาจอนุญาตได้แต่ในทางปฏิบัติต้องจัดการ attribute ทุกตัวที่อาจเป็น URL ได้อย่างถูกต้อง สุดท้ายแนวทางที่ดีกว่าน่าจะเป็นการอนุญาตเฉพาะแท็กบางชนิดกับ attribute ของมันเท่านั้น
['http:', 'https:', 'mailto:', 'tel:'].includes(new URL(value, location.origin).protocol)[1] https://caniuse.com/url
บทความนี้ไม่ควรทำให้รู้สึกว่า การ sanitize HTML เป็นเรื่องง่ายขนาดนั้น แม้ท้ายบทความจะพูดถึง https://github.com/cure53/DOMPurify แต่ควรพูดให้หนักแน่นกว่านี้มากว่าอย่าพยายามเขียนเอง และควรแนะนำ DOMPurify เท่านั้น
ได้เปิด issue เกี่ยวกับเรื่องนี้ไว้แล้ว: https://github.com/phuocng/html-dom/issues/281
ดูเป็นรายการที่มีประโยชน์ถ้ารู้ไว้ แม้ยังไม่ได้ดูทั้งหมด แต่เท่าที่เห็นตอนนี้ก็ดูโอเค
ลองกดตัวอย่างแรกแล้วพบว่ารัน
window.matchMedia('(prefers-color-scheme: dark)')สองครั้งโดยไม่จำเป็นถ้าจะใช้
setFaviconเป็น callback ของ event listener ก็ใช้event.matchesจากพารามิเตอร์eventที่ส่งเข้ามาใน callback ได้โดยส่วนตัวชอบให้จัดการใน HTML มากกว่า เพราะรองรับผู้ใช้ที่ปิด JS ด้วย
https://phuoc.ng/collection/html-dom/change-the-favicon-dyna...
theme-colorได้เช่นกัน เป็นฟีเจอร์ที่ดีมากแม้ นักพัฒนา PWA ที่ใส่ใจรายละเอียดจะไม่ค่อยใช้กันเทคนิค “textarea ขยายอัตโนมัติ” กำลังล้าสมัยเพราะพร็อพเพอร์ตี CSS ใหม่อย่าง form-sizing
https://chriscoyier.net/2023/09/29/css-solves-auto-expanding...
แม้จะไม่ใช้พร็อพเพอร์ตี CSS ใหม่นี้ วิธีปรับ
heightขององค์ประกอบก็ไม่เหมาะนักและอาจกระตุกได้แนวทางที่ดีกว่าคือใช้องค์ประกอบกระจกที่ซ่อนไว้
https://css-tricks.com/the-cleanest-trick-for-autogrowing-te...
หลายอย่างในนี้ใช้ API ที่เก่าและดูฝืน ๆ ทั้งที่มี native API ที่ดีกว่า
ตัวอย่างเช่น “การแทนที่องค์ประกอบ” เขียนไว้เป็น
ele.parentNode.replaceChild(newEle, ele);แต่ถ้าไม่ได้ตั้งเป้ารองรับ Chrome ต่ำกว่า 54, Firefox ต่ำกว่า 49, Safari ต่ำกว่า 10 ก็ใช้ele.replaceWith(newEle)ได้เลยreplaceWith ยังสามารถแทนที่โหนดหนึ่งด้วยหลายโหนดหรือข้อความได้ด้วย
เช่นเดียวกัน “การวนผ่าน NodeList” ก็แนะนำให้กระจายเป็นอาร์เรย์แล้วใช้
forEachแต่จริง ๆ แค่for (let ele of nodelist) ...ก็พอดีเลย บุ๊กมาร์กไว้แล้ว แหล่งข้อมูลดี ๆ แนวเดียวกันยังมี https://youmightnotneedjquery.com/
ผมไม่ได้ทำงานเว็บบ่อยนัก แต่ทุกครั้งที่ทำก็หมดกำลังใจสุด ๆ กับ อาการใช้เฟรมเวิร์กเกินจำเป็น ของฝั่งนั้น การที่โปรเจกต์ React ใหม่เริ่มต้นด้วย dependency หลายร้อยตัวนี่มันเกินควบคุมไปแล้ว และน้ำหนักของความซับซ้อนนั้นก็น่าตกใจ
ในทางกลับกัน เทคโนโลยีเบราว์เซอร์ข้ามแพลตฟอร์มที่มีมาในตัวกลับทรงพลังยิ่งกว่าที่เคย และโค้ดที่ใช้ทำงานทั่วไปก็ไม่ได้เยอะขนาดนั้น
นักพัฒนา frontend ยังโอเคกันอยู่ไหม?
เช่น การจัดการสถานะแอปพลิเคชัน และ routing เป็นส่วนใหญ่ที่ต้องสร้างเองถ้าใช้แต่ DOM API ล้วน ๆ
ขึ้นอยู่กับความซับซ้อนของแอป มันอาจไม่ใช่เรื่องใหญ่ แต่พอเลยแอปพื้นฐานไปแล้ว มูลค่าของฟีเจอร์เสริมเหล่านี้จะเพิ่มขึ้นอย่างรวดเร็ว เพียงแต่ถ้าใช้สิ่งที่ต้องพึ่ง NPM ก็จะกลายเป็นเรื่องปวดหัว
ในฐานะนักพัฒนา frontend ที่ชอบแพลตฟอร์มพื้นฐานและความสามารถของมัน ผมดีใจที่มีสิ่งดี ๆ เข้ามา เช่น CSS แบบ nesting, container queries และประสบการณ์ JS ที่ดีขึ้นโดยรวมหลัง ES6
พร้อมกันนั้นก็แปลกใจที่คนจำนวนมากใช้คอมโพเนนต์แม้กับเรื่องเล็กน้อยอย่างปุ่มหรือตัวหนา
ผมชอบตัวเฟรมเวิร์กเอง แต่การตลาดของเฟรมเวิร์กและไลบรารีจำนวนมากอาศัยความเชื่อว่า CSS และการจัดการ DOM ล้วน ๆ นั้นยากสุด ๆ และการดึงแพ็กเกจอะไรก็ได้จาก NPM ไม่เพียงรวดเร็วและง่าย แต่ยังเป็นงานแบบมืออาชีพด้วย
แค่สิ่งอย่าง Kubernetes, Helm, Terraform, CircleCI ก็เห็นได้ว่าปริมาณคำศัพท์ที่ซ้อนกันเป็นชั้น ๆ นั้นเกินเหตุ
ผมแปลกใจมากตอนรู้ว่าฟังก์ชัน DOM หลายตัวคืนค่าเป็น live HTMLCollection ที่อัปเดตอัตโนมัติ
สงสัยว่าก่อนกระแสเฟรมเวิร์กบูม หรือช่วงก่อน/หลังที่แนวทางกลับไปพัฒนาโดยไม่ใช้เฟรมเวิร์กเริ่มได้รับความนิยมขึ้นมาบ้าง ฟีเจอร์นี้ถูกใช้ในโค้ด production จริงจังแค่ไหน
ของเก่ากลับมาเป็นของใหม่อีกครั้ง งาน frontend แรกของผมคือการสร้างเว็บแอปพลิเคชันในปี 1999
ฟีเจอร์เจ๋ง ๆ อย่างการลากและวาง tree view, คลิกเพื่อแก้ไข, และสิ่งที่แทบจะเป็น single-page app ทั้งหมดทำด้วย JS “Vanilla”
ส่วนที่ยากที่สุดคือการปรับความต่างระหว่าง Netscape กับ Internet Explorer ให้เข้ากัน
ทำไมตอนนี้เราดูเหมือนไม่รู้วิธีทำเรื่องพวกนั้นแล้ว ;]
ฟีเจอร์ JavaScript ล้วน ๆ ที่ผมชอบที่สุดตอนนี้คือ Element.insertAdjacentHTML ซึ่งมีมาตั้งแต่ยุค Chrome 1.0 และ IE
โดยเฉพาะเมื่อใช้ร่วมกับสตริงหลายบรรทัดแบบ backtick มันมีประโยชน์เกินกว่าจะบอกว่าแค่มีประโยชน์ ไม่น่าเชื่อว่ามีมาตั้งนานขนาดนี้ แต่ผมเพิ่งรู้จักเมื่อประมาณหนึ่งปีก่อน
insertAdjacentHTML(position, text)จะพาร์สข้อความที่ระบุเป็น HTML หรือ XML แล้วแทรกโหนดที่ได้เข้าไปยังตำแหน่งที่กำหนดใน DOM treeถ้าทำด้วย DOM ล้วน ๆ เดิมทีต้องสร้างองค์ประกอบ
li, สร้างองค์ประกอบa, ผูกเป็นลูก, ตั้งค่าแอตทริบิวต์href, ใส่ค่าข้อความ แล้วค่อยแทรกเข้าไปในulหรือไม่ก็ต้องรวมเป็นสตริงใหญ่แล้วใส่ด้วยinnerHTMLและการแก้ไขรายการเดิมก็ยิ่งเจ็บปวดและยืดยาวกว่าเดิม