การสร้างคอมโพเนนต์การนำทางด้านข้าง

ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างแถบด้านข้างแบบเลื่อนออกที่ปรับเปลี่ยนตามอุปกรณ์

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

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

ภาพรวม

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

เดโมเลย์เอาต์ที่ปรับเปลี่ยนตามอุปกรณ์จากเดสก์ท็อปเป็นอุปกรณ์เคลื่อนที่
ธีมสว่างและธีมดาร์กใน iOS และ Android

กลยุทธ์บนเว็บ

ในการสำรวจคอมโพเนนต์นี้ เรามีความสุขที่ได้รวมฟีเจอร์สําคัญของแพลตฟอร์มเว็บเข้าด้วยกัน ดังนี้

  1. CSS :target
  2. ตารางกริด CSS
  3. การแปลง CSS
  4. คิวรีสื่อ CSS สำหรับวิวพอร์ตและค่ากําหนดของผู้ใช้
  5. JS สำหรับfocus การปรับปรุง UX

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

คลาสสมมติ :target ของ CSS

ลิงก์ <a> รายการหนึ่งตั้งค่าแฮช URL เป็น #sidenav-open และอีกรายการหนึ่งตั้งค่าเป็นว่าง ('') สุดท้าย องค์ประกอบมี id เพื่อจับคู่แฮช

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

การคลิกลิงก์แต่ละลิงก์เหล่านี้จะเปลี่ยนสถานะแฮชของ URL ของหน้าเว็บ จากนั้นฉันจะแสดงและซ่อนแถบด้านข้างด้วยพร็อกซีคลาส ดังนี้

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

CSS Grid

ก่อนหน้านี้ฉันใช้เฉพาะเลย์เอาต์และคอมโพเนนต์ของแถบด้านข้างในตำแหน่งสัมบูรณ์หรือตำแหน่งคงที่ แต่ตารางกริดที่มีไวยากรณ์ grid-area ช่วยให้เรากําหนดองค์ประกอบหลายรายการให้กับแถวหรือคอลัมน์เดียวกันได้

สแต็ก

องค์ประกอบเลย์เอาต์หลัก #sidenav-container คือตารางกริดที่สร้าง 1 แถวและ 2 คอลัมน์ โดยแต่ละรายการมีชื่อเป็น stack เมื่อพื้นที่มีจำกัด CSS จะกำหนดองค์ประกอบย่อยทั้งหมดขององค์ประกอบ <main> ให้มีชื่อตารางกริดเดียวกัน โดยวางองค์ประกอบทั้งหมดไว้ในพื้นที่เดียวกันเพื่อสร้างกอง

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> คือองค์ประกอบที่เคลื่อนไหวซึ่งมีการนำทางด้านข้าง โดยมีองค์ประกอบย่อย 2 รายการ ได้แก่ คอนเทนเนอร์การนำทาง <nav> ที่มีชื่อว่า [nav] และพื้นหลัง <a> ที่มีชื่อว่า [escape] ซึ่งใช้ปิดเมนู

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

ปรับ 2fr และ 1fr เพื่อหาสัดส่วนที่ต้องการสำหรับเมนูที่วางซ้อนและปุ่มปิดของพื้นที่เชิงลบ

การสาธิตสิ่งที่จะเกิดขึ้นเมื่อคุณเปลี่ยนอัตราส่วน

การเปลี่ยนรูปแบบและการเปลี่ยน 3 มิติของ CSS

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

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

เมื่อเริ่มใช้ภาพเคลื่อนไหว เราต้องการเริ่มต้นด้วยการคำนึงถึงการช่วยเหลือพิเศษสำหรับผู้ทุพพลภาพเป็นอันดับแรก

การเคลื่อนไหวที่เข้าถึงได้

ไม่ใช่ทุกคนที่ต้องการประสบการณ์การเคลื่อนไหวแบบเลื่อนออก ในโซลูชันของเรา ค่ากําหนดนี้จะมีผลโดยการปรับตัวแปร CSS --duration ภายใน Media Query ค่าการค้นหาสื่อนี้แสดงถึงค่ากําหนดของระบบปฏิบัติการของผู้ใช้สําหรับการเคลื่อนไหว (หากมี)

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
การสาธิตการโต้ตอบที่มีและไม่ได้ใช้ระยะเวลา

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

