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

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

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

หากต้องการดูวิดีโอ โปรดดูโพสต์นี้ใน YouTube

ภาพรวม

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

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

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

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

  1. CSS :target
  2. กริด CSS
  3. การเปลี่ยนรูปแบบ CSS
  4. CSS Media Queries สำหรับวิวพอร์ตและค่ากำหนดของผู้ใช้
  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 หน้าเว็บ จากนั้นฉันจะใช้ Pseudo-Class เพื่อแสดงและซ่อนแถบนำทางด้านข้าง

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

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

CSS Grid

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

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

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

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

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

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

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

เปลี่ยน แปลง แปล

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

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

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

@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 in

เมื่อองค์ประกอบ #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 ออกเมื่อปิด ทำให้ดูเหมือนว่าไม่เคยเปิดเมนู มาก่อน

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

บทสรุป

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

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

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