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

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

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

สาธิต

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

ภาพรวม

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

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

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

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

โดยรวมแล้ว เราพบว่าการสร้างคอมโพเนนต์นี้ค่อนข้างตรงไปตรงมา เนื่องด้วยฟีเจอร์สําคัญ 2-3 อย่างของแพลตฟอร์มเว็บ

  • 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 ของคอนเทนเนอร์การเลื่อนที่เกี่ยวข้อง ดูเหมือนว่าจะเป็นความสัมพันธ์ที่ราบรื่น

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

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

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

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

แถบเลื่อน 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 ฉันสามารถสลับค่ากำหนดและสาธิตรูปแบบการเปลี่ยนแบบต่างๆ ได้ 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
});

ฉันต้องการให้สิ่งหนึ่งติดตามตําแหน่งการเลื่อนของอีกสิ่งหนึ่ง และการสร้าง 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) จะไฮไลต์ลิงก์ ส่วนเฟรมหลักอื่นๆ จะเป็นสีข้อความมาตรฐาน การใช้ลูปที่ซ้อนกันทำให้การดำเนินการค่อนข้างตรงไปตรงมา เนื่องจากลูปด้านนอกคือรายการการนำทางแต่ละรายการ และลูปด้านในคือคีย์เฟรมส่วนบุคคลของรายการการนำทางแต่ละรายการ ฉันจะตรวจสอบว่าองค์ประกอบของลูปด้านนอกเหมือนกับของลูปด้านในหรือไม่ และใช้ข้อมูลดังกล่าวเพื่อดูว่ามีการเลือกองค์ประกอบนั้นหรือไม่

เราสนุกมากที่ได้เขียนบทความนี้ มาก

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

บทสรุป

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

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

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