การสร้างคอมโพเนนต์กล่องโต้ตอบ

ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างโมดัลมินิและเมกะที่ปรับสี ตอบสนอง และเข้าถึงได้โดยใช้องค์ประกอบ <dialog>

ในโพสต์นี้ เราต้องการแชร์แนวคิดเกี่ยวกับวิธีสร้างโมดัลขนาดเล็กและขนาดใหญ่ที่ปรับสีได้ ตอบสนอง และเข้าถึงได้โดยใช้องค์ประกอบ <dialog> ลองใช้เดโมและดูซอร์สโค้ด

การสาธิตกล่องโต้ตอบขนาดใหญ่และขนาดเล็กในธีมสว่างและธีมมืด

หากต้องการดูวิดีโอ โปรดดูโพสต์เวอร์ชัน YouTube ที่นี่

ภาพรวม

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

องค์ประกอบ <dialog> ใช้งานได้อย่างเสถียรในเบราว์เซอร์ต่างๆ แล้ว

การรองรับเบราว์เซอร์

  • Chrome: 37.
  • Edge: 79
  • Firefox: 98
  • Safari: 15.4

แหล่งที่มา

เราพบว่าองค์ประกอบขาดบางอย่าง ดังนั้นในภารกิจ GUI นี้ เราจึงเพิ่มรายการที่นักพัฒนาแอปควรได้รับ ซึ่งได้แก่ เหตุการณ์เพิ่มเติม การปิดแบบเบา การเคลื่อนไหวที่กําหนดเอง รวมถึงประเภทมินิและเมกะ

Markup

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

<dialog>
  …
</dialog>

เราปรับปรุงเกณฑ์พื้นฐานนี้ได้

แต่เดิมนั้น องค์ประกอบกล่องโต้ตอบมักจะใช้ร่วมกับโมดัลหลายโมดัลอยู่ และบ่อยครั้งที่ชื่อนั้นใช้แทนกันได้ เราใช้องค์ประกอบกล่องโต้ตอบสำหรับทั้งป๊อปอัปกล่องโต้ตอบขนาดเล็ก (มินิ) และกล่องโต้ตอบแบบเต็มหน้า (เมกะ) ผมจึงตั้งชื่อว่า "เมกะและมินิ" โดยกล่องโต้ตอบทั้ง 2 แบบมีการปรับให้เข้ากับกรณีการใช้งานที่แตกต่างกันเล็กน้อย เราได้เพิ่มแอตทริบิวต์ modal-mode ให้คุณระบุประเภท

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

ภาพหน้าจอของทั้งกล่องโต้ตอบขนาดเล็กและขนาดใหญ่ในทั้งธีมสว่างและธีมมืด

ไม่ได้เสมอไป แต่โดยทั่วไประบบจะใช้องค์ประกอบกล่องโต้ตอบเพื่อรวบรวมข้อมูลการโต้ตอบบางอย่าง แบบฟอร์มภายในองค์ประกอบกล่องโต้ตอบสร้างขึ้นเพื่อใช้ร่วมกัน คุณควรใช้องค์ประกอบแบบฟอร์มตัดเนื้อหากล่องโต้ตอบเพื่อให้ JavaScript เข้าถึงข้อมูลที่ผู้ใช้ป้อนได้ นอกจากนี้ ปุ่มภายในแบบฟอร์มที่ใช้ method="dialog" สามารถปิดกล่องโต้ตอบได้โดยไม่ต้องใช้ JavaScript และส่งข้อมูล

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    …
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

กล่องโต้ตอบ Mega

กล่องโต้ตอบขนาดใหญ่มีองค์ประกอบ 3 รายการในแบบฟอร์ม ได้แก่ <header>, <article> และ <footer> รูปแบบเหล่านี้จะทำหน้าที่เป็นที่เก็บความหมาย รวมถึงเป้าหมายรูปแบบสำหรับการนำเสนอกล่องโต้ตอบ ส่วนหัวจะตั้งชื่อโมดัลและให้ปุ่มปิด บทความนี้มีไว้สำหรับอินพุตและข้อมูลฟอร์ม ส่วนท้ายมี<menu> ปุ่มดำเนินการ

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    <header>
      <h3>Dialog title</h3>
      <button onclick="this.closest('dialog').close('close')"></button>
    </header>
    <article>...</article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

