สร้างแผงเนื้อหาที่ขยายและทำสัญญาของคุณเอง

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างรวดเร็ว ↬ ใน UI/UX รูปแบบทั่วไปที่จำเป็นต้องใช้ครั้งแล้วครั้งเล่าคือรูปแบบการเปิดและปิดแบบเคลื่อนไหวอย่างง่าย หรือ "ลิ้นชัก" คุณไม่จำเป็นต้องมีห้องสมุดเพื่อสร้างสิ่งเหล่านี้ ด้วย HTML/CSS พื้นฐานและ JavaScript เราจะเรียนรู้วิธีดำเนินการด้วยตนเอง

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

เพื่อชี้แจงให้ชัดเจนว่าเรากำลังพูดถึงอะไร ไปที่ตัวอย่างนี้ใน CodePen:

แสดง/ซ่อนลิ้นชักอย่างง่าย (หลายรายการ) โดย Ben Frain บน CodePen

แสดง/ซ่อนลิ้นชักอย่างง่าย (หลายรายการ) โดย Ben Frain บน CodePen

นั่นคือสิ่งที่เราจะสร้างในบทช่วยสอนสั้นๆ นี้

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

แนวทาง

เทคนิคเหล่านี้มีหลากหลายรูปแบบ แต่โดยทั่วไปแล้ว วิธีการแบ่งออกเป็นสามประเภท:

  1. ทำให้เคลื่อนไหว/เปลี่ยน height หรือความสูง max-height ของเนื้อหา
  2. ใช้ transform: translateY เพื่อย้ายองค์ประกอบไปยังตำแหน่งใหม่ สร้างภาพลวงตาของการปิดแผงแล้วแสดงผล DOM อีกครั้งเมื่อการแปลงเสร็จสมบูรณ์ด้วยองค์ประกอบในตำแหน่งสิ้นสุด
  3. ใช้ไลบรารี่ที่มีการผสมผสาน/รูปแบบต่างๆ ของ 1 หรือ 2!
เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

ข้อควรพิจารณาของแต่ละแนวทาง

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

ขั้นตอนพื้นฐานเมื่อใช้วิธีการแปลงคือ:

  1. รับความสูงของเนื้อหาที่จะยุบ
  2. ย้ายเนื้อหาและทุกอย่างหลังจากนั้นด้วยความสูงของเนื้อหาที่จะยุบโดยใช้ transform: translateY(Xpx) ดำเนินการแปลงร่างด้วยการเปลี่ยนตัวเลือกเพื่อให้เอฟเฟกต์ภาพที่น่าพึงพอใจ
  3. ใช้ JavaScript เพื่อฟังเหตุการณ์ transitionend เมื่อเริ่มทำงาน ให้ display: none เนื้อหาและนำการแปลงออก และทุกอย่างควรอยู่ในตำแหน่งที่ถูกต้อง

ฟังดูไม่เลวใช่มั้ย

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

ตัวอย่างเช่น ด้วยวิธีการ transform: translateY คุณต้องพิจารณา z-index ขององค์ประกอบ โดยค่าเริ่มต้น องค์ประกอบที่เปลี่ยนรูปจะอยู่หลังองค์ประกอบทริกเกอร์ใน DOM และดังนั้นจึงปรากฏอยู่ด้านบนของสิ่งต่าง ๆ ก่อนหน้าเมื่อแปล

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

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

อย่างไรก็ตาม วิธีการนี้ชนะจากมุมมองที่เรียบง่าย การจ่ายเงินสำหรับความทุกข์จากการคำนวณที่กล่าวถึงข้างต้นคือการที่ DOM re-flow จะดูแลตำแหน่งและเรขาคณิตของทุกสิ่ง เรามีวิธีการคำนวณในการเขียนน้อยมาก บวกกับ JavaScript ที่จำเป็นในการดึงออกมาได้ดีนั้นค่อนข้างง่าย

ช้างในห้อง: รายละเอียดและองค์ประกอบโดยย่อ

ผู้ที่มีความรู้เชิงลึกเกี่ยวกับองค์ประกอบ HTML จะรู้ว่ามีวิธีแก้ปัญหา HTML ดั้งเดิมสำหรับปัญหานี้ในรูปแบบของ details และองค์ประกอบ summary นี่คือตัวอย่างมาร์กอัปบางส่วน:

 <details> <summary>Click to open/close</summary> Here is the content that is revealed when clicking the summary... </details>

ตามค่าเริ่มต้น เบราว์เซอร์จะมีสามเหลี่ยมแสดงผลเล็กๆ ถัดจากองค์ประกอบสรุป คลิกสรุปและเนื้อหาด้านล่างสรุปจะถูกเปิดเผย

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

 details.addEventListener("toggle", () => { details.open ? thisCoolThing() : thisOtherThing(); })

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

ถ้าคุณรู้อย่างอื่น ฉันชอบที่จะถูกพิสูจน์ว่าผิด

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

