การสร้างคอมโพเนนต์แท็บ

ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างคอมโพเนนต์แท็บที่คล้ายกับที่พบในแอป iOS และ Android

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

สาธิต

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

ภาพรวม

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

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

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

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

สรุปทั้งหมด ผมพบว่าคอมโพเนนต์นี้สร้างได้ง่ายมาก ด้วยคุณสมบัติ ที่สำคัญของแพลตฟอร์มเว็บบางอย่าง:

  • scroll-snap-points สําหรับการโต้ตอบด้วยการปัดและแป้นพิมพ์ที่ราบรื่นพร้อมตําแหน่งการหยุดเลื่อนที่เหมาะสม
  • Deep Link ผ่านแฮช URL สำหรับ การรองรับการตรึงการเลื่อนในหน้าเว็บและ การแชร์ของเบราว์เซอร์ที่มีการจัดการ
  • รองรับโปรแกรมอ่านหน้าจอพร้อมมาร์กอัปองค์ประกอบ <a> และ id="#hash"
  • prefers-reduced-motion เพื่อเปิดใช้ทรานซิชันแบบ Crossfade และการเลื่อนในหน้าเว็บทันที
  • ฟีเจอร์เว็บ @scroll-timeline ฉบับร่างสำหรับการขีดเส้นใต้และเปลี่ยนสีแท็บที่เลือกแบบไดนามิก

HTML

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

มีองค์ประกอบเนื้อหาเชิงโครงสร้างบางอย่างอยู่ในนั้น เช่น ลิงก์และ :target เราต้องการรายการลิงก์ที่ <nav> เหมาะสําหรับใช้ และรายการองค์ประกอบ <article> ที่ <section> เหมาะสําหรับใช้ แฮชลิงก์แต่ละรายการจะจับคู่กับส่วนต่างๆ ซึ่งช่วยให้เบราว์เซอร์เลื่อนดูสิ่งต่างๆ ผ่านการเชื่อมโยง

ผู้ใช้คลิกปุ่มลิงก์เพื่อเลื่อนไปยังเนื้อหาที่โฟกัส

เช่น การคลิกลิงก์จะโฟกัสบทความ :target ใน Chrome 89 โดยอัตโนมัติ โดยไม่ต้องใช้ JS จากนั้นผู้ใช้สามารถเลื่อนดูเนื้อหาบทความ โดยใช้อุปกรณ์อินพุตได้เหมือนเช่นเคย เนื้อหานี้เป็นเนื้อหาเสริมตามที่ระบุไว้ในมาร์กอัป

เราใช้มาร์กอัปต่อไปนี้ในการจัดระเบียบแท็บ

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

ฉันสร้างการเชื่อมต่อระหว่างองค์ประกอบ <a> กับ <article> ด้วยพร็อพเพอร์ตี้ href และ id ดังนี้

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

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

เลย์เอาต์แบบเลื่อน

พื้นที่การเลื่อนในคอมโพเนนต์นี้มี 3 ประเภทที่แตกต่างกัน

  • การนำทาง (สีชมพู) สามารถเลื่อนในแนวนอน
  • พื้นที่เนื้อหา (สีน้ำเงิน) เลื่อนได้แนวนอน
  • รายการบทความแต่ละรายการ (สีเขียว) จะเลื่อนขึ้นลงได้
กล่องสีสันสดใส 3 กล่องที่มีลูกศรบังคับทิศทางการจับคู่สีซึ่งแสดงพื้นที่ที่เลื่อนและแสดงทิศทางที่พวกเขาจะเลื่อน

องค์ประกอบที่เกี่ยวข้องกับการเลื่อนมี 2 ประเภท ได้แก่

  1. หน้าต่าง
    กล่องที่มีมิติข้อมูลที่กําหนดซึ่งมีสไตล์พร็อพเพอร์ตี้ overflow
  2. แพลตฟอร์มขนาดใหญ่
    ในเลย์เอาต์นี้ แพลตฟอร์มคือคอนเทนเนอร์รายการต่างๆ ได้แก่ ลิงก์การนำทาง บทความส่วนต่างๆ และเนื้อหาบทความ

เลย์เอาต์ <snap-tabs>