ปุ่มเมนูแรกมี autofocus และ onclick ตัวแฮนเดิลเหตุการณ์ในบรรทัด แอตทริบิวต์ autofocus จะได้รับโฟกัสเมื่อเปิดกล่องโต้ตอบ และเราพบว่าแนวทางปฏิบัติแนะนำคือใส่แอตทริบิวต์นี้ในปุ่มยกเลิก ไม่ใช่ปุ่มยืนยัน วิธีนี้ช่วยให้มั่นใจได้ว่าการยืนยันเป็นการกระทำโดยเจตนาและไม่ได้เกิดขึ้นโดยไม่ตั้งใจ

กล่องโต้ตอบขนาดเล็ก

กล่องโต้ตอบขนาดเล็กคล้ายกับกล่องโต้ตอบขนาดใหญ่มาก แต่ขาดเพียงองค์ประกอบ <header> ซึ่งจะช่วยให้ข้อความมีขนาดเล็กลงและแทรกในบรรทัดมากขึ้น

<dialog id="MiniDialog" modal-mode="mini">
  <form method="dialog">
    <article>
      <p>Are you sure you want to remove this user?</p>
    </article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

องค์ประกอบกล่องโต้ตอบเป็นรากฐานที่แข็งแกร่งสําหรับองค์ประกอบวิวพอร์ตแบบเต็มที่สามารถรวบรวมข้อมูลและการโต้ตอบของผู้ใช้ ข้อมูลสําคัญเหล่านี้อาจทําให้การโต้ตอบในเว็บไซต์หรือแอปน่าสนใจและมีประสิทธิภาพ

การช่วยเหลือพิเศษ

องค์ประกอบกล่องโต้ตอบมีการช่วยเหลือพิเศษในตัวที่ดีมาก แทนที่จะเพิ่มฟีเจอร์เหล่านี้เหมือนที่ฉันมักจะทำ ฟีเจอร์หลายอย่างก็พร้อมใช้งานแล้ว

การคืนค่าโฟกัส

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

ลักษณะการทำงานเริ่มต้นขององค์ประกอบกล่องโต้ตอบมีดังนี้

ขออภัย หากต้องการแสดงกล่องโต้ตอบแบบเคลื่อนไหว ฟังก์ชันนี้จะใช้งานไม่ได้ เราจะกู้คืนฟังก์ชันการทำงานนั้นในส่วน JavaScript

การโฟกัส

องค์ประกอบกล่องโต้ตอบจะจัดการ inert ในเอกสารให้คุณ ก่อนวันที่ inert มีการใช้ JavaScript ในการเฝ้าระวังโฟกัสที่ออกจากองค์ประกอบ ซึ่งจุดนั้นไปสกัดกั้นไว้และนำกลับเข้าที่

การรองรับเบราว์เซอร์

  • Chrome: 102
  • Edge: 102
  • Firefox: 112
  • Safari: 15.5

แหล่งที่มา

หลังจาก inert คุณจะ "หยุด" ส่วนใดก็ได้ของเอกสารไว้ชั่วคราวเพื่อไม่ให้ส่วนนั้นๆ เป็นเป้าหมายโฟกัสหรือโต้ตอบกับเมาส์อีกต่อไป ระบบจะนําโฟกัสไปยังส่วนที่เป็นอินเทอร์แอกทีฟเพียงส่วนเดียวของเอกสารแทนที่จะตรึงโฟกัสไว้

เปิดและโฟกัสองค์ประกอบโดยอัตโนมัติ

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

การปิดด้วยแป้น Escape

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

รูปแบบ

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

การจัดแต่งโดยใช้พร็อพแบบเปิด

ฉันจึงนำ Open Props มาใส่ลงในไลบรารีตัวแปร CSS เพื่อเร่งให้สีที่ปรับได้และความสอดคล้องของการออกแบบโดยรวมเร็วขึ้น นอกจากตัวแปรที่ให้มาฟรีแล้ว เรายังนําเข้าไฟล์normalize และปุ่มบางรายการด้วย ซึ่ง Open Props มีให้นําเข้าหรือไม่ก็ได้ การนําเข้าเหล่านี้ช่วยให้ฉันมุ่งเน้นที่การปรับแต่งกล่องโต้ตอบและเดโมได้โดยไม่ต้องใช้สไตล์มากมายเพื่อรองรับและทำให้ดูดี

การจัดรูปแบบองค์ประกอบ <dialog>

การเป็นเจ้าของพร็อพเพอร์ตี้การแสดงผล

