เทมเพลต สล็อต และเงา

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

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

ในส่วนนี้ เราจะสร้างเอลิเมนต์ <star-rating> ซึ่งเป็นคอมโพเนนต์เว็บที่ช่วยให้ผู้ใช้ให้คะแนนประสบการณ์ใช้งาน ตั้งแต่ 1-5 ดาว เมื่อตั้งชื่อองค์ประกอบที่กำหนดเอง เราแนะนำให้ใช้ตัวอักษรตัวพิมพ์เล็กทั้งหมด นอกจากนี้ ให้ใส่ขีดกลาง เพราะจะช่วยแยกความแตกต่างระหว่างองค์ประกอบปกติและองค์ประกอบที่กำหนดเอง

เราจะพูดถึงการใช้องค์ประกอบ <template> และ <slot>, แอตทริบิวต์ slot และ JavaScript เพื่อสร้างเทมเพลตด้วย Shadow DOM ที่ห่อหุ้มไว้ จากนั้นเราจะใช้องค์ประกอบที่กำหนดไว้อีกครั้ง โดยปรับแต่งส่วนของข้อความ เช่นเดียวกับเอลิเมนต์อื่นๆ หรือคอมโพเนนต์เว็บ เราจะพูดคุยสั้นๆ เกี่ยวกับการใช้ CSS ทั้งภายในและภายนอกองค์ประกอบที่กำหนดเอง

องค์ประกอบ <template>

องค์ประกอบ <template> ใช้เพื่อประกาศ Fragment ของ HTML ที่จะโคลนและแทรกลงใน DOM ด้วย JavaScript เนื้อหาขององค์ประกอบจะไม่แสดงผลโดยค่าเริ่มต้น แต่จะมีการสร้างอินสแตนซ์โดยใช้ JavaScript

<template id="star-rating-template">
  <form>
    <fieldset>
      <legend>Rate your experience:</legend>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required />
        <input type="radio" name="rating" value="2" aria-label="2 stars" />
        <input type="radio" name="rating" value="3" aria-label="3 stars" />
        <input type="radio" name="rating" value="4" aria-label="4 stars" />
        <input type="radio" name="rating" value="5" aria-label="5 stars" />
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

เนื่องจากไม่มีการเขียนเนื้อหาขององค์ประกอบ <template> ลงบนหน้าจอ จึงไม่แสดงผล <form> และเนื้อหาขององค์ประกอบนั้น ใช่ Codepen นี้ว่างเปล่า แต่หากคุณตรวจสอบแท็บ HTML คุณจะเห็นมาร์กอัป <template>

ในตัวอย่างนี้ <form> ไม่ใช่รายการย่อยของ <template> ใน DOM เนื้อหาขององค์ประกอบ <template> เป็นเนื้อหาย่อย ของ DocumentFragment ที่ HTMLTemplateElement.content ส่งคืน ในการที่จะแสดงให้เห็นได้ จะต้องใช้ JavaScript ในการจับเนื้อหาและต่อท้ายเนื้อหาเหล่านั้นใน DOM

JavaScript สั้นๆ นี้ไม่ได้สร้างองค์ประกอบที่กำหนดเอง แต่ตัวอย่างนี้ได้เพิ่มเนื้อหาของ <template> ต่อท้าย <body> เนื้อหาดังกล่าวกลายเป็นส่วนหนึ่งของ DOM ที่มองเห็นได้และจัดรูปแบบได้

ภาพหน้าจอของตัวแปลงรหัสก่อนหน้าตามที่แสดงใน DOM

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

องค์ประกอบ <slot>

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

ในเทมเพลตของเรา เราเปลี่ยน <legend> เป็น <slot> ดังนี้

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

แอตทริบิวต์ name ใช้สำหรับกำหนดช่องให้กับองค์ประกอบอื่นๆ หากองค์ประกอบมีแอตทริบิวต์ slot ที่มีค่าตรงกับ ชื่อของช่องที่ตั้งชื่อ หากองค์ประกอบที่กำหนดเองไม่มีรายการที่ตรงกันสำหรับช่อง เนื้อหาของ <slot> จะแสดงผล เราจึงรวม <legend> ที่มีเนื้อหาทั่วไปซึ่งสามารถแสดงใน HTML ของตนได้หากมีเพียง <star-rating></star-rating> โดยไม่มีเนื้อหา