เอาล่ะ ด้วยข่าวที่น่าสลดใจ เรามาเริ่มทำสิ่งนี้กันดีกว่า

รูปแบบมาร์กอัป

มาร์กอัปพื้นฐานจะมีลักษณะดังนี้:

 <div class="container"> <button type="button" class="trigger">Show/Hide content</button> <div class="content"> All the content here </div> </div>

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

องค์ประกอบถัดไปหลังจากปุ่มคือลิ้นชักเนื้อหา ทุกสิ่งที่คุณต้องการซ่อนและแสดง

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

ลอจิกพื้นฐาน

ตรรกะพื้นฐานคือ:

  1. ให้โหลดหน้าวัดความสูงของเนื้อหา
  2. กำหนดความสูงของเนื้อหาบนคอนเทนเนอร์เป็นค่าของคุณสมบัติที่กำหนดเอง CSS
  3. ซ่อนเนื้อหาทันทีโดยเพิ่มแอตทริบิวต์ aria-hidden: "true" ลงในเนื้อหา การใช้ aria-hidden ช่วยให้เทคโนโลยีอำนวยความสะดวกรู้ว่าเนื้อหาถูกซ่อนไว้เช่นกัน
  4. วางสาย CSS เพื่อให้ max-height ของคลาสเนื้อหาเป็นค่าของคุณสมบัติที่กำหนดเอง
  5. การกดปุ่มทริกเกอร์ของเราจะเปลี่ยนคุณสมบัติ aria-hidden จาก true เป็น false ซึ่งจะสลับ max-height ของเนื้อหาระหว่าง 0 และความสูงที่ตั้งไว้ในคุณสมบัติที่กำหนดเอง การเปลี่ยนผ่านของคุณสมบัตินั้นทำให้ภาพมีไหวพริบ — ปรับให้เข้ากับรสนิยม!

หมายเหตุ: ตอนนี้ นี่อาจเป็นกรณีง่ายๆ ในการสลับคลาสหรือแอตทริบิวต์ หาก max-height: auto เท่ากับความสูงของเนื้อหา น่าเศร้าที่มันไม่ได้ ไปและตะโกนเกี่ยวกับเรื่องนี้ไปที่ W3C ที่นี่

มาดูกันว่าวิธีการนั้นปรากฏในโค้ดอย่างไร ความคิดเห็นที่มีหมายเลขแสดงขั้นตอนตรรกะที่เทียบเท่าจากด้านบนในโค้ด

นี่คือจาวาสคริปต์:

 // Get the containing element const container = document.querySelector(".container"); // Get content const content = document.querySelector(".content"); // 1. Get height of content you want to show/hide const heightOfContent = content.getBoundingClientRect().height; // Get the trigger element const btn = document.querySelector(".trigger"); // 2. Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { document.documentElement.classList.add("height-is-set"); 3. content.setAttribute("aria-hidden", "true"); }, 0); btn.addEventListener("click", function(e) { container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true"); // 5. Toggle aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true"); })

ซีเอสเอส:

 .content { transition: max-height 0.2s; overflow: hidden; } .content[aria-hidden="true"] { max-height: 0; } // 4. Set height to value of custom property .content[aria-hidden="false"] { max-height: var(--containerHeight, 1000px); }

ข้อสังเกต

แล้วหลายลิ้นชักล่ะ?

เมื่อคุณมีลิ้นชักแบบเปิดและซ่อนจำนวนหนึ่งบนหน้า คุณจะต้องวนซ้ำทุกช่องเนื่องจากอาจมีขนาดต่างกัน

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

setTimeout นั้น

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

ให้ยิงเมื่อเพจพร้อมเท่านั้น

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

 window.addEventListener("load", initDrawers);

อันที่จริงเราจะเพิ่มสิ่งนั้นในไม่ช้า

ข้อมูลเพิ่มเติม-* คุณลักษณะบนคอนเทนเนอร์

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

ค่าเริ่มต้นในคุณสมบัติที่กำหนดเอง

มีการตั้งค่าเริ่มต้นในคุณสมบัติที่กำหนดเองใน CSS 1000px นั่นคือบิตหลังเครื่องหมายจุลภาคภายในค่า: var(--containerHeight, 1000px) ซึ่งหมายความว่าหาก --containerHeight ไปในทางใดทางหนึ่ง คุณควรจะมีช่วงการเปลี่ยนภาพที่ดี เห็นได้ชัดว่าคุณสามารถตั้งค่าให้เหมาะกับกรณีการใช้งานของคุณได้

ทำไมไม่เพียงแค่ใช้ค่าเริ่มต้นที่ 100,000px?

เนื่องจาก max-height: auto ไม่เปลี่ยนแปลง คุณอาจสงสัยว่าเหตุใดคุณจึงไม่เลือกใช้ความสูงที่ตั้งไว้ของค่าที่มากกว่าที่คุณต้องการ ตัวอย่างเช่น 10000000px?