ลักษณะการทำงานเริ่มต้นในการแสดงหรือซ่อนองค์ประกอบกล่องโต้ตอบจะสลับพร็อพเพอร์ตี้การแสดงผลจาก block เป็น none ซึ่งหมายความว่าจะเคลื่อนไหวเข้าและออกไม่ได้ แต่จะเคลื่อนไหวเข้าได้เท่านั้น ฉันอยากให้เคลื่อนไหวทั้งขาเข้าและขาออก และขั้นตอนแรกคือ ตั้งค่าพร็อพเพอร์ตี้ display ของฉันเอง

dialog {
  display: grid;
}

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

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

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

กำหนดธีมสีแบบปรับเปลี่ยนได้ให้กับกล่องโต้ตอบ

กล่องโต้ตอบ Mega ที่แสดงธีมสว่างและธีมมืดเพื่อสาธิตสีของพื้นผิว

แม้ว่า color-scheme จะเลือกธีมสีแบบปรับเปลี่ยนได้ซึ่งเบราว์เซอร์มีให้สำหรับเอกสารของคุณเพื่อปรับตามค่ากำหนดของระบบเป็นธีมสว่างและธีมมืด แต่เราต้องการปรับแต่งองค์ประกอบกล่องโต้ตอบมากกว่านั้น Open Props มีสีพื้นผิวบางส่วนที่จะปรับตามค่ากำหนดของระบบสว่างและมืดโดยอัตโนมัติ ซึ่งคล้ายกับการใช้ color-scheme เครื่องมือเหล่านี้เหมาะอย่างยิ่งสำหรับการสร้างเลเยอร์ในการออกแบบ และผมชอบใช้สีเพื่อช่วยสนับสนุนลักษณะที่ปรากฏของพื้นผิวเลเยอร์ในลักษณะนี้ สีพื้นหลังคือ var(--surface-1) หากต้องการวางซ้อนบนเลเยอร์นั้น ให้ใช้ var(--surface-2)

dialog {
  
  background: var(--surface-2);
  color: var(--text-1);
}

@media (prefers-color-scheme: dark) {
  dialog {
    border-block-start: var(--border-size-1) solid var(--surface-3);
  }
}

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

การปรับขนาดกล่องโต้ตอบตามอุปกรณ์

กล่องโต้ตอบจะกำหนดขนาดตามเนื้อหาโดยค่าเริ่มต้น ซึ่งโดยทั่วไปแล้วถือว่ายอดเยี่ยม เป้าหมายของเราคือการจำกัด max-inline-size ให้มีขนาดที่อ่านได้ (--size-content-3 = 60ch) หรือ 90% ของความกว้างของวิวพอร์ต วิธีนี้ช่วยให้กล่องโต้ตอบไม่กินพื้นที่เต็มหน้าจอในอุปกรณ์เคลื่อนที่ และจะไม่กว้างจนอ่านยากบนหน้าจอเดสก์ท็อป จากนั้นฉันจะเพิ่ม max-block-size เพื่อให้กล่องโต้ตอบไม่สูงกว่าความสูงของหน้า ซึ่งหมายความว่าเราจะต้องระบุตําแหน่งของพื้นที่ที่เลื่อนได้ของกล่องโต้ตอบด้วยในกรณีที่เป็นองค์ประกอบกล่องโต้ตอบที่สูง

dialog {
  
  max-inline-size: min(90vw, var(--size-content-3));
  max-block-size: min(80vh, 100%);
  max-block-size: min(80dvb, 100%);
  overflow: hidden;
}

สังเกตไหมว่าฉันมี max-block-size 2 ครั้ง รายการแรกใช้ 80vh ซึ่งเป็นหน่วยวิวพอร์ตจริง สิ่งที่ฉันต้องการจริงๆ คือทำให้กล่องโต้ตอบอยู่ในลำดับที่สัมพันธ์กันสำหรับผู้ใช้ต่างชาติ ดังนั้นฉันจึงใช้หน่วย dvb ที่เป็นตรรกะ ใหม่กว่า และรองรับเพียงบางส่วนในการประกาศครั้งที่ 2 เมื่อหน่วยดังกล่าวมีความเสถียรมากขึ้น

ตำแหน่งของกล่องโต้ตอบ Mega

หากต้องการช่วยจัดตําแหน่งองค์ประกอบกล่องโต้ตอบ คุณควรแยกออกเป็น 2 ส่วน ได้แก่ พื้นหลังแบบเต็มหน้าจอและคอนเทนเนอร์กล่องโต้ตอบ ฉากหลังต้องครอบคลุมทุกอย่าง โดยให้เอฟเฟกต์แสงเงาเพื่อแสดงให้เห็นว่ากล่องโต้ตอบนี้อยู่ด้านหน้าและเนื้อหาด้านหลังเข้าถึงไม่ได้ คอนเทนเนอร์กล่องโต้ตอบสามารถวางอยู่ตรงกลางพื้นหลังนี้และปรับรูปร่างตามเนื้อหาที่ต้องการ