<star-rating>
  <legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Toasty McToastface</legend>
  <p>Is this text visible?</p>
</star-rating>

แอตทริบิวต์ slot เป็นแอตทริบิวต์ร่วมที่ใช้ เพื่อแทนที่เนื้อหาของ <slot> ภายใน <template> ในองค์ประกอบที่กำหนดเองของเรา องค์ประกอบที่มีแอตทริบิวต์ช่อง เป็น <legend> ซึ่งไม่จำเป็น ในเทมเพลตของเรา <slot name="star-rating-legend"> จะถูกแทนที่ด้วย <anyElement slot="star-rating-legend"> โดยที่ <anyElement> สามารถเป็นองค์ประกอบใดก็ได้ หรือแม้กระทั่งองค์ประกอบที่กำหนดเองอื่นๆ

องค์ประกอบที่ไม่ได้กำหนด

ใน <template> เราใช้องค์ประกอบ <rating> นี่ไม่ใช่องค์ประกอบที่กำหนดเอง แต่เป็นองค์ประกอบที่ไม่รู้จัก เบราว์เซอร์ จะไม่ล้มเหลวเมื่อไม่รู้จักองค์ประกอบหนึ่งๆ เบราว์เซอร์จะปฏิบัติต่อองค์ประกอบ HTML ที่ไม่รู้จักเป็นแบบอินไลน์ องค์ประกอบที่กำหนดสไตล์ด้วย CSS ได้ คล้ายกับ <span> องค์ประกอบ <rating> และ <star-rating> ไม่ได้ใช้ User Agent หรืออรรถศาสตร์

โปรดทราบว่าไม่มีการแสดงผล <template> และเนื้อหา <template> เป็นองค์ประกอบที่รู้จักซึ่งมีเนื้อหาที่ ไม่มีการแสดงผล ยังไม่ได้กำหนดองค์ประกอบ <star-rating> เบราว์เซอร์จะแสดงองค์ประกอบนั้นจนกว่าเราจะกำหนดองค์ประกอบหนึ่งๆ เช่น องค์ประกอบที่ไม่รู้จักทั้งหมด ในตอนนี้ <star-rating> ที่ไม่รู้จักจะถือว่าเป็นองค์ประกอบในบรรทัดที่ไม่ระบุตัวตน ดังนั้นเนื้อหา ซึ่งรวมถึงคำอธิบายและ <p> ใน <star-rating> ที่สามจะแสดงในลักษณะเดียวกับถ้าอยู่ใน <span> แทน

มากำหนดองค์ประกอบของเราเพื่อแปลงองค์ประกอบที่ไม่รู้จักนี้เป็นองค์ประกอบที่กำหนดเอง

องค์ประกอบที่กำหนดเอง

ต้องใช้ JavaScript เพื่อกำหนดองค์ประกอบที่กำหนดเอง เมื่อกำหนดไว้ เนื้อหาขององค์ประกอบ <star-rating> จะถูกแทนที่ด้วย รากเงาที่มีเนื้อหาทั้งหมดของเทมเพลตที่เราเชื่อมโยงด้วย ระบบจะแทนที่องค์ประกอบ <slot> จากเทมเพลต กับเนื้อหาขององค์ประกอบภายใน <star-rating> ซึ่งมีค่าแอตทริบิวต์ slot ตรงกับค่าของชื่อของ <slot> หาก มีอยู่ 1 รายการ หากไม่มี เนื้อหาในช่องของเทมเพลตจะปรากฏขึ้น

เนื้อหาภายในองค์ประกอบที่กำหนดเองที่ไม่ได้เชื่อมโยงกับช่องโฆษณา—<p>Is this text visible?</p> ใน <star-rating> ที่สามของเรา จะไม่รวมอยู่ใน รากเงา จึงไม่ปรากฏ

เรากำหนดองค์ประกอบที่กำหนดเองชื่อ star-rating โดยการขยาย HTMLElement:

customElements.define('star-rating',
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const starRating = document.getElementById('star-rating-template').content;
      const shadowRoot = this.attachShadow({
        mode: 'open'
      });
      shadowRoot.appendChild(starRating.cloneNode(true));
    }
  });

