Shadow DOM 101

บทนำ

Web Components คือชุดมาตรฐานล้ำสมัยซึ่งมีคุณสมบัติดังนี้

  1. ทำให้สามารถสร้างวิดเจ็ตได้
  2. ...ซึ่งสามารถนำกลับมาใช้ใหม่ได้อย่างน่าเชื่อถือ
  3. ...และจะไม่ทำให้หน้าเสียหายหากคอมโพเนนต์เวอร์ชันถัดไป เปลี่ยนแปลงรายละเอียดการใช้งานภายใน

นั่นหมายความว่าคุณต้องตัดสินใจว่าจะใช้ HTML/JavaScript เมื่อใด และ เวลาที่ควรใช้คอมโพเนนต์เว็บ ไม่เอาด้วยหรอก HTML และ JavaScript สามารถ สิ่งที่เป็นภาพแบบอินเทอร์แอกทีฟ วิดเจ็ตคือสิ่งที่ดึงดูดสายตาแบบอินเทอร์แอกทีฟ ทั้งนี้ ควรใช้ทักษะ HTML และ JavaScript เมื่อ การพัฒนาวิดเจ็ต มาตรฐานคอมโพเนนต์เว็บได้รับการออกแบบมาเพื่อช่วย คุณก็ทำแบบนั้นได้

แต่มีปัญหาพื้นฐานที่ทำให้วิดเจ็ตที่สร้างจาก HTML และ JavaScript ใช้งานยาก: แผนผัง DOM ภายในวิดเจ็ตไม่สามารถใช้ สรุปจากส่วนที่เหลือของหน้า การขาดการห่อหุ้มนี้ หมายความว่าสไตล์ชีตของเอกสารอาจนำไปใช้กับส่วนต่างๆ โดยไม่ได้ตั้งใจ ภายในวิดเจ็ต JavaScript อาจแก้ไขส่วนต่างๆ โดยไม่ได้ตั้งใจ ภายในวิดเจ็ต รหัสของคุณอาจทับซ้อนกับรหัสภายในวิดเจ็ต เป็นต้น

คอมโพเนนต์ของเว็บประกอบด้วย 3 ส่วนดังนี้

  1. เทมเพลต
  2. Shadow DOM
  3. องค์ประกอบที่กำหนดเอง

Shadow DOM จัดการกับปัญหาการสรุปผังต้นไม้ DOM คอมโพเนนต์เว็บ 4 ส่วนได้รับการออกแบบมาให้ ทำงานร่วมกัน แต่คุณ และยังเลือกส่วนของ Web Components ที่จะใช้ได้ด้วย ช่วงเวลานี้ แสดงวิธีใช้ Shadow DOM

สวัสดี Shadow World

เมื่อใช้ Shadow DOM องค์ประกอบจะได้รับโหนดประเภทใหม่ที่เชื่อมโยงกับ ให้พวกเขา โหนดประเภทใหม่นี้เรียกว่ารูทเงา องค์ประกอบที่มีรากของเงาเชื่อมโยงอยู่จะเรียกว่าเงา เนื้อหาของโฮสต์เงาไม่แสดงผล เนื้อหาของ และจะแสดงรากของเงาแทน

เช่น หากคุณมีมาร์กอัปดังนี้

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>

จากนั้นแทนที่

<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
  Array.prototype.forEach.call(
      document.querySelectorAll(selector),
      function (node) { node.parentNode.removeChild(node); });
}