สไตล์ต่อไปนี้จะยึดองค์ประกอบกล่องโต้ตอบกับหน้าต่าง ยืดให้เข้ากับแต่ละมุม และใช้ margin: auto เพื่อจัดเนื้อหาให้อยู่ตรงกลาง

dialog {
  
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
รูปแบบกล่องโต้ตอบขนาดใหญ่บนอุปกรณ์เคลื่อนที่

ในวิวพอร์ตขนาดเล็ก ฉันจัดสไตล์ Mega Modal แบบเต็มหน้านี้ให้แตกต่างออกไปเล็กน้อย ฉันตั้งค่าระยะขอบด้านล่างเป็น 0 ซึ่งจะแสดงเนื้อหากล่องโต้ตอบที่ด้านล่างของวิวพอร์ต ผมเปลี่ยนกล่องโต้ตอบเป็น แผ่นงานการดำเนินการที่ใกล้กับนิ้วโป้งของผู้ใช้มากขึ้นได้ 2 ครั้ง

@media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    margin-block-end: 0;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
  }
}

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

ตำแหน่งของกล่องโต้ตอบขนาดเล็ก

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

องค์ประกอบต้องโดดเด่น

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

dialog {
  
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

การปรับแต่งองค์ประกอบจำลองฉากหลัง

เราเลือกที่จะปรับพื้นหลังเพียงเล็กน้อย โดยเพิ่มเอฟเฟกต์เบลอด้วย backdrop-filter ให้กับกล่องโต้ตอบขนาดใหญ่ ดังนี้

การรองรับเบราว์เซอร์

  • Chrome: 76
  • ขอบ: 79
  • Firefox: 103
  • Safari: 18.

แหล่งที่มา

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

เรายังเลือกใส่ทรานซิชันใน backdrop-filter ด้วย หวังว่าเบราว์เซอร์จะอนุญาตให้ใช้ทรานซิชันกับองค์ประกอบฉากหลังในอนาคต

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

ภาพหน้าจอของกล่องโต้ตอบขนาดใหญ่ที่วางซ้อนบนพื้นหลังเบลอที่มีรูปโปรไฟล์สีสันสดใส

อุปกรณ์เสริมสำหรับจัดแต่ง

เราเรียกส่วนนี้ว่า "พิเศษ" เนื่องจากเกี่ยวข้องกับองค์ประกอบกล่องโต้ตอบในการแสดงตัวอย่างมากกว่าองค์ประกอบกล่องโต้ตอบโดยทั่วไป

การจำกัดการเลื่อน

เมื่อกล่องโต้ตอบแสดงขึ้น ผู้ใช้จะยังเลื่อนหน้าเว็บที่อยู่เบื้องหลังได้ ซึ่งเราไม่ต้องการ

โดยปกติแล้ว overscroll-behavior จะเป็นวิธีแก้ปัญหาปกติ แต่ตามข้อกำหนด จะไม่มีผลกับกล่องโต้ตอบเนื่องจากไม่ใช่พอร์ตเลื่อน นั่นคือไม่ใช่ตัวเลื่อน จึงไม่มีอะไรที่จะป้องกัน ฉันสามารถใช้ JavaScript เพื่อรอเหตุการณ์ใหม่จากคู่มือนี้ เช่น "ปิด" และ "เปิด" และเปิด/ปิด overflow: hidden ในเอกสาร หรือจะรอให้ :has() ใช้งานได้อย่างเสถียรในเบราว์เซอร์ทุกรุ่นก็ได้

การรองรับเบราว์เซอร์

  • Chrome: 105
  • Edge: 105
  • Firefox: 121
  • Safari: 15.4

แหล่งที่มา

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

ตอนนี้เมื่อเปิดกล่องโต้ตอบขนาดใหญ่ เอกสาร html จะมี overflow: hidden

เลย์เอาต์ <form>

นอกเหนือจากที่จะเป็นองค์ประกอบที่สําคัญมากในการรวบรวมข้อมูลการโต้ตอบจากผู้ใช้แล้ว เรายังใช้องค์ประกอบนี้เพื่อวางเลย์เอาต์ส่วนหัว ส่วนท้าย และองค์ประกอบบทความ เลย์เอาต์นี้ตั้งใจจะแสดงองค์ประกอบย่อยของบทความเป็นพื้นที่ที่เลื่อนได้ เราทำสิ่งนี้ได้โดยใช้ grid-template-rows องค์ประกอบบทความได้รับ 1fr และแบบฟอร์มเองก็มีความสูงสูงสุดเท่ากับองค์ประกอบกล่องโต้ตอบ การตั้งค่าความสูงและขนาดแถวที่แน่นอนนี้เป็นสิ่งที่ทำให้องค์ประกอบบทความถูกจำกัดและเลื่อนได้เมื่อเนื้อหามีมากเกิน

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

ภาพหน้าจอของเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์วางซ้อนข้อมูลเลย์เอาต์ตารางกริดบนแถว

จัดรูปแบบกล่องโต้ตอบ <header>

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

dialog > form > header {
  display: flex;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  background: var(--surface-2);
  padding-block: var(--size-3);
  padding-inline: var(--size-5);
}

@media (prefers-color-scheme: dark) {
  dialog > form > header {
    background: var(--surface-1);
  }
}

ภาพหน้าจอของ Chrome Devtools ซ้อนทับข้อมูลเลย์เอาต์ Flexbox บนส่วนหัวของกล่องโต้ตอบ

การจัดรูปแบบปุ่มปิดส่วนหัว

เนื่องจากเดโมใช้ปุ่มเปิดพร็อพ จึงมีการปรับแต่งปุ่มปิดให้เป็นรูปปุ่มกลมที่มีไอคอนอยู่ตรงกลางดังต่อไปนี้

dialog > form > header > button {
  border-radius: var(--radius-round);
  padding: .75ch;
  aspect-ratio: 1;
  flex-shrink: 0;
  place-items: center;
  stroke: currentColor;
  stroke-width: 3px;
}

ภาพหน้าจอของเครื่องมือสำหรับนักพัฒนาเว็บของ Chrome ที่วางซ้อนข้อมูลขนาดและการเว้นวรรคสำหรับปุ่มปิดส่วนหัว

จัดรูปแบบกล่องโต้ตอบ <article>

องค์ประกอบบทความมีบทบาทพิเศษในกล่องโต้ตอบนี้ โดยเป็นพื้นที่ว่างที่ตั้งใจให้เลื่อนได้ในกรณีที่เป็นกล่องโต้ตอบที่มีความสูงหรือยาว

องค์ประกอบแบบฟอร์มหลักได้กำหนดค่าสูงสุดบางอย่างสำหรับตัวเองเพื่อกำหนดข้อจำกัดขององค์ประกอบบทความนี้หากมีความยาวมากเกินไป ตั้งค่า overflow-y: auto เพื่อให้แถบเลื่อนแสดงขึ้นเมื่อจำเป็นเท่านั้น มีแถบเลื่อนภายในด้วย overscroll-behavior: contain และส่วนที่เหลือจะเป็นสไตล์การนำเสนอที่กำหนดเอง

dialog > form > article {
  overflow-y: auto; 
  max-block-size: 100%; /* safari */
  overscroll-behavior-y: contain;
  display: grid;
  justify-items: flex-start;
  gap: var(--size-3);
  box-shadow: var(--shadow-2);
  z-index: var(--layer-1);
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: light) {
  dialog > form > article {
    background: var(--surface-1);
  }
}

บทบาทของส่วนท้ายคือประกอบด้วยเมนูของปุ่มดำเนินการ Flexbox ใช้สำหรับจัดเนื้อหาให้ชิดกับจุดสิ้นสุดของแกนแทรกในบรรทัดของส่วนท้าย และเว้นระยะห่างบางอย่างเพื่อให้ปุ่มมีพื้นที่ว่าง

dialog > form > footer {
  background: var(--surface-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: dark) {
  dialog > form > footer {
    background: var(--surface-1);
  }
}

ภาพหน้าจอของ Chrome DevTools ที่วางซ้อนข้อมูลเลย์เอาต์ Flexbox บนองค์ประกอบส่วนท้าย

องค์ประกอบ menu ใช้เพื่อบรรจุปุ่มดำเนินการสำหรับกล่องโต้ตอบ โดยใช้เลย์เอาต์ Flexbox ที่รวม gap เพื่อให้มีพื้นที่ระหว่างปุ่ม องค์ประกอบเมนูต้องมีระยะห่างจากขอบ เช่น <ul> และนำรูปแบบนั้นออกด้วยเนื่องจากเราไม่ต้องการ

dialog > form > footer > menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  padding-inline-start: 0;
}

dialog > form > footer > menu:only-child {
  margin-inline-start: auto;
}

ภาพหน้าจอของ Chrome DevTools ที่วางซ้อนข้อมูล Flexbox บนองค์ประกอบเมนูส่วนท้าย

แอนิเมชัน

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

โดยปกติแล้ว องค์ประกอบกล่องโต้ตอบจะเคลื่อนไหวได้เฉพาะเมื่อปรากฏขึ้นเท่านั้น เนื่องจากเบราว์เซอร์จะเปิด/ปิดพร็อพเพอร์ตี้ display ในองค์ประกอบ ก่อนหน้านี้ ไกด์ตั้งค่า "จอแสดงผล" เป็นตารางกริด และอย่าตั้งค่าเป็น "ไม่มี" วิธีนี้ช่วยปลดล็อกความสามารถในการ เคลื่อนไหวเข้าและออก

Open Props มีคีย์เฟรมการเคลื่อนไหวมากมายให้ใช้งาน ซึ่งทำให้การจัดระเบียบง่ายและอ่านออกได้ เป้าหมายภาพเคลื่อนไหวและวิธี แบบเลเยอร์ที่ผมทำมีดังนี้

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

การเปลี่ยนค่าเริ่มต้นที่ปลอดภัยและมีความหมาย

แม้ว่า Open Props จะมีคีย์เฟรมสำหรับการค่อยๆ เลือนหายไปและหายไป แต่ผมชอบการเปลี่ยนแบบเลเยอร์นี้เป็นค่าเริ่มต้นโดยมีภาพเคลื่อนไหวของคีย์เฟรมเป็นการอัปเกรดที่เป็นไปได้ ก่อนหน้านี้เราได้จัดรูปแบบการเปิดเผยของกล่องโต้ตอบให้เป็นแบบทึบแสงแล้ว โดยจัดกลุ่ม 1 หรือ 0 ตามแอตทริบิวต์ [open] หากต้องการเปลี่ยนระหว่าง 0% ถึง 100% ให้บอกเบราว์เซอร์ว่าต้องการการเปลี่ยนแบบใดและระยะเวลาที่ต้องการ

dialog {
  transition: opacity .5s var(--ease-3);
}

การเพิ่มการเคลื่อนไหวในทรานซิชัน

หากผู้ใช้สามารถเคลื่อนไหวได้ กล่องโต้ตอบขนาดใหญ่และกล่องโต้ตอบขนาดเล็ก ควรเลื่อนขึ้นเป็นทางเข้าและปรับขนาดเมื่อออก คุณทําได้ด้วย prefers-reduced-motionMedia Query และ Open Prop 2-3 รายการ ดังนี้

@media (prefers-reduced-motion: no-preference) {
  dialog {
    animation: var(--animation-scale-down) forwards;
    animation-timing-function: var(--ease-squish-3);
  }

  dialog[open] {
    animation: var(--animation-slide-in-up) forwards;
  }
}

การปรับภาพเคลื่อนไหวเมื่อออกจากแอปสำหรับอุปกรณ์เคลื่อนที่

ก่อนหน้านี้ในส่วนการจัดสไตล์ เราได้ปรับสไตล์กล่องโต้ตอบขนาดใหญ่สำหรับอุปกรณ์เคลื่อนที่ให้คล้ายกับชีตการดำเนินการมากขึ้น ราวกับว่ากระดาษแผ่นเล็กเลื่อนขึ้นจากด้านล่างของหน้าจอและยังคงติดอยู่ที่ด้านล่าง ภาพเคลื่อนไหวการออกจากหน้าจอแบบปรับขนาดไม่เหมาะกับการออกแบบใหม่นี้มากนัก และเราสามารถปรับแต่งด้วย Media Query 2-3 รายการและ Open Prop บางรายการ ดังนี้

@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    animation: var(--animation-slide-out-down) forwards;
    animation-timing-function: var(--ease-squish-2);
  }
}