เมื่อกำหนดองค์ประกอบแล้ว ทุกครั้งที่เบราว์เซอร์พบองค์ประกอบ <star-rating> ก็จะแสดงผลตามที่กำหนดไว้ ข้างองค์ประกอบที่มี #star-rating-template ซึ่งเป็นเทมเพลตของเรา เบราว์เซอร์จะแนบแผนผัง Shadow DOM เข้ากับโหนดโดยต่อท้าย โคลนของเนื้อหาเทมเพลตไปยัง Shadow DOM นั้น โปรดทราบว่าองค์ประกอบที่คุณสามารถattachShadow() ถูกจำกัด

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));

เมื่อดูที่เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ คุณจะสังเกตเห็นว่า <form> จาก <template> เป็นส่วนหนึ่งของรากเงาขององค์ประกอบที่กำหนดเองแต่ละรายการ การโคลนของเนื้อหา <template> จะปรากฏในองค์ประกอบที่กำหนดเองแต่ละรายการในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์และมองเห็นได้ในเบราว์เซอร์ แต่เนื้อหา ขององค์ประกอบที่กำหนดเองจะไม่แสดงผลในหน้าจอ

ภาพหน้าจอของเครื่องมือสำหรับนักพัฒนาเว็บที่แสดงเนื้อหาของเทมเพลตที่โคลนในองค์ประกอบที่กำหนดเองแต่ละรายการ

ในตัวอย่าง <template> เราเพิ่มเนื้อหาของเทมเพลตต่อท้ายเนื้อหาเอกสาร โดยเพิ่มเนื้อหาใน DOM ปกติ ในคำจำกัดความ customElements เราใช้ appendChild() แต่เนื้อหาเทมเพลตที่โคลนถูกนำไปต่อท้าย Shadow DOM ที่ห่อหุ้ม

สังเกตไหมว่าดวงดาวกลับไปเป็นปุ่มตัวเลือกที่ไม่มีรูปแบบแล้ว ในฐานะที่เป็นส่วนหนึ่งของ Shadow DOM แทนที่จะเป็น DOM มาตรฐาน การจัดรูปแบบภายในแท็บ CSS ของ Codepen จะไม่มีผล CSS ของแท็บนั้น สไตล์จะกำหนดขอบเขตของเอกสาร ไม่ใช่ Shadow DOM ดังนั้นจะไม่มีการใช้รูปแบบ เราต้องสร้างการสรุป เพื่อจัดรูปแบบเนื้อหา Shadow DOM ที่รวมอยู่

Shadow DOM

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

เนื่องจากเราได้เพิ่มเนื้อหาลงใน Shadow DOM แล้ว เราจึงรวมเอลิเมนต์ <style> ได้ การระบุ CSS ที่ห่อหุ้มไว้ให้กับองค์ประกอบที่กำหนดเอง

เราจึงไม่ต้องกังวลว่ารูปแบบจะเลือนหายไปจากส่วนที่เหลือของเอกสาร เราสามารถ ลดความจำเพาะของตัวเลือกลงอย่างมาก ตัวอย่างเช่น เนื่องจากอินพุตที่ใช้ในองค์ประกอบที่กำหนดเองมีเพียงตัวเลือกเดียว เราสามารถใช้ input แทน input[type="radio"] เป็นตัวเลือกได้

 <template id="star-rating-template">
  <style>
    rating {
      display: inline-flex;
    }
    input {
      appearance: none;
      margin: 0;
      box-shadow: none;
    }
    input::after {
      content: '\2605'; /* solid star */
      font-size: 32px;
    }
    rating:hover input:invalid::after,
    rating:focus-within input:invalid::after {
      color: #888;
    }
    input:invalid::after,
      rating:hover input:hover ~ input:invalid::after,
      input:focus ~ input:invalid::after  {
      color: #ddd;
    }
    input:valid {
      color: orange;
    }
    input:checked ~ input:not(:checked)::after {
      color: #ccc;
      content: '\2606'; /* hollow star */
    }
  </style>
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required/>
        <input type="radio" name="rating" value="2" aria-label="2 stars"/>
        <input type="radio" name="rating" value="3" aria-label="3 stars"/>
        <input type="radio" name="rating" value="4" aria-label="4 stars"/>
        <input type="radio" name="rating" value="5" aria-label="5 stars"/>
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

