ภาพรวมพื้นฐานเกี่ยวกับวิธีสร้างคอมโพเนนต์แท็บที่คล้ายกับที่พบในแอป iOS และ Android
ในโพสต์นี้ เราต้องการแชร์แนวคิดในการสร้างคอมโพเนนต์แท็บสําหรับเว็บที่ปรับเปลี่ยนตามพื้นที่โฆษณา รองรับอินพุตจากอุปกรณ์หลายเครื่อง และทํางานได้กับเบราว์เซอร์ต่างๆ ลองใช้เดโม
หากต้องการดูวิดีโอ โปรดดูโพสต์เวอร์ชัน YouTube ที่นี่
ภาพรวม
แท็บเป็นคอมโพเนนต์ทั่วไปของระบบการออกแบบ แต่อาจมีรูปร่างและรูปแบบได้หลายแบบ ช่วงแรกคือแท็บบนเดสก์ท็อปที่สร้างขึ้นจากเอลิเมนต์ <frame>
และตอนนี้เรามีคอมโพเนนต์อุปกรณ์เคลื่อนที่แบบเนยๆ ซึ่งสร้างภาพเคลื่อนไหวตามคุณสมบัติทางฟิสิกส์
ทั้งหมดนี้พยายามทำสิ่งเดียวกันคือประหยัดพื้นที่
ปัจจุบัน สิ่งสำคัญสำหรับประสบการณ์ของผู้ใช้แท็บคือพื้นที่สำหรับการนำทางด้วยปุ่ม ที่เปิด/ปิดการมองเห็นเนื้อหาในเฟรมแสดงผล พื้นที่เนื้อหาต่างๆ จำนวนมากใช้พื้นที่เดียวกัน แต่แสดงแบบมีเงื่อนไขตามปุ่มที่เลือกในการนําทาง
กลยุทธ์บนเว็บ
สรุปทั้งหมด ผมพบว่าคอมโพเนนต์นี้สร้างได้ง่ายมาก ด้วยคุณสมบัติ ที่สำคัญของแพลตฟอร์มเว็บบางอย่าง:
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 พื้นที่
ด้านล่างที่ฉันเลือกในการตั้งค่าระบบเป็น "แสดงแถบเลื่อนเสมอ" เราคิดว่าการทำให้เลย์เอาต์ทำงานได้เมื่อเปิดการตั้งค่านี้สำคัญกว่า 2 เท่า เนื่องจากเราต้องตรวจสอบเลย์เอาต์และการประสานงานการเลื่อน
เราคิดว่าการเห็นส่วนที่เป็นร่องของแถบเลื่อนในคอมโพเนนต์นี้ช่วยให้เห็นตำแหน่งของพื้นที่เลื่อน ทิศทางที่รองรับ และวิธีที่ส่วนต่างๆ โต้ตอบกันอย่างชัดเจน พิจารณาว่าเฟรมหน้าต่างเลื่อนแต่ละเฟรมเหล่านี้เป็นองค์ประกอบหลักของ 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 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 คีย์เฟรม โดยขึ้นอยู่กับตำแหน่งการเลื่อนของส่วนเลื่อน จุดยึดช่วยแบ่งคีย์เฟรมอย่างชัดเจนและช่วยเพิ่มความรู้สึกว่าภาพเคลื่อนไหวนั้นทำงานร่วมกัน
ผู้ใช้ขับเคลื่อนภาพเคลื่อนไหวจากการโต้ตอบของตน เห็นความกว้างและตำแหน่งของสัญญาณบอกสถานะที่เปลี่ยนไปจากส่วนหนึ่งไปยังส่วนถัดไป การติดตามจะสมบูรณ์แบบด้วยการเลื่อน
คุณอาจไม่เคยสังเกตมาก่อน แต่ผมภูมิใจมากที่การเปลี่ยนสีเมื่อรายการ การนำทางที่ไฮไลต์ถูกเลือก
สีเทาอ่อนที่ไม่ได้เลือกจะดูจางลงเมื่อรายการที่ไฮไลต์มีคอนทราสต์มากขึ้น การเปลี่ยนสีข้อความเป็นอะไรที่พบได้ทั่วไป เช่น เมื่อวางเมาส์เหนือหรือเลือกข้อความ แต่การเปลี่ยนสีนั้นเมื่อเลื่อนหน้าเว็บและซิงค์กับตัวบ่งชี้ขีดล่างถือเป็นอีกระดับ
วิธีการของฉันมีดังนี้
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 เป็นคำค้นหาสำหรับอุปกรณ์เคลื่อนที่มากกว่า แต่ผมคิดว่าจุดประสงค์ของ 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 แล้วทวีตเวอร์ชันของคุณมาให้เรา เราจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง
รีมิกซ์ของชุมชน
- @devnook, @rob_dodson และ @DasSurma กับ Web Components: article
- @jhvanderschee พร้อมปุ่ม: Codepen