ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างโมดัลมินิและเมกะที่ปรับสีตามอุปกรณ์ ตอบสนอง และเข้าถึงได้โดยใช้องค์ประกอบ <dialog>
ในโพสต์นี้ เราต้องการแชร์แนวคิดเกี่ยวกับวิธีสร้างโมดัลขนาดเล็กและขนาดใหญ่ที่ปรับสีได้ ตอบสนอง และเข้าถึงได้โดยใช้องค์ประกอบ <dialog>
ลองใช้เดโมและดูซอร์สโค้ด
หากต้องการดูวิดีโอ โปรดดูโพสต์เวอร์ชัน YouTube ที่นี่
ภาพรวม
องค์ประกอบ <dialog>
เหมาะสําหรับข้อมูลหรือการดำเนินการตามบริบทในหน้า พิจารณาว่าประสบการณ์ของผู้ใช้จะได้รับประโยชน์จากการดำเนินการในหน้าเดียวกันแทนการดำเนินการในหลายหน้าเมื่อใด เช่น อาจเป็นเพราะแบบฟอร์มมีขนาดเล็ก หรือการดำเนินการเพียงอย่างเดียวที่ผู้ใช้ต้องทำคือยืนยันหรือยกเลิก
องค์ประกอบ <dialog>
ใช้งานได้อย่างเสถียรในเบราว์เซอร์ต่างๆ แล้ว
เราพบว่าองค์ประกอบขาดบางอย่าง ดังนั้นในภารกิจ GUI นี้ เราจึงเพิ่มรายการที่นักพัฒนาแอปควรได้รับ ซึ่งได้แก่ เหตุการณ์เพิ่มเติม การปิดแบบเบา การเคลื่อนไหวที่กําหนดเอง รวมถึงประเภทมินิและเมกะ
Markup
องค์ประกอบ <dialog>
ต้องมีข้อมูลที่จำเป็นเท่านั้น องค์ประกอบจะซ่อนโดยอัตโนมัติและมีสไตล์ในตัวเพื่อวางซ้อนเนื้อหา
<dialog> … </dialog>
เราปรับปรุงเกณฑ์พื้นฐานนี้ได้
โดยทั่วไปแล้ว องค์ประกอบกล่องโต้ตอบจะแชร์ข้อมูลจำนวนมากกับโมดัล และมักใช้ชื่อแทนกันได้ เราใช้องค์ประกอบกล่องโต้ตอบสำหรับทั้งป๊อปอัปกล่องโต้ตอบขนาดเล็ก (มินิ) และกล่องโต้ตอบแบบเต็มหน้า (เมกะ) เราตั้งชื่อว่า Mega และ Mini โดยปรับบทสนทนาทั้ง 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 เพื่อคอยดูว่าโฟกัสออกจากองค์ประกอบใด จากนั้นจะแทรกแซงและนำโฟกัสกลับ
หลังจาก 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
ในกล่องโต้ตอบ เพื่อให้ผู้ใช้แป้นพิมพ์และโปรแกรมอ่านหน้าจอเข้าถึงกล่องโต้ตอบที่ซ่อนอยู่ไม่ได้
กำหนดธีมสีแบบปรับเปลี่ยนได้ให้กับกล่องโต้ตอบ
แม้ว่า 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 เมื่อหน่วยดังกล่าวมีความเสถียรมากขึ้น
ตำแหน่งของกล่องโต้ตอบ Mega
หากต้องการช่วยจัดตําแหน่งองค์ประกอบกล่องโต้ตอบ คุณควรแยกออกเป็น 2 ส่วน ได้แก่ พื้นหลังแบบเต็มหน้าจอและคอนเทนเนอร์กล่องโต้ตอบ ฉากหลังต้องครอบคลุมทุกอย่าง โดยให้เอฟเฟกต์แสงเงาเพื่อแสดงให้เห็นว่ากล่องโต้ตอบนี้อยู่ด้านหน้าและเนื้อหาด้านหลังเข้าถึงไม่ได้ คอนเทนเนอร์กล่องโต้ตอบสามารถวางอยู่ตรงกลางพื้นหลังนี้และปรับรูปร่างตามเนื้อหาที่ต้องการ
สไตล์ต่อไปนี้จะยึดองค์ประกอบกล่องโต้ตอบกับหน้าต่าง ยืดให้เข้ากับแต่ละมุม และใช้ margin: auto
เพื่อจัดเนื้อหาให้อยู่ตรงกลาง
dialog { … margin: auto; padding: 0; position: fixed; inset: 0; z-index: var(--layer-important); }
รูปแบบกล่องโต้ตอบขนาดใหญ่บนอุปกรณ์เคลื่อนที่
ในวิวพอร์ตขนาดเล็ก เราจะจัดสไตล์ Mega Modal แบบเต็มหน้านี้ให้แตกต่างออกไปเล็กน้อย ฉันตั้งค่าระยะขอบด้านล่างเป็น 0
ซึ่งจะแสดงเนื้อหากล่องโต้ตอบที่ด้านล่างของวิวพอร์ต การปรับสไตล์เพียง 2-3 อย่างก็เปลี่ยนกล่องโต้ตอบเป็น ActionSheet ที่อยู่ใกล้กับนิ้วของผู้ใช้มากขึ้นได้
@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
ให้กับกล่องโต้ตอบขนาดใหญ่ ดังนี้
dialog[modal-mode="mega"]::backdrop { backdrop-filter: blur(25px); }
เรายังเลือกใส่ทรานซิชันใน backdrop-filter
ด้วย หวังว่าเบราว์เซอร์จะอนุญาตให้ใช้ทรานซิชันกับองค์ประกอบฉากหลังในอนาคต
dialog::backdrop { transition: backdrop-filter .5s ease; }
อุปกรณ์เสริมสำหรับจัดแต่งทรงผม
เราเรียกส่วนนี้ว่า "พิเศษ" เนื่องจากเกี่ยวข้องกับองค์ประกอบกล่องโต้ตอบในการแสดงตัวอย่างมากกว่าองค์ประกอบกล่องโต้ตอบโดยทั่วไป
การจำกัดการเลื่อน
เมื่อกล่องโต้ตอบแสดงขึ้น ผู้ใช้จะยังเลื่อนหน้าเว็บที่อยู่เบื้องหลังได้ ซึ่งเราไม่ต้องการ
โดยปกติแล้ว overscroll-behavior
จะเป็นวิธีแก้ปัญหาปกติ แต่ตามข้อกำหนด จะไม่มีผลกับกล่องโต้ตอบเนื่องจากไม่ใช่พอร์ตเลื่อน นั่นคือไม่ใช่ตัวเลื่อน จึงไม่มีอะไรที่จะป้องกัน ฉันสามารถใช้ JavaScript เพื่อรอเหตุการณ์ใหม่จากคู่มือนี้ เช่น "ปิด" และ "เปิด" และเปิด/ปิด overflow: hidden
ในเอกสาร หรือจะรอให้ :has()
ใช้งานได้อย่างเสถียรในเบราว์เซอร์ทุกรุ่นก็ได้
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); } }
จัดรูปแบบปุ่มปิดส่วนหัว
เนื่องจากเดโมใช้ปุ่มเปิดพร็อพ จึงมีการปรับแต่งปุ่มปิดให้เป็นรูปปุ่มกลมที่มีไอคอนอยู่ตรงกลางดังต่อไปนี้
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; }
จัดรูปแบบกล่องโต้ตอบ <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); } }
จัดรูปแบบกล่องโต้ตอบ <footer>
บทบาทของส่วนท้ายคือมีเมนูปุ่มดำเนินการ ใช้ 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); } }
จัดรูปแบบเมนูส่วนท้ายของกล่องโต้ตอบ
องค์ประกอบ 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; }
แอนิเมชัน
องค์ประกอบของกล่องโต้ตอบมักมีภาพเคลื่อนไหวเนื่องจากจะเข้าสู่และออกจากหน้าต่าง การให้ภาพเคลื่อนไหวสนับสนุนบทสนทนาสำหรับการเข้าและออกนี้จะช่วยให้ผู้ใช้ปรับทิศทางตัวเองในขั้นตอนต่างๆ ได้
โดยปกติแล้ว องค์ประกอบกล่องโต้ตอบจะมีภาพเคลื่อนไหวเฉพาะเมื่อปรากฏขึ้นเท่านั้น เนื่องจากเบราว์เซอร์จะเปิด/ปิดพร็อพเพอร์ตี้ display
ในองค์ประกอบ ก่อนหน้านี้ คำแนะนำจะตั้งค่าการแสดงผลเป็นตารางกริดและไม่เคยตั้งค่าเป็น "ไม่มี" ซึ่งจะปลดล็อกความสามารถในการทำให้ภาพเคลื่อนไหวเข้าและออก
Open Props มีคีย์เฟรมภาพเคลื่อนไหวมากมายให้ใช้งาน ซึ่งทำให้การจัดระเบียบง่ายและอ่านออกได้ เป้าหมายของภาพเคลื่อนไหวและแนวทางแบบเลเยอร์ที่เราใช้มีดังนี้
- การเคลื่อนไหวลดลงคือทรานซิชันเริ่มต้น ซึ่งเป็นการค่อยๆ ปรากฏและจางหายไปอย่างง่ายดาย
- หากการเคลื่อนไหวเรียบร้อยดี ระบบจะเพิ่มภาพเคลื่อนไหวแบบเลื่อนและปรับขนาด
- เลย์เอาต์อุปกรณ์เคลื่อนที่ที่ปรับเปลี่ยนตามพื้นที่โฆษณาสำหรับกล่องโต้ตอบขนาดใหญ่จะปรับให้เลื่อนออก
การเปลี่ยนค่าเริ่มต้นที่ปลอดภัยและมีความหมาย
แม้ว่าพร็อพแบบเปิดจะมีคีย์เฟรมสำหรับการค่อยๆ ปรากฏขึ้นและจางหายไป แต่ฉันชอบใช้ทรานซิชันแบบหลายเลเยอร์นี้เป็นค่าเริ่มต้น โดยมีภาพเคลื่อนไหวของคีย์เฟรมเป็นตัวเลือกการอัปเกรด ก่อนหน้านี้เราได้จัดสไตล์การแสดงผลของกล่องโต้ตอบด้วยค่าความทึบแสง โดยจัดเรียง 1
หรือ 0
ทั้งนี้ขึ้นอยู่กับแอตทริบิวต์ [open]
หากต้องการเปลี่ยนระหว่าง 0% ถึง 100% ให้บอกเบราว์เซอร์ว่าต้องการระยะเวลาและลักษณะการเปลี่ยนอย่างไร
dialog { transition: opacity .5s var(--ease-3); }
การเพิ่มการเคลื่อนไหวในทรานซิชัน
หากผู้ใช้ยอมรับการเคลื่อนไหว ทั้งกล่องโต้ตอบขนาดใหญ่และขนาดเล็กควรเลื่อนขึ้นเมื่อปรากฏขึ้น และเลื่อนออกเมื่อปิด คุณทําได้ด้วย prefers-reduced-motion
Media 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)
ตัวอย่างการจัดการเหตุการณ์ดังกล่าวมี 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
: นี้มีสตริงปิดที่ส่งเมื่อเรียกเหตุการณ์ dialog close()
ข้อมูลในเหตุการณ์ dialogClosed
มีความสำคัญอย่างยิ่งในการระบุว่ากล่องโต้ตอบถูกปิด ยกเลิก หรือยืนยัน หากยืนยันแล้ว สคริปต์จะดึงค่าของแบบฟอร์มและรีเซ็ตแบบฟอร์ม การรีเซ็ตมีประโยชน์เมื่อกล่องโต้ตอบแสดงขึ้นอีกครั้ง กล่องโต้ตอบจะว่างเปล่าและพร้อมสำหรับการส่งใหม่
บทสรุป
ตอนนี้คุณรู้วิธีที่เราทำแล้ว คุณจะทำอย่างไรบ้าง 🙂
มาลองใช้แนวทางที่หลากหลายและดูวิธีทั้งหมดในการสร้างบนเว็บกัน
สร้างเดโม แล้วทวีตลิงก์มาหาเรา เราจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง
รีมิกซ์ของชุมชน
- @GrimLink พร้อมกล่องโต้ตอบแบบ 3-in-1
- @mikemai2awesome มีรีมิกซ์ที่ดีซึ่งไม่เปลี่ยนพร็อพเพอร์ตี้
display
- @geoffrich_ กับ Svelte และ Svelte FLIP ที่ขัดเงาอย่างดี
แหล่งข้อมูล
- ซอร์สโค้ดใน GitHub
- รูปโปรไฟล์ Doodle