ปัญหาของแนวทางนี้คือการเปลี่ยนแปลงจากความสูงนั้นเสมอ หากกำหนดระยะเวลาการเปลี่ยนภาพเป็น 1 วินาที การเปลี่ยนภาพจะ 'เดินทาง' 10000000px ในไม่กี่วินาที หากเนื้อหาของคุณสูงเพียง 50px คุณจะได้รับเอฟเฟกต์การเปิด/ปิดอย่างรวดเร็ว!

ตัวดำเนินการ Ternary สำหรับการสลับ

เราใช้ ternary operator สองครั้งเพื่อสลับแอตทริบิวต์ บางคนเกลียดพวกเขา แต่ฉันและคนอื่น ๆ รักพวกเขา พวกเขาอาจดูแปลกไปหน่อยและ 'รหัสกอล์ฟ' เล็กน้อยในตอนแรก แต่เมื่อคุณคุ้นเคยกับไวยากรณ์แล้ว ฉันคิดว่ามันอ่านตรงไปตรงมามากกว่ามาตรฐาน if/else

สำหรับผู้ที่ไม่ได้ฝึกหัด ตัวดำเนินการ ternary คือรูปแบบย่อของ if/else พวกเขาเขียนเพื่อให้สิ่งที่ต้องตรวจสอบเป็นอันดับแรก แล้ว ? แยกสิ่งที่ต้องดำเนินการหากการตรวจสอบเป็นจริง จากนั้น : เพื่อแยกความแตกต่างว่าควรเรียกใช้สิ่งใดหากการตรวจสอบเป็นเท็จ

 isThisTrue ? doYesCode() : doNoCode();

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

จะเกิดอะไรขึ้นกับการปรับขนาดหน้า?

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

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

A11Y . พิเศษนิดหน่อย

เป็นไปได้ที่จะเพิ่มการพิจารณาเพิ่มเติมเล็กน้อยสำหรับการช่วยสำหรับการเข้าถึงโดยใช้แอตทริบิวต์ aria-expanded , aria-controls และ aria-labelledby สิ่งนี้จะบ่งชี้ได้ดีขึ้นถึงเทคโนโลยีช่วยเหลือเมื่อมีการเปิด/ขยายลิ้นชัก เราเพิ่ม aria-expanded="false" ให้กับมาร์กอัปปุ่มของเราควบคู่ไปกับ aria-controls="IDofcontent" โดยที่ IDofcontent คือค่าของ id ที่เราเพิ่มลงในคอนเทนเนอร์เนื้อหา

จากนั้นเราใช้โอเปอเรเตอร์ ternary อื่นเพื่อสลับแอตทริบิวต์ aria-expanded เมื่อคลิกใน JavaScript

รวมกันหมด

ด้วยการโหลดหน้าเว็บ หลายลิ้นชัก งานพิเศษ A11Y และการจัดการเหตุการณ์การปรับขนาด โค้ด JavaScript ของเรามีลักษณะดังนี้:

 var containers; function initDrawers() { // Get the containing elements containers = document.querySelectorAll(".container"); setHeights(); wireUpTriggers(); window.addEventListener("resize", setHeights); } window.addEventListener("load", initDrawers); function setHeights() { containers.forEach(container => { // Get content let content = container.querySelector(".content"); content.removeAttribute("aria-hidden"); // Height of content to show/hide let heightOfContent = content.getBoundingClientRect().height; // Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { container.classList.add("height-is-set"); content.setAttribute("aria-hidden", "true"); }, 0); }); } function wireUpTriggers() { containers.forEach(container => { // Get each trigger element let btn = container.querySelector(".trigger"); // Get content let content = container.querySelector(".content"); btn.addEventListener("click", () => { btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false" ? "true" : "false"); container.setAttribute( "data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true" ); content.setAttribute( "aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true" ); }); }); }

คุณสามารถเล่นกับ CodePen ได้ที่นี่:

แสดง/ซ่อนลิ้นชักอย่างง่าย (หลายรายการ) โดย Ben Frain บน CodePen

แสดง/ซ่อนลิ้นชักอย่างง่าย (หลายรายการ) โดย Ben Frain บน CodePen

สรุป

เป็นไปได้ที่จะปรับแต่งและจัดเตรียมเพิ่มเติมสำหรับสถานการณ์ต่างๆ มากขึ้นเรื่อยๆ แต่กลไกพื้นฐานของการสร้างลิ้นชักเปิดและปิดที่เชื่อถือได้สำหรับเนื้อหาของคุณควรอยู่ไม่ไกลเกินเอื้อม หวังว่าคุณจะตระหนักถึงอันตรายบางอย่างด้วย องค์ประกอบ details ไม่สามารถเคลื่อนไหวได้ max-height: auto ไม่ทำในสิ่งที่คุณหวังไว้ คุณไม่สามารถเพิ่มค่าความสูงสูงสุดจำนวนมากได้อย่างน่าเชื่อถือ และคาดว่าแผงเนื้อหาทั้งหมดจะเปิดขึ้นตามที่คาดไว้

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

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