if (!HTMLElement.prototype.createShadowRoot) {
  remove('#ex1a');
  document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>

หน้าเว็บของคุณมีลักษณะ

<button id="ex1b">Hello, world!</button>
<script>
(function () {
  if (!HTMLElement.prototype.createShadowRoot) {
    remove('#ex1b');
    document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
    return;
  }
  var host = document.querySelector('#ex1b');
  var root = host.createShadowRoot();
  root.textContent = 'こんにちは、影の世界!';
})();
</script>

นอกจากนั้น หาก JavaScript ในหน้าเว็บถามถึงปุ่ม textContent จะไม่ถือว่า "こんこちは、影の世界!" แต่พูดว่า "สวัสดีทุกคน!" เนื่องจากแผนผังย่อย DOM ซึ่งจะถูกห่อหุ้มไว้ภายใต้รากของเงา

การแยกเนื้อหาออกจากงานนำเสนอ

ต่อไปเราจะดูการใช้ Shadow DOM เพื่อแยกเนื้อหาจาก งานนำเสนอ สมมติว่าเรามีแท็กชื่อ:

<style>
.ex2a.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.ex2a .boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.ex2a .name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

มาร์กอัปมีดังนี้ นี่คือสิ่งที่คุณจะเขียนในวันนี้ ไม่ได้ผล ใช้ Shadow DOM

<style>
.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

เนื่องจากต้นไม้ DOM ไม่มีการห่อหุ้ม โครงสร้างทั้งหมดของส่วน แสดงแท็กชื่อในเอกสาร หากองค์ประกอบอื่นๆ บนหน้าเว็บ ใช้ชื่อคลาสเดียวกันสำหรับการจัดรูปแบบหรือสคริปต์โดยไม่ตั้งใจ เราจะ ต้องเจอแน่ๆ

เราหลีกเลี่ยงช่วงเวลาแย่ๆ ได้

ขั้นตอนที่ 1: ซ่อนรายละเอียดงานนำเสนอ

ในเชิงความหมาย เราอาจสนใจเฉพาะเรื่องต่อไปนี้

  • เป็นป้ายชื่อ
  • ชื่อ "บ๊อบ" นะ

ก่อนอื่น เราเขียนมาร์กอัปที่ใกล้เคียงกับความหมายที่แท้จริงที่เราต้องการ ดังนี้

<div id="nameTag">Bob</div>

จากนั้นเราก็ใส่รูปแบบและ div ทั้งหมดที่ใช้ในการนำเสนอ องค์ประกอบ <template>:

<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
  border: 2px solid brown;

  … same as above …

</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div></span>
</template>

ในจุดนี้ "บ๊อบ" เป็นสิ่งเดียวที่แสดงขึ้น เนื่องจากเรา ย้ายองค์ประกอบ DOM การนำเสนอไปไว้ภายใน องค์ประกอบ <template> จะไม่แสดงผล แต่ คุณสามารถเข้าถึงโฆษณาได้จาก JavaScript เราพยายามปรับปรุง สร้างรากเงา:

<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

เมื่อเราตั้งค่ารากที่เงาแล้ว ระบบจะแสดงแท็กชื่อ อีกครั้ง ถ้าคุณคลิกขวาที่แท็กชื่อและตรวจสอบ คุณจะเห็นว่าคุณใส่มาร์กอัปที่สื่อความหมายและหวานซึ้งดังนี้

<div id="nameTag">Bob</div>

ซึ่งแสดงให้เห็นว่าด้วยการใช้ Shadow DOM เราได้ซ่อน รายละเอียดของแท็กชื่อจากเอกสาร รายละเอียดงานนำเสนอจะถูกห่อหุ้มไว้ใน Shadow DOM

ขั้นตอนที่ 2: แยกเนื้อหาออกจากงานนำเสนอ

ตอนนี้แท็กชื่อของเราจะซ่อนรายละเอียดการนำเสนอจากหน้าเว็บ แต่ ไม่ได้แยกการนำเสนอ ออกจากเนื้อหาเพราะจริงๆ แล้ว เนื้อหา (ชื่อ "บ๊อบ") อยู่ในหน้า ชื่อที่แสดง คือส่วนที่เราคัดลอกไปยังรากของเงา หากเราต้องการเปลี่ยน บนแท็กชื่อ เราจะต้องทำทั้ง 2 ที่ ทำให้ไม่ซิงค์กัน

องค์ประกอบ HTML คือองค์ประกอบ เช่น วางปุ่มไว้ภายในตาราง เป็นต้น องค์ประกอบคือสิ่งที่เราต้องการตรงนี้ แท็กชื่อต้องเป็น องค์ประกอบของพื้นหลังสีแดงที่มีคำว่า "สวัสดี" ข้อความ และเนื้อหา ที่อยู่ในแท็กชื่อ

คุณซึ่งเป็นผู้เขียนคอมโพเนนต์จะเป็นผู้กำหนดวิธีการทำงานของการเรียบเรียง โดยใช้องค์ประกอบใหม่ที่ชื่อว่า <content> ช่วงเวลานี้ สร้างจุดแทรกในงานนำเสนอของวิดเจ็ต และ จุดแทรกเนื้อหาที่สุ่มเลือกจากโฮสต์เงาจนถึงปัจจุบัน ในขณะนั้น

หากเราเปลี่ยนมาร์กอัปใน Shadow DOM เป็นดังนี้

<span class="unchanged"><template id="nameTagTemplate">
<style>
  …
</style></span>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    <content></content>
  </div>
</div>
<span class="unchanged"></template></span>

เมื่อแสดงแท็กชื่อ เนื้อหาของโฮสต์เงาจะปรากฏ ฉายภาพลงในจุดที่องค์ประกอบ <content> จะปรากฏขึ้น

คราวนี้โครงสร้างของเอกสารก็จะง่ายขึ้นเพราะชื่อ เอกสารในที่เดียว หากหน้าเว็บของคุณจำเป็นต้องอัปเดต เพียงเขียนว่า:

document.querySelector('#nameTag').textContent = 'Shellie';

เท่านี้ก็เรียบร้อย ระบบจะอัปเดตการแสดงผลของแท็กชื่อโดยอัตโนมัติ เนื่องจากเรากำลังคาดการณ์เนื้อหา วางแท็กชื่อด้วย <content> แล้ว

<div id="ex2b">

ตอนนี้เราแยกแยะระหว่างเนื้อหาและการนำเสนอแล้ว เนื้อหาอยู่ในเอกสาร งานนำเสนออยู่ใน Shadow DOM เบราว์เซอร์จะซิงค์ข้อมูลกันโดยอัตโนมัติเมื่อถึงเวลา เพื่อแสดงผลบางอย่าง

ขั้นตอนที่ 3: กำไร

การแยกเนื้อหาและการนำเสนอเข้าด้วยกัน ทำให้เราสามารถลดความซับซ้อนของ ที่ดัดแปลงเนื้อหา ซึ่งในตัวอย่างแท็กชื่อนั้น เพียงแค่ต้องจัดการกับโครงสร้างง่ายๆ ที่มี หนึ่ง <div> แทนที่จะเป็นหลายตัว

คราวนี้ถ้าเราเปลี่ยนการนำเสนอ เราคงไม่ต้องเปลี่ยนอะไร โค้ด

เช่น สมมติว่าเราต้องการแปลป้ายชื่อ แต่ยังเป็นชื่ออยู่ เพื่อให้เนื้อหาเชิงความหมายในเอกสารไม่เปลี่ยนแปลง

<div id="nameTag">Bob</div>

ส่วนรหัสการตั้งค่ารูทของเงาจะยังคงเดิม สิ่งที่จะเกิดขึ้นใน การเปลี่ยนแปลงรากที่มืด:

<template id="nameTagTemplate">
<style>
.outer {
  border: 2px solid pink;
  border-radius: 1em;
  background: url(sakura.jpg);
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
  font-family: sans-serif;
  font-weight: bold;
}
.name {
  font-size: 45pt;
  font-weight: normal;
  margin-top: 0.8em;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="name">
    <content></content>
  </div>
  と申します。
</div>
</template>

นี่เป็นการปรับปรุงครั้งสำคัญที่เกิดกับสถานการณ์บนเว็บในปัจจุบัน เนื่องจาก รหัสอัปเดตชื่อของคุณอาจขึ้นอยู่กับโครงสร้างของ component ซึ่งเรียบง่ายและสม่ำเสมอ ชื่อของคุณ โค้ดอัปเดตไม่จำเป็นต้องทราบโครงสร้างที่ใช้สำหรับ การแสดงภาพ หากเราพิจารณาสิ่งที่แสดงผล ชื่อจะปรากฏ วินาทีในภาษาอังกฤษ (ต่อจาก “Hi! "ฉันชื่อ") แต่ขึ้นต้นด้วยภาษาญี่ปุ่น (ที่อยู่ก่อน “ NUMBER申แบบสบายๆ”) ความแตกต่างนั้นไม่มีความหมาย ในแง่ของการอัปเดตชื่อที่แสดงอยู่ เพื่อให้รหัสอัปเดตชื่อไม่จำเป็นต้องรู้รายละเอียดนั้น

เครดิตเพิ่มเติม: การคาดการณ์ขั้นสูง

ในตัวอย่างข้างต้น องค์ประกอบ <content> คัดสรรเนื้อหาทั้งหมด จากพิธีกรเงา โดยการใช้ select คุณควบคุมสิ่งต่อไปนี้ได้ โปรเจ็กต์เอลิเมนต์เนื้อหา คุณยังใช้เนื้อหาหลายรายการ จากองค์ประกอบเหล่านี้

ตัวอย่างเช่น หากคุณมีเอกสารที่มีข้อมูลต่อไปนี้

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

และรากเงาซึ่งใช้ตัวเลือก CSS เพื่อเลือกเนื้อหาที่เจาะจง

<div style="background: purple; padding: 1em;">
  <div style="color: red;">
    <content **select=".first"**></content>
  </div>
  <div style="color: yellow;">
    <content **select="div"**></content>
  </div>
  <div style="color: blue;">
    <content **select=".email">**</content>
  </div>
</div>

องค์ประกอบ <div class="email"> ตรงกับทั้ง 2 อย่าง องค์ประกอบ <content select="div"> และ <content select=".email"> จำนวนครั้งที่เขาส่งอีเมลของบัญชา ที่อยู่ปรากฏขึ้น และสีใด

คำตอบคืออีเมลของปวิชจะปรากฏเพียงครั้งเดียวและเป็นสีเหลือง

อย่างที่ผู้ที่แฮ็ก Shadow DOM ทราบดีว่า การสร้างแผนผังของสิ่งที่แสดงบนหน้าจอจริงๆ เป็นเหมือน ปาร์ตี้ขนาดใหญ่ องค์ประกอบของเนื้อหา คือข้อความเชิญที่ช่วยให้ เนื้อหาจากเอกสารไปยังการแสดงผล Shadow DOM หลังเวที ปาร์ตี้ คำเชิญเหล่านี้จะส่งตามลำดับ ผู้ที่จะได้รับ คำเชิญขึ้นอยู่กับว่าส่งถึงใคร (กล่าวคือ แอตทริบิวต์ select) เนื้อหา ครั้งเดียว เชิญแล้ว จะยอมรับคำเชิญ (ใครจะไม่มีส่วนร่วม) และปิดคำเชิญ ไป หากมีการส่งคำเชิญครั้งต่อๆ ไปยังที่อยู่ดังกล่าว ไม่มีใครอยู่บ้านและไม่มีใครมางานเลี้ยงของคุณ

ในตัวอย่างด้านบน <div class="email"> ตรงกับ ทั้งตัวเลือก div และ .email แต่เนื่องจากเอลิเมนต์เนื้อหาที่มี div มาก่อนในเอกสาร <div class="email"> ไปงานปาร์ตี้สีเหลือง และ ไม่มีใครมางานปาร์ตี้สีน้ำเงินได้ (อาจ ทำไมถึงเป็นสีฟ้า แม้ว่าความทุกข์ทรมานจะรักบริษัท ไม่เคยรู้มาก่อน)

หากมีคนได้รับเชิญให้ไม่มีปาร์ตี้ กิจกรรมดังกล่าวจะไม่ได้รับโอกาส แสดงผลเลย นี่คือสิ่งที่เกิดขึ้นกับข้อความ "สวัสดีชาวโลก" ใน เป็นตัวอย่างแรก ซึ่งจะมีประโยชน์เมื่อต้องการบรรลุเป้าหมาย การแสดงภาพที่แตกต่างอย่างสิ้นเชิง: เขียนแบบจำลองทางอรรถศาสตร์ใน ซึ่งเป็นสิ่งที่สคริปต์เข้าถึงในหน้าเว็บได้แต่ซ่อนไว้ เพื่อการแสดงผลและเชื่อมต่อกับ โมเดลการแสดงผลใน Shadow DOM โดยใช้ JavaScript

ตัวอย่างเช่น HTML มีเครื่องมือเลือกวันที่ที่เหมาะสม ถ้าคุณเขียน <input type="date"> คุณจะได้รับปฏิทินแบบป๊อปอัปที่สวยงาม แต่ถ้าคุณ ต้องการให้ผู้ใช้เลือกช่วงวันที่ของหวาน วันหยุดพักผ่อนบนเกาะ (มีเปลญวนที่ทำจาก Red Vines) คุณ ตั้งค่าเอกสารของคุณด้วยวิธีต่อไปนี้

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

แต่สร้าง Shadow DOM ที่ใช้ตารางเพื่อสร้างปฏิทินที่ราบรื่น ซึ่งจะไฮไลต์ช่วงวันที่เป็นต้น เมื่อผู้ใช้คลิกที่ วันในปฏิทิน คอมโพเนนต์จะอัปเดตสถานะ อินพุต startDate และ endDate เมื่อผู้ใช้ส่งแบบฟอร์ม ระบบจะส่งค่าจากองค์ประกอบอินพุตเหล่านั้น

ทำไมฉันถึงใส่ป้ายกำกับในเอกสารทั้งที่ระบบจะไม่ใส่ป้ายกำกับ ที่แสดงผลหรือไม่ สาเหตุคือหากผู้ใช้ดูแบบฟอร์มโดยใช้เบราว์เซอร์ ที่ไม่รองรับ Shadow DOM แต่แบบฟอร์มจะยังคงใช้ได้ แต่ไม่ น่ารัก ผู้ใช้จะเห็นสิ่งต่อไปนี้

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

คุณผ่าน Shadow DOM 101

นี่คือข้อมูลเบื้องต้นของ Shadow DOM คุณผ่าน Shadow DOM 101 แล้ว! คุณสามารถ ทำสิ่งต่างๆ ได้มากขึ้นกับ Shadow DOM เช่น คุณสามารถใช้เงาหลายรายการ โฮสต์เงาหนึ่งโฮสต์ หรือเงาที่ซ้อนกันสำหรับการห่อหุ้ม หรือแสงสถาปนิก หน้าเว็บของคุณโดยใช้มุมมองที่เป็นไปตามรูปแบบ (MDV) และ Shadow DOM และเว็บ คอมโพเนนต์เป็นมากกว่า Shadow DOM

เราจะอธิบายเรื่องนี้ในโพสต์ต่อๆ ไป