เลย์เอาต์ระดับบนสุดที่ฉันเลือกคือ Flex (Flexbox) ฉันตั้งค่าทิศทางเป็น column เพื่อให้ส่วนหัวและส่วนเรียงตามแนวตั้ง นี่คือหน้าต่างเลื่อนแรกของเรา และซ่อนทุกอย่างด้วยการแสดงผลที่ตัดออก ส่วนหัวและส่วนต่างๆ จะใช้การเลื่อนผ่านในเร็วๆ นี้ โดยจะเป็นโซนแยกกัน

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

ชี้กลับไปที่แผนภาพการเลื่อน 3 แบบสีสันสดใส

  • <header> พร้อมแล้วที่จะใช้เป็นคอนเทนเนอร์การเลื่อน (สีชมพู)
  • <section> เตรียมพร้อมเป็นคอนเทนเนอร์แบบเลื่อน (สีน้ำเงิน)

เฟรมที่เราไฮไลต์ด้านล่างด้วย VisBug ช่วยให้เราสามารถดูหน้าต่างที่คอนเทนเนอร์การเลื่อนสร้างขึ้น

องค์ประกอบส่วนหัวและส่วนมีสีชมพูร้อนวางซ้อนอยู่ ซึ่งแสดงพื้นที่ที่องค์ประกอบเหล่านั้นใช้ไปในคอมโพเนนต์

เลย์เอาต์แท็บ <header>

เลย์เอาต์ถัดไปเกือบจะเหมือนกัน ฉันใช้ Flex เพื่อสร้างการจัดเรียงแนวตั้ง

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator ควรเดินทางในแนวนอนไปพร้อมกับกลุ่มลิงก์ และเลย์เอาต์ส่วนหัวนี้จะช่วยกำหนดระยะนั้น ไม่มีองค์ประกอบที่มีตำแหน่งแบบสัมบูรณ์

องค์ประกอบ nav และ span.indicator มีการซ้อนทับสีชมพูร้อน ซึ่งแสดงพื้นที่ที่องค์ประกอบใช้ไปในคอมโพเนนต์

ถัดไปคือรูปแบบการเลื่อน ปรากฏว่าเราแชร์สไตล์การเลื่อนระหว่างพื้นที่เลื่อนแนวนอน 2 พื้นที่ (ส่วนหัวและส่วน) ได้ ดังนั้นฉันจึงสร้างคลาสยูทิลิตี .scroll-snap-x

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

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

เลย์เอาต์ส่วนหัวของแท็บ <nav>

ลิงก์การนำทางต้องจัดวางเป็นบรรทัดเดียวโดยไม่มีการแบ่งบรรทัด จัดกึ่งกลางในแนวตั้ง และแต่ละรายการของลิงก์ควรยึดกับคอนเทนเนอร์การเลื่อน Swift work สำหรับ CSS ปี 2021

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

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

องค์ประกอบ a ของส่วนนําทางมีการวางซ้อนสีชมพูร้อน ซึ่งแสดงพื้นที่ที่องค์ประกอบใช้ไปในคอมโพเนนต์และตำแหน่งที่องค์ประกอบแสดงเกินขอบ

เลย์เอาต์แท็บ <section>

ส่วนนี้เป็นรายการแบบยืดหยุ่นและต้องเป็นผู้ใช้พื้นที่ส่วนใหญ่ นอกจากนี้ ยังต้องสร้างคอลัมน์สำหรับวางบทความด้วย ขอขอบคุณอีกครั้งสำหรับการทำงานที่ยอดเยี่ยมใน CSS 2021 block-size: 100% จะขยายองค์ประกอบนี้ให้เต็มองค์ประกอบหลักมากที่สุด จากนั้นจะสร้างชุดคอลัมน์ที่มีความกว้างเท่ากับ100%ขององค์ประกอบหลักสำหรับเลย์เอาต์ของตัวเอง เปอร์เซ็นต์เหมาะสําหรับกรณีนี้เนื่องจากเราได้เขียนข้อจำกัดที่เข้มงวดในรายการหลัก

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