การเปลี่ยนฉาก การเปลี่ยนรูปแบบ การเปลี่ยนภาษา

แถบด้านข้างออก (ค่าเริ่มต้น)

หากต้องการตั้งค่าสถานะเริ่มต้นของแถบด้านข้างในอุปกรณ์เคลื่อนที่เป็นสถานะ "นอกหน้าจอ" ฉันจะจัดตําแหน่งองค์ประกอบด้วย transform: translateX(-110vw)

โปรดทราบว่าเราได้เพิ่ม 10vw อื่นลงในโค้ดนอกหน้าจอทั่วไปของ -100vw เพื่อไม่ให้ box-shadow ของแถบด้านข้างแสดงในวิวพอร์ตหลักเมื่อซ่อนอยู่

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
แผงด้านข้างใน

เมื่อองค์ประกอบ #sidenav ตรงกับ :target ให้ตั้งค่าตำแหน่ง translateX() เป็น 0 ของฐานบ้าน และดู CSS เลื่อนองค์ประกอบจากตำแหน่ง "ออก" ของ -110vw ไปยังตำแหน่ง "เข้า" ของ 0 เหนือ var(--duration) เมื่อมีการแฮช URL

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

ระดับการเข้าถึงของการเปลี่ยน

เป้าหมายตอนนี้คือการซ่อนเมนูจากโปรแกรมอ่านหน้าจอเมื่อเมนูปรากฏขึ้น เพื่อไม่ให้ระบบโฟกัสไปที่เมนูที่อยู่นอกหน้าจอ เราทําได้โดยการตั้งค่าการเปลี่ยนระดับการมองเห็นเมื่อ :target มีการเปลี่ยนแปลง

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

การปรับปรุง UX ด้านการช่วยเหลือพิเศษ

โซลูชันนี้อาศัยการเปลี่ยนแปลง URL เพื่อจัดการสถานะ คุณควรใช้องค์ประกอบ <a> ที่นี่ และองค์ประกอบดังกล่าวจะมีฟีเจอร์การช่วยเหลือพิเศษที่ยอดเยี่ยมให้ใช้งานฟรี มาตกแต่งองค์ประกอบแบบอินเทอร์แอกทีฟด้วยป้ายกำกับที่สื่อถึงเจตนาอย่างชัดเจนกัน

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
การสาธิต UX ของการบรรยายและโต้ตอบด้วยแป้นพิมพ์

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

:is(:hover, :focus)

ตัวเลือกเสมือนที่ใช้งานได้จริงของ CSS ที่มีประโยชน์นี้ช่วยให้เรารวมสไตล์โฮเวอร์เข้ากับโฟกัสได้อย่างรวดเร็ว

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

ใส่ JavaScript

กด escape เพื่อปิด

แป้น Escape บนแป้นพิมพ์ควรปิดเมนูใช่ไหม มาเดินสายไฟกัน

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
ประวัติการเข้าชมของเบราว์เซอร์

หากต้องการป้องกันไม่ให้การโต้ตอบแบบเปิดและปิดซ้อนกันหลายรายการในประวัติของเบราว์เซอร์ ให้เพิ่ม JavaScript ต่อไปนี้ในบรรทัดของปุ่มปิด

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

ซึ่งจะเป็นการนํารายการประวัติ URL ออกเมื่อปิด ราวกับว่าไม่เคยเปิดเมนู

Focus UX

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

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

เมื่อเมนูด้านข้างเปิดขึ้น ให้โฟกัสที่ปุ่มปิด เมื่อแถบด้านข้างปิดลง ให้โฟกัสที่ปุ่มเปิด ฉันทําเช่นนี้โดยการเรียก focus() ในองค์ประกอบใน JavaScript

บทสรุป

ตอนนี้คุณรู้วิธีที่เราทําแล้ว คุณจะทําอย่างไร โครงสร้างคอมโพเนนต์นี้จึงมีความสนุก Who's going to make the 1st version with slots? 🙂

มาลองใช้แนวทางที่หลากหลายและดูวิธีทั้งหมดในการสร้างบนเว็บกัน สร้างGlitch แล้วทวีตเวอร์ชันของคุณมาให้เรา เราจะเพิ่มเวอร์ชันของคุณลงในส่วนรีมิกซ์ของชุมชนด้านล่าง

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