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

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

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

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

หากชอบวิดีโอ นี่คือโพสต์นี้เวอร์ชัน YouTube

ภาพรวม

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

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

การสนับสนุนเบราว์เซอร์

  • 37
  • 79
  • 98
  • 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>

กล่องโต้ตอบขนาดใหญ่

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

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

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

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

dialog {
  display: grid;
}

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

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

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

เพิ่มธีมสีที่ปรับได้ให้กับกล่องโต้ตอบ

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

แม้ว่า color-scheme จะเลือกให้เอกสารของคุณใช้ธีมสีแบบปรับอัตโนมัติที่เบราว์เซอร์มีให้สำหรับค่ากำหนดของระบบสีอ่อนและมืด แต่เราต้องการปรับแต่งองค์ประกอบกล่องโต้ตอบมากกว่านั้น Open Props มีสีพื้นผิว 2-3 แบบซึ่งจะปรับตามค่ากำหนดของระบบแสงและมืดโดยอัตโนมัติ เช่นเดียวกับการใช้ 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 เมื่อมีความเสถียรมากขึ้น

การวางตำแหน่งกล่องโต้ตอบขนาดใหญ่

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

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

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

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

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

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

เนื่องจากการสาธิตใช้ปุ่ม "เปิดพร็อพ" ปุ่มปิดจึงได้รับการกำหนดค่าให้เป็นปุ่มศูนย์กลางของไอคอนทรงกลมในลักษณะต่อไปนี้

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 และพรอมต์แบบเปิดบางรายการดังต่อไปนี้

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

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

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

บทสรุป

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

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

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

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

แหล่งข้อมูล