เหมือนกับที่เราพูดว่า "ขยายในแนวตั้งให้มากที่สุดเท่าที่จะเป็นไปได้" (อย่าลืมว่าส่วนหัวที่เราตั้งค่าเป็น flex-shrink: 0 มีไว้เพื่อป้องกันการดันขยายนี้) ซึ่งเป็นการกำหนดความสูงของแถวสำหรับชุดคอลัมน์ความสูงแบบเต็ม สไตล์ auto-flow บอกให้ตารางกริดจัดวางองค์ประกอบย่อยในแนวนอนเสมอ ไม่มีการตัดขึ้นบรรทัดใหม่ ซึ่งเป็นสิ่งที่เราต้องการเพื่อให้องค์ประกอบย่อยแสดงเกินขอบหน้าต่างหลัก

องค์ประกอบบทความมีการวางซ้อนสีชมพูร้อน ซึ่งแสดงพื้นที่ที่องค์ประกอบใช้ไปในคอมโพเนนต์และตำแหน่งที่องค์ประกอบแสดงเกินขอบ

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

เลย์เอาต์แท็บ <article>

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

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

ฉันเลือกให้บทความแสดงในแถบเลื่อนหลัก เราชอบมากที่รายการลิงก์การนำทางและองค์ประกอบบทความจัดวางตามค่าเริ่มต้นที่ inline-start ของคอนเทนเนอร์การเลื่อนที่เกี่ยวข้อง ดูเหมือนว่าจะเป็นความสัมพันธ์ที่ราบรื่น

องค์ประกอบบทความและองค์ประกอบย่อยมีการวางซ้อน Hotpink อยู่เพื่ออธิบายพื้นที่ที่องค์ประกอบใช้ไปในคอมโพเนนต์และทิศทางที่องค์ประกอบล้น

บทความคือองค์ประกอบย่อยของตารางกริด และขนาดของบทความได้รับการกําหนดไว้ล่วงหน้าให้เป็นพื้นที่ในวิวพอร์ตที่เราต้องการให้ UX เลื่อน ซึ่งหมายความว่าฉันไม่จําเป็นต้องใช้สไตล์ความสูงหรือความกว้างใดๆ ที่นี่ ฉันจําเป็นต้องกําหนดวิธีแสดงผลที่เกินขอบเท่านั้น เราตั้งค่า overflow-y เป็น auto แล้วจับการโต้ตอบการเลื่อนด้วยพร็อพเพอร์ตี้ overscroll-behavior ที่มีประโยชน์

สรุปพื้นที่การเลื่อน 3 พื้นที่

ด้านล่างที่ฉันเลือกในการตั้งค่าระบบเป็น "แสดงแถบเลื่อนเสมอ" เราคิดว่าการทำให้เลย์เอาต์ทำงานได้เมื่อเปิดการตั้งค่านี้สำคัญกว่า 2 เท่า เนื่องจากเราต้องตรวจสอบเลย์เอาต์และการประสานงานการเลื่อน

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

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

เครื่องมือสำหรับนักพัฒนาเว็บช่วยให้เราแสดงข้อมูลต่อไปนี้

พื้นที่เลื่อนมีการวางซ้อนเครื่องมือตารางกริดและ Flexbox ซึ่งแสดงพื้นที่ที่เครื่องมือใช้ไปในคอมโพเนนต์และทิศทางที่เครื่องมือตกขอบ
เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ Chromium ที่แสดงเลย์เอาต์องค์ประกอบการนำทางแบบ Flexbox ที่มีองค์ประกอบ Anchor จำนวนมาก เลย์เอาต์ส่วนตารางกริดที่มีองค์ประกอบบทความจำนวนมาก และองค์ประกอบบทความที่มีย่อหน้าและองค์ประกอบส่วนหัวจำนวนมาก

เลย์เอาต์การเลื่อนสมบูรณ์แล้ว มีการจับคู่ ทำการ Deep Link ได้ และเข้าถึงได้ด้วยแป้นพิมพ์ รากฐานที่แข็งแกร่งสำหรับการปรับปรุง UX, สไตล์ และความสุข

ฟีเจอร์เด่น

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

แอนิเมชัน

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

ฉันจะลิงก์แท็บที่ขีดเส้นใต้กับตำแหน่งการเลื่อนบทความ การยึดตำแหน่งไม่เพียงแต่จะจัดแนวให้เรียบร้อยเท่านั้น แต่ยังเป็นการยึดจุดเริ่มต้นและจุดสิ้นสุดของภาพเคลื่อนไหวด้วย ซึ่งช่วยให้ <nav> ซึ่งทำหน้าที่เป็นแผนที่ขนาดเล็กเชื่อมต่อกับเนื้อหาอยู่เสมอ เราจะตรวจสอบค่ากำหนดการเคลื่อนไหวของผู้ใช้จากทั้ง CSS และ JS มีสถานที่ดีๆ 2-3 แห่งที่ควรพิจารณา