JavaScript

คุณเพิ่มสิ่งต่างๆ ต่อไปนี้ได้โดยใช้ JavaScript

// dialog.js
export default async function (dialog) {
  // add light dismiss
  // add closing and closed events
  // add opening and opened events
  // add removed event
  // removing loading attribute
}

การเพิ่มเหล่านี้เกิดจากความต้องการในการปิดแบบเบาๆ (คลิกพื้นหลังของกล่องโต้ตอบ) ภาพเคลื่อนไหว และเหตุการณ์เพิ่มเติมบางอย่างเพื่อให้ได้เวลาในการรับข้อมูลแบบฟอร์มที่ดีขึ้น

กำลังเพิ่มการปิดไฟ

งานนี้ทําได้ง่ายๆ และเป็นส่วนเสริมที่ยอดเยี่ยมสําหรับองค์ประกอบกล่องโต้ตอบที่ไม่ได้เคลื่อนไหว การโต้ตอบเกิดขึ้นได้จากการดูการคลิกบนองค์ประกอบกล่องโต้ตอบและใช้ประโยชน์จากการฟองอากาศของเหตุการณ์เพื่อประเมินสิ่งที่มีการคลิก และจะพิจารณาเฉพาะ close() หากองค์ประกอบที่อยู่ด้านบนสุดเท่านั้น

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

