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

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

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

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

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

ภาพรวม

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

องค์ประกอบ <dialog> มีความเสถียรในเบราว์เซอร์ต่างๆ เมื่อเร็วๆ นี้:

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

  • 37
  • 79
  • 98
  • 15.4

แหล่งที่มา

ฉันพบว่าองค์ประกอบหายไป 2-3 รายการ ดังนั้นในภารกิจ 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 ในการเฝ้าระวังโฟกัสที่ออกจากองค์ประกอบ ซึ่งจุดนั้นไปสกัดกั้นไว้และนำกลับเข้าที่

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

  • 102
  • 102
  • 112
  • 15.5

แหล่งที่มา

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

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

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

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

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

รูปแบบ

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

การจัดรูปแบบด้วยอุปกรณ์ประกอบแบบเปิด

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

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

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

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

dialog {
  display: grid;
}

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

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

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

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

กล่องโต้ตอบขนาดใหญ่แสดงธีมสว่างและธีมมืด ซึ่งจะแสดงสีของพื้นผิว

แม้ว่า 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);
}
รูปแบบกล่องโต้ตอบขนาดใหญ่สำหรับอุปกรณ์เคลื่อนที่

ในวิวพอร์ตขนาดเล็ก ฉันจัดรูปแบบโมดัลขนาดใหญ่แบบเต็มหน้านี้แตกต่างกันเล็กน้อย ฉันตั้งค่าขอบด้านล่างเป็น 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 ลงในกล่องโต้ตอบขนาดใหญ่เท่านั้น

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

  • 76
  • 79
  • 103
  • 9

แหล่งที่มา

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

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

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

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

ส่วนเสริมของสไตล์

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

ปุ่มเลื่อน

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

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

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

  • 105
  • 105
  • 121
  • 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 บนส่วนหัวของกล่องโต้ตอบ

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

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

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 Devtools ซ้อนทับข้อมูลขนาดและระยะห่างจากขอบสำหรับปุ่มปิดส่วนหัว

การจัดรูปแบบกล่องโต้ตอบ <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-motion และ Open Props ต่อไปนี้

@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;
  }
}

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

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

@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() เนื่องจากเรากำลังทำให้องค์ประกอบนี้เคลื่อนไหว จึงเป็นเรื่องดีหากมีเหตุการณ์ทั้งก่อนและหลังภาพเคลื่อนไหว การเปลี่ยนแปลงเพื่อดึงข้อมูลหรือรีเซ็ตฟอร์มกล่องโต้ตอบ ฉันใช้ที่นี่เพื่อจัดการการเพิ่มแอตทริบิวต์ 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 ซึ่งใช้ในการสร้างคอมโพเนนต์ข้อความโทสต์ด้วยเช่นกัน จะแสดงคำสัญญาโดยอิงตามความสมบูรณ์ของภาพเคลื่อนไหวและการเปลี่ยนฉาก นี่เป็นเหตุผลที่ dialogClose เป็นฟังก์ชันอะซิงโครนัส ซึ่งก็จะสามารถ await คำมั่นสัญญาที่ให้ไว้และเดินหน้าไปสู่กิจกรรมแบบปิดได้อย่างมั่นใจ

กำลังเพิ่มเหตุการณ์เปิดและกิจกรรมที่เปิดอยู่

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

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

…
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 ของผู้สังเกตการเปลี่ยนแปลงเมื่อมีการเปลี่ยนแอตทริบิวต์กล่องโต้ตอบ โดยแสดงรายการการเปลี่ยนแปลงเป็นอาร์เรย์ ทำซ้ำการเปลี่ยนแปลงแอตทริบิวต์โดยรอให้ attributeName เปิดขึ้น ถัดไป ให้ตรวจสอบว่าองค์ประกอบมีแอตทริบิวต์หรือไม่ ซึ่งจะแจ้งให้ทราบว่ากล่องโต้ตอบเปิดอยู่หรือไม่ หากเปิดแล้ว ให้นำแอตทริบิวต์ inert ออก ตั้งโฟกัสเป็นองค์ประกอบที่ขอ autofocus หรือองค์ประกอบ button แรกที่พบในกล่องโต้ตอบ สุดท้าย ให้ส่งกิจกรรมเปิดทันที รอให้ภาพเคลื่อนไหวจบแล้วจึงส่งกิจกรรมที่เปิด

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

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

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

…
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)
      }
    })
  })
})

ระบบจะเรียกใช้ Callback ของผู้สังเกตการเปลี่ยนแปลงเมื่อมีการเพิ่มหรือนำเด็กออกจากเนื้อความของเอกสาร การเปลี่ยนแปลงที่เฉพาะเจาะจงซึ่งกําลังเฝ้าดูอยู่เป็นของ 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)

ต่อไปนี้เป็นตัวอย่าง 2 ตัวอย่างของการจัดการเหตุการณ์เหล่านั้น

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

บทสรุป

ตอนนี้คุณก็รู้แล้วว่าฉันทำท่านั้นได้อย่างไร คุณจะทำยังไงบ้างคะ‽ 🙂

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

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

รีมิกซ์ในชุมชน

แหล่งข้อมูล