ลักษณะการเลื่อน

คุณมีโอกาสปรับปรุงพฤติกรรมการเคลื่อนไหวของทั้ง :target และ element.scrollIntoView() ได้ ซึ่งจะแสดงทันทีโดยค่าเริ่มต้น เบราว์เซอร์จะตั้งค่าตำแหน่งการเลื่อนเท่านั้น แล้วในกรณีที่เราต้องการเปลี่ยนไปยังตำแหน่งการเลื่อนนั้นแทนการกะพริบที่ตำแหน่งนั้นล่ะ

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

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

สัญญาณบอกสถานะแท็บ

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

ใน Chromium Devtools ฉันสามารถสลับค่ากำหนดและสาธิตการเปลี่ยน 2 รูปแบบที่แตกต่างกัน เราสนุกมากที่สร้างวิดีโอนี้

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

เราจะซ่อน .snap-indicator เมื่อผู้ใช้ต้องการลดการเคลื่อนไหวเนื่องจากเราไม่จำเป็นต้องใช้อีกต่อไป จากนั้นฉันจะแทนที่ด้วยสไตล์ border-block-end และ transition นอกจากนี้ โปรดสังเกตการโต้ตอบของแท็บว่ารายการการนำทางที่ใช้งานอยู่ไม่เพียงมีการไฮไลต์ขีดเส้นใต้ของแบรนด์เท่านั้น แต่สีข้อความยังเข้มกว่าด้วย องค์ประกอบที่ใช้งานอยู่จะมีคอนทราสต์ของสีข้อความสูงกว่าและมีแสงสะท้อนที่สว่าง

การใช้ CSS เพิ่มมาอีกไม่กี่บรรทัดจะทำให้ผู้ชมรู้สึกว่าคุณใส่ใจ (หมายความว่าเราเคารพค่ากำหนดการเคลื่อนไหวของพวกเขาเป็นอย่างดี) ชอบมาก

@scroll-timeline

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

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

ก่อนอื่น เราจะตรวจสอบค่ากําหนดการเคลื่อนไหวของผู้ใช้จาก JavaScript หากผลลัพธ์คือ false ซึ่งหมายความว่าผู้ใช้ต้องการลดการเคลื่อนไหว เราจะไม่แสดงเอฟเฟกต์การเคลื่อนไหวของการลิงก์การเลื่อน

if (motionOK) {
  // motion based animation code
}

ขณะเขียนบทความนี้ เบราว์เซอร์ที่รองรับภาษา @scroll-timeline ยังไม่มี นี่เป็นข้อกำหนดฉบับร่างที่มีการใช้งานแบบทดลองเท่านั้น แต่ก็มี polyfill ซึ่งเราใช้ในเดโมนี้

ScrollTimeline

แม้ว่าทั้ง CSS และ JavaScript จะสร้างไทม์ไลน์การเลื่อนได้ แต่ฉันเลือกใช้ JavaScript เพื่อให้ใช้การวัดองค์ประกอบแบบเรียลไทม์ในภาพเคลื่อนไหวได้

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

ฉันต้องการให้ 1 สิ่งติดตามตำแหน่งการเลื่อนของอีกสิ่งหนึ่ง และเมื่อสร้าง ScrollTimeline ฉันกำหนดไดรเวอร์ของลิงก์เลื่อนคือ scrollSource โดยปกติแล้วภาพเคลื่อนไหวบนเว็บจะทำงานตามกรอบเวลาทั่วโลก แต่เมื่อใช้ sectionScrollTimeline ที่กำหนดเองในหน่วยความจำ ผมเปลี่ยนทุกอย่างนี้ได้

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

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

คีย์เฟรมแบบไดนามิก

มีวิธีสร้างภาพเคลื่อนไหวด้วย CSS แบบประกาศล้วนที่มีประสิทธิภาพมากซึ่งใช้ @scroll-timeline แต่ภาพเคลื่อนไหวที่ฉันเลือกทำนั้นเป็นแบบไดนามิกเกินไป ไม่มีวิธีเปลี่ยนไปมาระหว่างความกว้าง auto และไม่มีวิธีสร้างจำนวนคีย์เฟรมแบบไดนามิกโดยอิงตามความยาวของวิดีโอย่อย