ขณะที่คอมโพเนนต์เว็บมีการห่อหุ้มด้วยมาร์กอัป in <template> และรูปแบบ CSS จะกำหนดขอบเขตเป็น Shadow DOM และซ่อนไว้ จากทุกอย่างภายนอกคอมโพเนนต์ เนื้อหาของช่องที่แสดงผล <anyElement slot="star-rating-legend"> ไม่ได้ห่อหุ้มด้วย <star-rating> บางส่วน

การจัดรูปแบบนอกขอบเขตปัจจุบัน

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

เงาต้นไม้ คือต้นไม้ DOM ภายใน Shadow DOM รากของเงาคือโหนดรากของเงาต้นไม้

คลาสเทียมของ :host จะเลือก <star-rating> ซึ่งเป็นองค์ประกอบโฮสต์ที่เป็นเงา โฮสต์เงา คือโหนด DOM ที่ Shadow DOM แนบอยู่ หากต้องการกำหนดเป้าหมายเฉพาะเวอร์ชันโฮสต์ของโฮสต์ ให้ใช้ :host() วิธีนี้จะเลือกเฉพาะองค์ประกอบของโฮสต์เงาที่ตรงกับพารามิเตอร์ที่ส่งผ่าน เช่น ตัวเลือกคลาสหรือแอตทริบิวต์ หากต้องการเลือก องค์ประกอบที่กำหนดเองทั้งหมดที่คุณสามารถใช้ star-rating { /* styles */ } ใน CSS ส่วนกลาง หรือ :host(:not(#nonExistantId)) ในรูปแบบเทมเพลต ในแง่ของ ของข้อกำหนดเฉพาะ ในกรณีนี้ CSS ทั่วโลกจะเป็นผู้ชนะ

องค์ประกอบเทียมของ ::slotted() ข้ามขอบเขต Shadow DOM จากภายใน Shadow DOM เลือกองค์ประกอบแบบสล็อตหากตรงกับตัวเลือก ในตัวอย่าง ::slotted(legend) ตรงกับคำอธิบาย 3 รายการของเรา

หากต้องการกำหนดเป้าหมาย Shadow DOM จาก CSS ในขอบเขตรวม คุณต้องแก้ไขเทมเพลต part ลงในเอลิเมนต์ ใดก็ได้ที่ต้องการจัดรูปแบบ จากนั้นใช้องค์ประกอบจำลองของ ::part() เพื่อจับคู่องค์ประกอบภายในแผนผังเงาที่ตรงกับพารามิเตอร์ที่ส่งผ่าน องค์ประกอบจุดยึดหรือองค์ประกอบต้นทางสำหรับองค์ประกอบจำลองคือ โฮสต์หรือชื่อองค์ประกอบที่กำหนดเอง ในกรณีนี้คือ star-rating พารามิเตอร์คือค่าของแอตทริบิวต์ part

หากมาร์กอัปเทมเพลตเริ่มต้นเช่นนั้น

<template id="star-rating-template">
  <form part="formPart">
    <fieldset part="fieldsetPart">

เราสามารถกำหนดเป้าหมาย <form> และ <fieldset> ด้วยค่าต่อไปนี้

star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }

ชื่อส่วนทำหน้าที่คล้ายกับคลาส โดยองค์ประกอบหนึ่งอาจมีชื่อส่วนที่คั่นด้วยช่องว่างได้หลายชื่อ และองค์ประกอบหลายรายการสามารถ มีชื่อชิ้นส่วนเหมือนกัน

Google มีรายการตรวจสอบที่ยอดเยี่ยมสำหรับการสร้างองค์ประกอบที่กำหนดเอง คุณอาจอยากทราบข้อมูลเพิ่มเติมเกี่ยวกับ เกี่ยวกับ Dreamative Shadow จากการประกาศ

ตรวจสอบความเข้าใจ

ทดสอบความรู้เกี่ยวกับเทมเพลต ช่อง และแสงเงา

โดยค่าเริ่มต้น รูปแบบจากภายนอก Shadow DOM จะจัดรูปแบบองค์ประกอบภายใน

จริง
โปรดลองอีกครั้ง
เท็จ
ถูกต้อง

คำตอบใดเป็นคำอธิบายที่ถูกต้องขององค์ประกอบ <template>

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