สร้างแผงเนื้อหาที่ขยายและทำสัญญาของคุณเอง
เผยแพร่แล้ว: 2022-03-10จนถึงตอนนี้เราเรียกพวกมันว่า 'แผงเปิดและปิด' แต่พวกมันยังถูกอธิบายว่าเป็นแผงส่วนขยายหรือเรียกง่ายๆ ว่าแผงขยาย
เพื่อชี้แจงให้ชัดเจนว่าเรากำลังพูดถึงอะไร ไปที่ตัวอย่างนี้ใน CodePen:
แสดง/ซ่อนลิ้นชักอย่างง่าย (หลายรายการ) โดย Ben Frain บน CodePen
นั่นคือสิ่งที่เราจะสร้างในบทช่วยสอนสั้นๆ นี้
จากมุมมองของการใช้งาน มีสองสามวิธีในการทำให้ภาพเคลื่อนไหวเปิดและปิดอย่างที่เรากำลังมองหา แต่ละแนวทางมีข้อดีและข้อเสียต่างกันไป ฉันจะแบ่งปันรายละเอียดของวิธีการ 'ไปสู่' โดยละเอียดในบทความนี้ พิจารณาแนวทางที่เป็นไปได้ก่อน
แนวทาง
เทคนิคเหล่านี้มีหลากหลายรูปแบบ แต่โดยทั่วไปแล้ว วิธีการแบ่งออกเป็นสามประเภท:
- ทำให้เคลื่อนไหว/เปลี่ยน
height
หรือความสูงmax-height
ของเนื้อหา - ใช้
transform: translateY
เพื่อย้ายองค์ประกอบไปยังตำแหน่งใหม่ สร้างภาพลวงตาของการปิดแผงแล้วแสดงผล DOM อีกครั้งเมื่อการแปลงเสร็จสมบูรณ์ด้วยองค์ประกอบในตำแหน่งสิ้นสุด - ใช้ไลบรารี่ที่มีการผสมผสาน/รูปแบบต่างๆ ของ 1 หรือ 2!
ข้อควรพิจารณาของแต่ละแนวทาง
จากมุมมองด้านประสิทธิภาพ การใช้การเปลี่ยนรูปแบบจะมีประสิทธิภาพมากกว่าการสร้างภาพเคลื่อนไหวหรือการเปลี่ยนความสูง/ความสูงสูงสุด ด้วยการแปลงรูปแบบ องค์ประกอบที่เคลื่อนไหวจะถูกแรสเตอร์และเปลี่ยนโดย GPU นี่เป็นการดำเนินการที่ราคาถูกและง่ายสำหรับ GPU ดังนั้นประสิทธิภาพจึงมีแนวโน้มที่จะดีขึ้นมาก
ขั้นตอนพื้นฐานเมื่อใช้วิธีการแปลงคือ:
- รับความสูงของเนื้อหาที่จะยุบ
- ย้ายเนื้อหาและทุกอย่างหลังจากนั้นด้วยความสูงของเนื้อหาที่จะยุบโดยใช้
transform: translateY(Xpx)
ดำเนินการแปลงร่างด้วยการเปลี่ยนตัวเลือกเพื่อให้เอฟเฟกต์ภาพที่น่าพึงพอใจ - ใช้ 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 เล็กน้อย
ลอจิกพื้นฐาน
ตรรกะพื้นฐานคือ:
- ให้โหลดหน้าวัดความสูงของเนื้อหา
- กำหนดความสูงของเนื้อหาบนคอนเทนเนอร์เป็นค่าของคุณสมบัติที่กำหนดเอง CSS
- ซ่อนเนื้อหาทันทีโดยเพิ่มแอตทริบิวต์
aria-hidden: "true"
ลงในเนื้อหา การใช้aria-hidden
ช่วยให้เทคโนโลยีอำนวยความสะดวกรู้ว่าเนื้อหาถูกซ่อนไว้เช่นกัน - วางสาย CSS เพื่อให้
max-height
ของคลาสเนื้อหาเป็นค่าของคุณสมบัติที่กำหนดเอง - การกดปุ่มทริกเกอร์ของเราจะเปลี่ยนคุณสมบัติ 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
สรุป
เป็นไปได้ที่จะปรับแต่งและจัดเตรียมเพิ่มเติมสำหรับสถานการณ์ต่างๆ มากขึ้นเรื่อยๆ แต่กลไกพื้นฐานของการสร้างลิ้นชักเปิดและปิดที่เชื่อถือได้สำหรับเนื้อหาของคุณควรอยู่ไม่ไกลเกินเอื้อม หวังว่าคุณจะตระหนักถึงอันตรายบางอย่างด้วย องค์ประกอบ details
ไม่สามารถเคลื่อนไหวได้ max-height: auto
ไม่ทำในสิ่งที่คุณหวังไว้ คุณไม่สามารถเพิ่มค่าความสูงสูงสุดจำนวนมากได้อย่างน่าเชื่อถือ และคาดว่าแผงเนื้อหาทั้งหมดจะเปิดขึ้นตามที่คาดไว้
ในการย้ำแนวทางของเราที่นี่: วัดคอนเทนเนอร์ จัดเก็บความสูงเป็นคุณสมบัติ CSS ที่กำหนดเอง ซ่อนเนื้อหา จากนั้นใช้การสลับอย่างง่ายเพื่อสลับระหว่าง max-height
0 และความสูงที่คุณจัดเก็บไว้ในคุณสมบัติที่กำหนดเอง
อาจไม่ใช่วิธีการที่มีประสิทธิภาพดีที่สุด แต่ฉันพบว่าในสถานการณ์ส่วนใหญ่นั้นเพียงพออย่างสมบูรณ์และได้ประโยชน์จากการนำไปปฏิบัติที่ค่อนข้างตรงไปตรงมา