การแจ้งเตือน dialog.close('dismiss') ระบบเรียกเหตุการณ์และระบุสตริง JavaScript อื่นๆ สามารถดึงสตริงนี้เพื่อรับข้อมูลเชิงลึกเกี่ยวกับวิธีปิดกล่องโต้ตอบได้ ฉันจะระบุสตริงปิดทุกครั้งที่เรียกใช้ฟังก์ชันจากปุ่มต่างๆ เพื่อให้บริบทแก่แอปพลิเคชันของฉันเกี่ยวกับการโต้ตอบของผู้ใช้ด้วย

การเพิ่มกิจกรรมการปิดและกิจกรรมที่ปิดไปแล้ว

องค์ประกอบกล่องโต้ตอบมาพร้อมกับเหตุการณ์ปิด ซึ่งจะแสดงทันทีเมื่อมีเรียกใช้ฟังก์ชัน close()dialog เนื่องจากเรากำลังทำให้องค์ประกอบนี้เคลื่อนไหว จึงเป็นเรื่องดีหากมีเหตุการณ์ทั้งก่อนและหลังภาพเคลื่อนไหว การเปลี่ยนแปลงเพื่อดึงข้อมูลหรือรีเซ็ตฟอร์มกล่องโต้ตอบ ฉันใช้ที่นี่เพื่อจัดการการเพิ่มแอตทริบิวต์ inert ในกล่องโต้ตอบแบบปิด และในการสาธิต ฉันใช้เพื่อแก้ไขรายการรูปโปรไฟล์หากผู้ใช้ส่งรูปภาพใหม่

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

