ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างคอมโพเนนต์แท็บที่คล้ายกับที่พบในแอป iOS และ Android
ในโพสต์นี้ เราต้องการแชร์แนวคิดในการสร้างคอมโพเนนต์แท็บสําหรับเว็บที่ปรับเปลี่ยนตามพื้นที่โฆษณา รองรับอินพุตจากอุปกรณ์หลายเครื่อง และทํางานได้กับเบราว์เซอร์ต่างๆ ลองใช้เดโม
หากต้องการดูวิดีโอ โปรดดูโพสต์เวอร์ชัน YouTube ที่นี่
ภาพรวม
แท็บเป็นคอมโพเนนต์ทั่วไปของระบบการออกแบบ แต่อาจมีรูปร่างและรูปแบบได้หลายแบบ ก่อนหน้านี้เรามีแท็บบนเดสก์ท็อปที่สร้างจากองค์ประกอบ <frame>
และตอนนี้เรามีคอมโพเนนต์บนอุปกรณ์เคลื่อนที่ที่ลื่นไหลซึ่งทำให้เนื้อหาเคลื่อนไหวตามคุณสมบัติทางกายภาพ
ทั้งหมดนี้พยายามทำสิ่งเดียวกันคือประหยัดพื้นที่
ปัจจุบันสิ่งสําคัญของประสบการณ์การใช้งานแท็บคือพื้นที่การนําทางด้วยปุ่ม ซึ่งจะสลับการแสดงเนื้อหาในกรอบการแสดงผล พื้นที่เนื้อหาต่างๆ จำนวนมากใช้พื้นที่เดียวกัน แต่แสดงแบบมีเงื่อนไขตามปุ่มที่เลือกในการนําทาง
กลยุทธ์บนเว็บ
โดยรวมแล้ว เราพบว่าการสร้างคอมโพเนนต์นี้ค่อนข้างตรงไปตรงมา เนื่องด้วยฟีเจอร์สําคัญ 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 ประเภท ได้แก่
- การนำทาง (สีชมพู) เลื่อนได้แนวนอน
- พื้นที่เนื้อหา (สีน้ำเงิน) เลื่อนได้แนวนอน
- รายการบทความแต่ละรายการ (สีเขียว) จะเลื่อนขึ้นลงได้
องค์ประกอบที่เกี่ยวข้องกับการเลื่อนมี 2 ประเภท ได้แก่
- หน้าต่าง
กล่องที่มีมิติข้อมูลที่กําหนดซึ่งมีสไตล์พร็อพเพอร์ตี้overflow
- แพลตฟอร์มขนาดใหญ่
ในเลย์เอาต์นี้ แพลตฟอร์มคือคอนเทนเนอร์รายการต่างๆ ได้แก่ ลิงก์การนำทาง บทความส่วนต่างๆ และเนื้อหาบทความ
เลย์เอาต์ <snap-tabs>
เลย์เอาต์ระดับบนสุดที่ฉันเลือกคือ Flex (Flexbox) ฉันตั้งค่าทิศทางเป็น column
เพื่อให้ส่วนหัวและส่วนเรียงตามแนวตั้ง นี่คือหน้าต่างเลื่อนแรกของเรา และซ่อนทุกอย่างด้วยการแสดงผลที่มากเกินไปซ่อนอยู่ ส่วนหัวและส่วนต่างๆ จะใช้การเลื่อนผ่านในเร็วๆ นี้ โดยเป็นโซนแยกกัน
<snap-tabs> <header></header> <section></section> </snap-tabs>
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 againstneeding 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }
กลับไปที่แผนภาพ 3 หน้าจอสีสันสดใส
<header>
พร้อมแล้วที่จะใช้เป็นคอนเทนเนอร์การเลื่อน (สีชมพู)<section>
พร้อมที่จะเป็นตัวควบคุม(สีน้ำเงิน) เลื่อน
เฟรมที่เราไฮไลต์ด้านล่างด้วย VisBug ช่วยให้เราสามารถดูหน้าต่างที่คอนเทนเนอร์การเลื่อนสร้างขึ้น
เลย์เอาต์แท็บ <header>
เลย์เอาต์ถัดไปเกือบจะเหมือนกัน ฉันใช้ Flex เพื่อสร้างการจัดเรียงแนวตั้ง
<snap-tabs> <header> <nav></nav> <span class="snap-indicator"></span> </header> <section></section> </snap-tabs>
header { display: flex; flex-direction: column; }
.snap-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
<nav> <a></a> <a></a> <a></a> <a></a> </nav>
nav { display: flex; & a { scroll-snap-align: start; display: inline-flex; align-items: center; white-space: nowrap; } }
แต่ละลิงก์จะมีสไตล์และขนาดของตนเอง ดังนั้นเลย์เอาต์การนำทางจึงต้องระบุเพียงทิศทางและลำดับเท่านั้น แถบนำทางที่มีขนาดต่างกันทำให้การเปลี่ยนระหว่างแท็บสนุกขึ้นเมื่อตัวบ่งชี้ปรับขนาดให้เข้ากับเป้าหมายใหม่ เบราว์เซอร์จะแสดงผลแถบเลื่อนหรือไม่นั้นขึ้นอยู่กับจำนวนองค์ประกอบที่อยู่ในส่วนนี้
เลย์เอาต์แท็บ <section>
ส่วนนี้เป็นรายการแบบยืดหยุ่นและต้องเป็นผู้ใช้พื้นที่ส่วนใหญ่ นอกจากนี้ ยังต้องสร้างคอลัมน์สำหรับวางบทความด้วย ขอขอบคุณอีกครั้งสำหรับความร่วมมือใน CSS 2021 block-size: 100%
จะขยายองค์ประกอบนี้ให้เต็มองค์ประกอบหลักมากที่สุด จากนั้นจะสร้างชุดคอลัมน์ที่มีความกว้างเท่ากับ100%
ขององค์ประกอบหลักสำหรับเลย์เอาต์ของตัวเอง เปอร์เซ็นต์เหมาะสําหรับกรณีนี้เนื่องจากเราได้เขียนข้อจำกัดที่เข้มงวดในรายการหลัก
<section> <article></article> <article></article> <article></article> <article></article> </section>
section { block-size: 100%; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; }
เหมือนกับว่าเรากำลังพูดว่า "ขยายในแนวตั้งให้ได้มากที่สุดเท่าที่จะทำได้" (โปรดจำส่วนหัวที่เราตั้งค่าเป็น flex-shrink: 0
: นั่นคือการป้องกันการขยายนี้) ซึ่งจะกำหนดความสูงของแถวสำหรับชุดคอลัมน์ความสูงเต็ม สไตล์ auto-flow
บอกให้ตารางกริดจัดวางองค์ประกอบย่อยในแนวนอนเสมอ ไม่มีการตัดขึ้นบรรทัดใหม่ ซึ่งเป็นสิ่งที่เราต้องการเพื่อให้องค์ประกอบย่อยแสดงเกินขอบหน้าต่างหลัก
บางครั้งฉันก็เข้าใจยากเหมือนกัน องค์ประกอบส่วนนี้พอดีกับกล่อง แต่ยังสร้างชุดกล่องด้วย เราหวังว่าภาพและคำอธิบายจะเป็นประโยชน์
เลย์เอาต์แท็บ <article>
ผู้ใช้ควรเลื่อนเนื้อหาบทความได้ และแถบเลื่อนควรปรากฏขึ้นก็ต่อเมื่อมีเนื้อหาที่แสดงเกินขอบเขตเท่านั้น องค์ประกอบบทความเหล่านี้อยู่ในตำแหน่งที่เรียบร้อย องค์ประกอบดังกล่าวเป็นองค์ประกอบหลักของการเลื่อนและองค์ประกอบย่อยของการเลื่อนในเวลาเดียวกัน เบราว์เซอร์จัดการการโต้ตอบด้วยการสัมผัส เมาส์ และแป้นพิมพ์ที่ยากลำบากให้เราได้
<article> <h2></h2> <p></p> <p></p> <h2></h2> <p></p> <p></p> ... </article>
article { scroll-snap-align: start; overflow-y: auto; overscroll-behavior-y: contain; }
ฉันเลือกให้บทความแสดงในแถบเลื่อนหลัก เราชอบมากที่รายการลิงก์การนำทางและองค์ประกอบบทความจัดวางตามค่าเริ่มต้นที่ inline-start ของคอนเทนเนอร์การเลื่อนที่เกี่ยวข้อง ดูเหมือนว่าจะเป็นความสัมพันธ์ที่ราบรื่น
บทความคือองค์ประกอบย่อยของตารางกริด และขนาดของบทความได้รับการกําหนดไว้ล่วงหน้าให้เป็นพื้นที่ในวิวพอร์ตที่เราต้องการให้ UX เลื่อน ซึ่งหมายความว่าฉันไม่จําเป็นต้องใช้สไตล์ความสูงหรือความกว้างใดๆ ที่นี่ ฉันจําเป็นต้องกําหนดวิธีแสดงผลที่เกินขอบเท่านั้น เราตั้งค่า overflow-y เป็น auto แล้วจับการโต้ตอบการเลื่อนด้วยพร็อพเพอร์ตี้ overscroll-behavior ที่มีประโยชน์
สรุปพื้นที่การเลื่อน 3 พื้นที่
ด้านล่างนี้ฉันได้เลือกในการตั้งค่าระบบให้ "แสดงแถบเลื่อนเสมอ" เราคิดว่าการทำให้เลย์เอาต์ทำงานได้เมื่อเปิดการตั้งค่านี้สำคัญกว่าการที่เราตรวจสอบเลย์เอาต์และการประสานงานการเลื่อน
เราคิดว่าการเห็นส่วนที่เป็นร่องของแถบเลื่อนในคอมโพเนนต์นี้ช่วยให้เห็นตำแหน่งของพื้นที่เลื่อน ทิศทางที่รองรับ และวิธีที่แต่ละส่วนโต้ตอบกันอย่างชัดเจน ลองพิจารณาว่าเฟรมหน้าต่างเลื่อนแต่ละเฟรมเหล่านี้เป็นองค์ประกอบหลักแบบ Flex หรือ Grid ของเลย์เอาต์อย่างไร
เครื่องมือสำหรับนักพัฒนาเว็บช่วยให้เราเห็นภาพได้ดังนี้
เลย์เอาต์การเลื่อนสมบูรณ์แล้ว มีการจับคู่ ทำการ 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 คีย์เฟรม โดยขึ้นอยู่กับตำแหน่งการเลื่อนของส่วนเลื่อน จุดยึดช่วยแบ่งคีย์เฟรมออกจากกันอย่างชัดเจนและช่วยเพิ่มความรู้สึกว่าภาพเคลื่อนไหวนั้นทำงานร่วมกัน
ผู้ใช้สามารถควบคุมภาพเคลื่อนไหวด้วยการโต้ตอบ โดยจะเห็นความกว้างและตําแหน่งของเคอร์เซอร์เปลี่ยนจากส่วนหนึ่งไปยังอีกส่วนหนึ่ง ซึ่งจะติดตามการเลื่อนได้อย่างสมบูรณ์
คุณอาจไม่สังเกตเห็น แต่เราภูมิใจกับการเปลี่ยนสีเมื่อรายการการนำทางที่ไฮไลต์ได้รับการเลือก
สีเทาอ่อนที่ไม่ได้เลือกจะดูจางลงเมื่อรายการที่ไฮไลต์มีคอนทราสต์มากขึ้น การเปลี่ยนสีข้อความเป็นอะไรที่พบได้ทั่วไป เช่น เมื่อวางเมาส์เหนือหรือเลือกข้อความ แต่การเปลี่ยนสีนั้นเมื่อเลื่อนหน้าเว็บและซิงค์กับตัวบ่งชี้ขีดล่างถือเป็นอีกระดับ
วิธีการของฉันมีดังนี้
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 เป็นคําศัพท์เกี่ยวกับอุปกรณ์เคลื่อนที่มากกว่า แต่เราคิดว่าแท็บสามารถตอบสนองวัตถุประสงค์ของ 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 แล้วทวีตเวอร์ชันของคุณมาให้เรา เราจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง
รีมิกซ์ของชุมชน
- @devnook, @rob_dodson และ @DasSurma กับ Web Components: article
- @jhvanderschee พร้อมปุ่ม: Codepen