6 คะแนน โดย GN⁺ 2023-11-07 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • บทความนี้กล่าวถึงการฝึกฝนการจัดการ 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 ความคิดเห็น

 
GN⁺ 2023-11-07
ความคิดเห็นจาก Hacker News
  • รายการตัวอย่างยอดเยี่ยมมาก แค่ไล่ดูไม่กี่อันก็รู้สึกทึ่งกับ ความใส่ใจและรายละเอียด
    เวลาเปลี่ยนจากแพลตฟอร์มอย่าง React ไปใช้การทำงานที่เล็กและมินิมอลกว่า มักจะติดอยู่บ่อย ๆ ว่าจะจัดการแพตเทิร์นซับซ้อนด้วย JS มาตรฐานอย่างไร แต่พอเห็นโค้ดจริงก็มีหลายครั้งที่คิดว่า “ง่ายกว่าการส่งพร็อพเพอร์ตีจำนวนมากเข้าไลบรารียักษ์มากเลย”

    • หลังจากผ่านอัปเดตใหญ่ของ RiotJS มา 3–4 ครั้ง ก็ได้ข้อสรุปคล้ายกัน
      แม้แต่ JS framework แบบมินิมอล หากคำขอฟีเจอร์ที่ดูบริสุทธิ์สะสมไปเรื่อย ๆ สุดท้ายก็มักกลายเป็นสัตว์ประหลาดของ hook, event และการปะซ่อม side effect
      ช่วงนี้ไม่ใช้แม้แต่ jQuery และของอย่าง ES modules ก็ใช้เพื่อจัดระเบียบโค้ด แต่ไม่จำเป็นต้องรันคำสั่ง npm แต่อย่างใด นี่ไม่ใช่ของที่ต้องดึงเข้ามาเป็น vendor แต่เป็นมาตรฐานเบราว์เซอร์แบบ global
      ทุกวันนี้มอง MDN เหมือนคัมภีร์ ถ้าเชื่อแล้วเดินตามไปก็มักพาไปได้เกือบสุดทาง ส่วน framework กลับเริ่มเป็นตัวถ่วงตั้งแต่จุดหนึ่ง เมื่อ 5 ปีก่อนยังไม่ได้ขนาดนี้ แต่ browser API ได้คอนเวิร์จอย่างรวดเร็วไปสู่ primitive พื้นฐานที่ทรงพลังและสอดคล้องกัน เช่น modules, flexbox, grid
    • ตัวอย่างในต้นฉบับไม่ได้มีลักษณะไปแข่งกับ web client framework ปัญหาทั่วไปที่ framework แก้คือ การจัดการ state และการอัปเดต UI เมื่อ state เปลี่ยน
      เช่น ตัวอย่างส่วนใหญ่ในต้นฉบับใกล้เคียงกับโค้ดที่ยังต้องเขียนเองแม้จะใช้ React
  • กำลังทำ side project ที่ยังไม่มีชื่อ คล้าย “vim สำหรับเว็บดีไซเนอร์” โดยสร้างเว็บไซต์หรือคอมโพเนนต์ในเบราว์เซอร์เหมือน Webflow แต่ทุกอย่างขับเคลื่อนด้วย ภาษาคำสั่ง
    ตอนแรกตั้งใจว่าจะไม่ใช้ framework ทำงานนี้มาตั้งแต่ปี 2009 และใช้ JS มานานก่อนจะจับ framework จึงคิดว่า native DOM API ก็น่าจะพอ
    แต่การย้ายข้อมูลเข้าออก DOM น่าเบื่อและเกิดข้อผิดพลาดมาก จนเพื่อสุขภาพจิตสุดท้ายก็เริ่มเขียน mini framework ใช้เอง ตอนนั้นถึงนึกออกว่าทำไมแรกเริ่มถึงมีการใช้ framework กัน สุดท้ายแล้วก็ต้องใช้ framework ในรูปแบบใดรูปแบบหนึ่งอยู่ดี ต่างกันแค่ว่าเป็นของทำเองแบบปรับเฉพาะทาง หรือมาตรฐานอุตสาหกรรมที่ชุมชนสร้าง
    ถ้าเป็นไซต์ที่มี interaction น้อย native DOM ก็ดี และไซต์ส่วนใหญ่ที่ใช้ React จริง ๆ อาจไม่จำเป็นต้องใช้ด้วยซ้ำ แต่ถ้าผลิตภัณฑ์มี nuance หรือความซับซ้อนแม้แต่นิดเดียว ผมจะใช้ framework แล้วเลี่ยงความยุ่งยาก สุดท้ายย้ายไป Svelte และพอใจกว่ามาก

    • โปรเจกต์ที่มินิมอลสุด ๆ สุดท้ายก็มักจะต้อง implement ครึ่งหนึ่งของ jquery หรือ lodash ขึ้นมาใหม่ หาไลบรารีดี ๆ อย่าง ramda ที่ทำ tree shaking ได้ดี แล้วใช้เท่าที่จำเป็นจะง่ายกว่ามาก
    • คำว่า “ถ้าไม่เลือก framework สักตัว สุดท้ายก็จะสร้างมันเอง” นั้นตรงมาก
    • กรณีนี้เป็นการสร้างแอปพลิเคชัน ดังนั้นแค่ฟังคำอธิบายก็เดาได้ว่าการห่อ DOM interaction ไว้จะดีกว่า
      ถ้าเป็นเว็บไซต์ที่ต้องมี interaction การจัดการ DOM อาจเป็นตัวเลือกที่ดีกว่า เพราะใช้เทคโนโลยีที่พื้นฐานและเป็นสากลกว่า กระบวนการ build เบาหรือไม่มีเลย รันเร็ว และชัดเจน
      แน่นอนว่ามีพื้นที่สีเทาเยอะ แต่กรณีนี้เข้าหมวดที่ framework ให้ประโยชน์พอดี
    • ช่วงนี้ SvelteKit เน้น SSR และตัว Svelte เองไม่มี client-side router เริ่มต้น เคยลองใช้ Svelte กับแอป CSR แบบหน้าเดียวแล้วก็ล้มเลิกไป
  • แค่ value.startsWith('javascript:') อย่างเดียวทำให้เกิดช่องโหว่ได้ เพราะหน้า URL อาจมี whitespace ใด ๆ ก็ได้ อย่างน้อยจึงต้องใช้ value.trim().startsWith('javascript:')
    ถึงอย่างนั้นก็คิดว่า allowlist ดีกว่า blocklist ควรอนุญาตแค่ http:, https: และถ้าจำเป็นก็ mailto:, ftp:, sftp: ประมาณนี้ ส่วน relative URL ที่ขึ้นต้นด้วย / ก็อาจอนุญาตได้
    แต่ในทางปฏิบัติต้องจัดการ attribute ทุกตัวที่อาจเป็น URL ได้อย่างถูกต้อง สุดท้ายแนวทางที่ดีกว่าน่าจะเป็นการอนุญาตเฉพาะแท็กบางชนิดกับ attribute ของมันเท่านั้น

    • ถ้าไม่จำเป็นต้องรองรับเบราว์เซอร์เก่ามาก [1] วิธีง่าย ๆ คือให้เบราว์เซอร์เป็นคน parse URL เช่นจัดการแบบ ['http:', 'https:', 'mailto:', 'tel:'].includes(new URL(value, location.origin).protocol)
      [1] https://caniuse.com/url
    • สิ่งที่พูดถึงตรงนี้น่าจะเป็น “Sanitize HTML strings” ในบทความนี้: https://phuoc.ng/collection/html-dom/sanitize-html-strings/
      บทความนี้ไม่ควรทำให้รู้สึกว่า การ 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...

    • ถ้าจัดการใน HTML ก็ช่วยกันอาการเนื้อหาธีมอื่นกะพริบขึ้นมาชั่วครู่ได้ด้วย วิธีเดียวกันนี้ใช้กับ meta 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) ... ก็พอ

    • ต้องจำไว้ว่า NodeList อาจเป็นอ็อบเจ็กต์แบบ live ได้ หากแก้ไขรายการนั้นระหว่างวนลูป อาจได้ผลลัพธ์ที่ไม่คาดคิด
  • ดีเลย บุ๊กมาร์กไว้แล้ว แหล่งข้อมูลดี ๆ แนวเดียวกันยังมี https://youmightnotneedjquery.com/
    ผมไม่ได้ทำงานเว็บบ่อยนัก แต่ทุกครั้งที่ทำก็หมดกำลังใจสุด ๆ กับ อาการใช้เฟรมเวิร์กเกินจำเป็น ของฝั่งนั้น การที่โปรเจกต์ React ใหม่เริ่มต้นด้วย dependency หลายร้อยตัวนี่มันเกินควบคุมไปแล้ว และน้ำหนักของความซับซ้อนนั้นก็น่าตกใจ
    ในทางกลับกัน เทคโนโลยีเบราว์เซอร์ข้ามแพลตฟอร์มที่มีมาในตัวกลับทรงพลังยิ่งกว่าที่เคย และโค้ดที่ใช้ทำงานทั่วไปก็ไม่ได้เยอะขนาดนั้น
    นักพัฒนา frontend ยังโอเคกันอยู่ไหม?

    • ผมไม่ได้ชอบ React หรือ npm เลย แต่ก็คิดว่าเฟรมเวิร์กบางตัว ยังมีที่ยืนอยู่ เพราะให้สิ่งที่มีประโยชน์ซึ่ง DOM API ล้วน ๆ ไม่มี
      เช่น การจัดการสถานะแอปพลิเคชัน และ routing เป็นส่วนใหญ่ที่ต้องสร้างเองถ้าใช้แต่ DOM API ล้วน ๆ
      ขึ้นอยู่กับความซับซ้อนของแอป มันอาจไม่ใช่เรื่องใหญ่ แต่พอเลยแอปพื้นฐานไปแล้ว มูลค่าของฟีเจอร์เสริมเหล่านี้จะเพิ่มขึ้นอย่างรวดเร็ว เพียงแต่ถ้าใช้สิ่งที่ต้องพึ่ง NPM ก็จะกลายเป็นเรื่องปวดหัว
    • แหล่งข้อมูลยอดเยี่ยมอีกแห่งในแนวเดียวกันคือ https://javascript.info/ ที่ดีมาก
      ในฐานะนักพัฒนา frontend ที่ชอบแพลตฟอร์มพื้นฐานและความสามารถของมัน ผมดีใจที่มีสิ่งดี ๆ เข้ามา เช่น CSS แบบ nesting, container queries และประสบการณ์ JS ที่ดีขึ้นโดยรวมหลัง ES6
      พร้อมกันนั้นก็แปลกใจที่คนจำนวนมากใช้คอมโพเนนต์แม้กับเรื่องเล็กน้อยอย่างปุ่มหรือตัวหนา
      ผมชอบตัวเฟรมเวิร์กเอง แต่การตลาดของเฟรมเวิร์กและไลบรารีจำนวนมากอาศัยความเชื่อว่า CSS และการจัดการ DOM ล้วน ๆ นั้นยากสุด ๆ และการดึงแพ็กเกจอะไรก็ได้จาก NPM ไม่เพียงรวดเร็วและง่าย แต่ยังเป็นงานแบบมืออาชีพด้วย
    • เห็นด้วยว่าเกินควบคุมไปแล้ว แต่ในช่วงปีที่ผ่านมา ผมย้ายไปทีมแพลตฟอร์มและเริ่มจับเครื่องมือฝั่ง DevOps เป็นครั้งแรก ความซับซ้อนของมันก็ไม่น้อยหน้า frontend เลย
      แค่สิ่งอย่าง Kubernetes, Helm, Terraform, CircleCI ก็เห็นได้ว่าปริมาณคำศัพท์ที่ซ้อนกันเป็นชั้น ๆ นั้นเกินเหตุ
    • สงสัยว่าเคยทำอะไรที่ไม่ใช่เรื่องเล็ก ๆ ด้วย “เทคโนโลยีข้ามแพลตฟอร์มที่ทรงพลัง” เหล่านี้ไหม และ React ก็ไม่ใช่ตัวเลือกเดียว
  • ผมแปลกใจมากตอนรู้ว่าฟังก์ชัน DOM หลายตัวคืนค่าเป็น live HTMLCollection ที่อัปเดตอัตโนมัติ
    สงสัยว่าก่อนกระแสเฟรมเวิร์กบูม หรือช่วงก่อน/หลังที่แนวทางกลับไปพัฒนาโดยไม่ใช้เฟรมเวิร์กเริ่มได้รับความนิยมขึ้นมาบ้าง ฟีเจอร์นี้ถูกใช้ในโค้ด production จริงจังแค่ไหน

  • ของเก่ากลับมาเป็นของใหม่อีกครั้ง งาน frontend แรกของผมคือการสร้างเว็บแอปพลิเคชันในปี 1999
    ฟีเจอร์เจ๋ง ๆ อย่างการลากและวาง tree view, คลิกเพื่อแก้ไข, และสิ่งที่แทบจะเป็น single-page app ทั้งหมดทำด้วย JS “Vanilla”
    ส่วนที่ยากที่สุดคือการปรับความต่างระหว่าง Netscape กับ Internet Explorer ให้เข้ากัน

    • สงสัยว่าตอนนั้นจัดการ state และการเปลี่ยนแปลงของมันอย่างไร
      ทำไมตอนนี้เราดูเหมือนไม่รู้วิธีทำเรื่องพวกนั้นแล้ว ;]
  • ฟีเจอร์ JavaScript ล้วน ๆ ที่ผมชอบที่สุดตอนนี้คือ Element.insertAdjacentHTML ซึ่งมีมาตั้งแต่ยุค Chrome 1.0 และ IE
    โดยเฉพาะเมื่อใช้ร่วมกับสตริงหลายบรรทัดแบบ backtick มันมีประโยชน์เกินกว่าจะบอกว่าแค่มีประโยชน์ ไม่น่าเชื่อว่ามีมาตั้งนานขนาดนี้ แต่ผมเพิ่งรู้จักเมื่อประมาณหนึ่งปีก่อน
    insertAdjacentHTML(position, text) จะพาร์สข้อความที่ระบุเป็น HTML หรือ XML แล้วแทรกโหนดที่ได้เข้าไปยังตำแหน่งที่กำหนดใน DOM tree
    ถ้าทำด้วย DOM ล้วน ๆ เดิมทีต้องสร้างองค์ประกอบ li, สร้างองค์ประกอบ a, ผูกเป็นลูก, ตั้งค่าแอตทริบิวต์ href, ใส่ค่าข้อความ แล้วค่อยแทรกเข้าไปใน ul หรือไม่ก็ต้องรวมเป็นสตริงใหญ่แล้วใส่ด้วย innerHTML และการแก้ไขรายการเดิมก็ยิ่งเจ็บปวดและยืดยาวกว่าเดิม

    1. https://developer.mozilla.org/en-US/docs/Web/API/Element/ins...