const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')

export default async function (dialog) {
  
  dialog.addEventListener('close', dialogClose)
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

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

การเพิ่มกิจกรรมที่เปิดและกิจกรรมที่เปิด

เหตุการณ์เหล่านี้เพิ่มได้ยากกว่า เนื่องจากองค์ประกอบกล่องโต้ตอบในตัวไม่มีเหตุการณ์เปิดเหมือนที่มีสำหรับเหตุการณ์ปิด ฉันใช้ MutationObserver เพื่อแสดงข้อมูลเชิงลึกเกี่ยวกับการเปลี่ยนแปลงแอตทริบิวต์ของกล่องโต้ตอบ ในเครื่องมือตรวจสอบนี้เราจะคอยดูการเปลี่ยนแปลงแอตทริบิวต์ "เปิด" และจัดการเหตุการณ์ที่กําหนดเองตามความเหมาะสม

สร้างเหตุการณ์ใหม่ 2 รายการโดยตั้งชื่อว่า opening และ opened เช่นเดียวกับที่เราเริ่มเหตุการณ์ที่ปิดและปิดแล้ว ก่อนหน้านี้เราคอยฟังเหตุการณ์การปิดกล่องโต้ตอบ แต่ครั้งนี้จะใช้ Mutation Observer ที่สร้างขึ้นเพื่อดูแอตทริบิวต์ของกล่องโต้ตอบ


const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')

export default async function (dialog) {
  
  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })
}

const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

ระบบจะเรียกใช้ฟังก์ชัน Callback ของ Mutation Observer เมื่อมีการทําการเปลี่ยนแปลงแอตทริบิวต์ของกล่องโต้ตอบ โดยระบุรายการการเปลี่ยนแปลงเป็นอาร์เรย์ วนผ่านการเปลี่ยนแปลงแอตทริบิวต์เพื่อหา attributeName ที่เปิดอยู่ ถัดไป ให้ตรวจสอบว่าองค์ประกอบมีแอตทริบิวต์หรือไม่ ซึ่งจะระบุว่ากล่องโต้ตอบเปิดขึ้นหรือไม่ หากเปิดอยู่ ให้นําแอตทริบิวต์ inert ออก ตั้งค่าโฟกัสไปที่องค์ประกอบที่ขอ autofocus หรือองค์ประกอบ button รายการแรกที่พบในกล่องโต้ตอบ สุดท้าย ให้ส่งเหตุการณ์เปิดทันที รอให้ภาพเคลื่อนไหวเสร็จสิ้น แล้วส่งเหตุการณ์ที่เปิด