แต่ JavaScript รู้วิธีรับข้อมูลดังกล่าว เราจึงจะวนซ้ำผ่านรายการย่อยด้วยตนเองและรับค่าที่คำนวณแล้วที่รันไทม์

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

สําหรับ tabnavitem แต่ละรายการ ให้แยกโครงสร้างตําแหน่ง offsetLeft และแสดงผลสตริงที่ใช้ตําแหน่งดังกล่าวเป็นค่า translateX ซึ่งจะสร้างคีย์เฟรมการเปลี่ยนรูปแบบ 4 รายการสำหรับภาพเคลื่อนไหว ซึ่งจะทำเช่นเดียวกันนี้สำหรับความกว้าง แต่ละความกว้างจะถามว่าความกว้างแบบไดนามิกคืออะไร จากนั้นก็จะใช้เป็นค่าคีย์เฟรม

ตัวอย่างเอาต์พุตตามค่ากำหนดแบบอักษรและเบราว์เซอร์ของฉันมีดังนี้

คีย์เฟรม TranslateX:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

คีย์เฟรมความกว้าง:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

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

แท็บที่ใช้งานอยู่และแท็บที่ไม่ได้ใช้งานจะแสดงพร้อมกับการวางซ้อน VisBug ซึ่งแสดงคะแนนคอนทราสต์ที่ผ่านเกณฑ์สำหรับทั้ง 2 แท็บ

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

คุณอาจไม่เคยสังเกตมาก่อน แต่ผมภูมิใจมากที่การเปลี่ยนสีเมื่อรายการ การนำทางที่ไฮไลต์ถูกเลือก

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

วิธีการของฉันมีดังนี้

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

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

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

เฟรมหลักที่มีสี var(--text-active-color) จะไฮไลต์ลิงก์ ส่วนเฟรมหลักอื่นๆ จะเป็นสีข้อความมาตรฐาน ลูปที่ซ้อนกันในจุดนี้จะค่อนข้างตรงไปตรงมา เนื่องจากลูปด้านนอกคือรายการการนำทางแต่ละรายการ และลูปด้านในคือคีย์เฟรมส่วนตัวของ Navitem แต่ละรายการ ฉันจะตรวจสอบว่าองค์ประกอบของลูปด้านนอกเหมือนกับของลูปด้านในหรือไม่ และใช้ข้อมูลนี้เพื่อดูว่ามีการเลือกหรือไม่

ฉันสนุกกับการเขียนเรื่องนี้มากเลย มาก

การเพิ่มประสิทธิภาพ JavaScript มากยิ่งขึ้น

โปรดทราบว่าหัวใจสําคัญของสิ่งที่เราแสดงให้คุณเห็นที่นี่ทํางานได้โดยไม่ต้องใช้ JavaScript ด้วยเหตุนี้ เรามาดูกันว่าเราจะปรับปรุงได้อย่างไรเมื่อ JS พร้อมใช้งาน

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

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

การซิงค์การสิ้นสุดการเลื่อน

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

วิธีการรอให้เลื่อนจนจบมีดังนี้ js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

เมื่อมีการเลื่อนส่วนต่างๆ ให้ล้างการหมดเวลาของส่วน (หากมี) และเริ่มส่วนใหม่ เมื่อส่วนต่างๆ หยุดเลื่อนแล้ว อย่าล้างการหมดเวลา และเริ่มทํางาน 100 มิลลิวินาทีหลังจากหยุดพัก เมื่อเริ่มทํางาน ให้เรียกใช้ฟังก์ชันที่พยายามค้นหาตําแหน่งที่ผู้ใช้หยุด

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

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

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

การตั้งค่าแท็บที่ใช้งานอยู่จะเริ่มต้นด้วยการล้างแท็บที่ใช้งานอยู่ในปัจจุบัน จากนั้นกำหนดแอตทริบิวต์สถานะแอ็กทีฟให้กับรายการนำทางที่เข้ามาใหม่ การเรียกใช้ scrollIntoView() มีการโต้ตอบกับ CSS ที่สนุกซึ่งควรค่าแก่การสังเกต

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

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

บทสรุป

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

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

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