การเพิ่มกิจกรรมที่นำออก

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

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


const dialogRemovedEvent = new Event('removed')

export default async function (dialog) {
  
  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })
}

const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

ระบบจะเรียกใช้การเรียกกลับของ Mutation Observer ทุกครั้งที่มีการเพิ่มหรือนำรายการย่อยออกจากเนื้อหาของเอกสาร การกลายพันธุ์ที่เฉพาะเจาะจงซึ่งระบบจะติดตามมีไว้สำหรับ removedNodes ที่มี nodeName ของกล่องโต้ตอบ หากนํากล่องโต้ตอบออก ระบบจะนำเหตุการณ์การคลิกและการปิดออกเพื่อเพิ่มพื้นที่ว่างในหน่วยความจํา และระบบจะส่งเหตุการณ์ที่นําออกเอง

กำลังนำแอตทริบิวต์การโหลดออก

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

export default async function (dialog) {
  
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับปัญหาการป้องกันภาพเคลื่อนไหวคีย์เฟรมเมื่อโหลดหน้าเว็บ ที่นี่

รวมกันทั้งหมด

ต่อไปนี้เป็น dialog.js โดยสังเขป และเราได้อธิบายแต่ละส่วนแยกกันแล้ว

// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')
const dialogRemovedEvent = new Event('removed')

// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

// wait for all dialog animations to complete their promises
const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

// page load dialogs setup
export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
  dialog.addEventListener('close', dialogClose)

  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })

  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })

  // remove loading attribute
  // prevent page load @keyframes playing
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

การใช้ข้อบังคับ dialog.js

ฟังก์ชันที่ส่งออกจากโมดูลจะคาดหวังว่าจะมีการเรียกใช้และส่งผ่านองค์ประกอบกล่องโต้ตอบที่ต้องการเพิ่มเหตุการณ์และฟังก์ชันการทำงานใหม่เหล่านี้

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

เพียงเท่านี้ กล่องโต้ตอบ 2 รายการก็ได้รับการอัปเกรดด้วยการปิดเบาๆ การแก้ไขภาพเคลื่อนไหวขณะโหลด และเหตุการณ์อื่นๆ เพิ่มเติม

การฟังเหตุการณ์ที่กําหนดเองใหม่

ตอนนี้องค์ประกอบกล่องโต้ตอบที่อัปเกรดแล้วแต่ละรายการจะรับฟังเหตุการณ์ใหม่ได้ 5 รายการ ดังนี้

MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)

MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)

MegaDialog.addEventListener('removed', dialogRemoved)

ตัวอย่างการจัดการเหตุการณ์เหล่านั้นมีดังนี้

const dialogOpening = ({target:dialog}) => {
  console.log('Dialog opening', dialog)
}

const dialogClosed = ({target:dialog}) => {
  console.log('Dialog closed', dialog)
  console.info('Dialog user action:', dialog.returnValue)

  if (dialog.returnValue === 'confirm') {
    // do stuff with the form values
    const dialogFormData = new FormData(dialog.querySelector('form'))
    console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))

    // then reset the form
    dialog.querySelector('form')?.reset()
  }
}

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

โปรดทราบว่า dialog.returnValue: มีสตริงปิดที่ส่งเมื่อเรียกเหตุการณ์ dialog close() ในเหตุการณ์ dialogClosed จําเป็นต้องทราบว่ากล่องโต้ตอบปิด ยกเลิก หรือยืนยันหรือไม่ หากยืนยันแล้ว สคริปต์จะดึงค่าของแบบฟอร์มและรีเซ็ตแบบฟอร์ม การรีเซ็ตมีประโยชน์เมื่อกล่องโต้ตอบแสดงขึ้นอีกครั้ง กล่องโต้ตอบจะว่างเปล่าและพร้อมสำหรับการส่งใหม่

บทสรุป

ตอนนี้คุณรู้วิธีที่เราทำแล้ว คุณจะทำอย่างไรบ้าง 🙂

มาลองใช้แนวทางที่หลากหลายและดูวิธีทั้งหมดในการสร้างบนเว็บกัน

สร้างเดโม แล้วทวีตลิงก์มาหาเรา เราจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง

รีมิกซ์ของชุมชน

แหล่งข้อมูล