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

กลยุทธ์บนเว็บ
โดยรวมแล้ว ฉันพบว่าคอมโพเนนต์นี้สร้างได้ง่ายมาก เนื่องจากมีฟีเจอร์สำคัญ 2-3 อย่างในแพลตฟอร์มเว็บ ดังนี้
scroll-snap-points
เพื่อการโต้ตอบด้วยการปัดและแป้นพิมพ์ที่ราบรื่น พร้อมตำแหน่งหยุดการเลื่อนที่เหมาะสม- Deep Link ผ่านแฮช URL สำหรับ เบราว์เซอร์ที่จัดการการตรึงการเลื่อนในหน้าและการรองรับการแชร์
- รองรับโปรแกรมอ่านหน้าจอด้วยมาร์กอัปองค์ประกอบ
<a>
และid="#hash"
prefers-reduced-motion
เพื่อเปิดใช้การเปลี่ยนฉากแบบครอสเฟดและการเลื่อนในหน้าเว็บทันที@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>
จากนั้นฉันก็ใส่ข้อความจำลองในบทความโดยมีจำนวนที่แตกต่างกัน และใส่ลิงก์ที่มีความยาวแตกต่างกันและชุดรูปภาพของชื่อ เมื่อมีเนื้อหาที่จะใช้แล้ว เราก็เริ่ม จัดวางได้
เลย์เอาต์แบบเลื่อน
คอมโพเนนต์นี้มีพื้นที่เลื่อน 3 ประเภท ได้แก่
- การนำทาง(สีชมพู)เลื่อนได้ในแนวนอน
- พื้นที่เนื้อหา(สีน้ำเงิน)เลื่อนได้ในแนวนอน
- รายการบทความแต่ละรายการ(สีเขียว) จะเลื่อนได้ในแนวตั้ง

องค์ประกอบที่เกี่ยวข้องกับการเลื่อนมี 2 ประเภท ได้แก่
- หน้าต่าง
กล่องที่มีขนาดที่กำหนดซึ่งมีoverflow
รูปแบบพร็อพเพอร์ตี้ - พื้นผิวที่มีขนาดใหญ่เกินไป
ในเลย์เอาต์นี้คือคอนเทนเนอร์รายการ ได้แก่ nav links, section articles และ article contents
เลย์เอาต์ <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, การจำกัดการเลื่อนเพื่อดักจับการเลื่อนเกิน, แถบเลื่อนที่ซ่อนไว้ สำหรับอุปกรณ์ระบบสัมผัส และสุดท้ายคือ scroll-snap สำหรับล็อกพื้นที่นำเสนอเนื้อหา เราสามารถเข้าถึงลำดับแท็บของแป้นพิมพ์ได้ และการโต้ตอบใดๆ จะนำโฟกัส อย่างเป็นธรรมชาติ คอนเทนเนอร์การเลื่อนสแนปยังมีการโต้ตอบสไตล์ภาพสไลด์ที่สวยงามจากแป้นพิมพ์ด้วย
เลย์เอาต์ส่วนหัวของแท็บ <nav>
ลิงก์นำทางต้องจัดวางเป็นบรรทัดเดียวโดยไม่มีการแบ่งบรรทัด จัดกึ่งกลางในแนวตั้ง และรายการลิงก์แต่ละรายการควรสแนปกับคอนเทนเนอร์ Scroll-Snap Swift work for 2021 CSS!
<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; }
ฉันเลือกให้บทความสแนปภายในตัวเลื่อนหลัก ฉันชอบมาก ที่รายการลิงก์การนำทางและองค์ประกอบบทความจะสแนปไปที่จุดเริ่มต้นแบบอินไลน์ ของคอนเทนเนอร์การเลื่อนที่เกี่ยวข้อง ดูเหมือนว่าจะเป็นความสัมพันธ์ที่ราบรื่น

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

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

เลย์เอาต์การเลื่อนเสร็จสมบูรณ์แล้ว ได้แก่ การสแนป การทำ Deep Link และการเข้าถึงด้วยแป้นพิมพ์ รากฐานที่มั่นคงสำหรับการปรับปรุง UX สไตล์ และความพึงพอใจ
ไฮไลต์ฟีเจอร์
วิดเจ็ตที่เลื่อนจะยังคงอยู่ในตำแหน่งที่ล็อกไว้ในระหว่างการปรับขนาด ซึ่งหมายความว่า JavaScript ไม่จำเป็นต้องนำสิ่งใดมาแสดงเมื่อหมุนอุปกรณ์หรือปรับขนาดเบราว์เซอร์ ลองใช้ในเครื่องมือสำหรับนักพัฒนาเว็บของ Chromium ในโหมดอุปกรณ์โดย เลือกโหมดอื่นที่ไม่ใช่โหมดที่ปรับตามอุปกรณ์ แล้วปรับขนาดกรอบอุปกรณ์ สังเกตว่าองค์ประกอบจะยังคงแสดงอยู่และล็อกไว้กับเนื้อหา ฟีเจอร์นี้พร้อมใช้งานแล้วตั้งแต่ Chromium อัปเดตการใช้งานให้ตรงกับข้อกำหนด ดูบล็อกโพสต์เกี่ยวกับฟีเจอร์นี้ได้ที่นี่
แอนิเมชัน
เป้าหมายของงานภาพเคลื่อนไหวที่นี่คือการเชื่อมโยงการโต้ตอบกับฟีดแบ็กของ UI อย่างชัดเจน ซึ่งจะช่วยแนะนําหรือช่วยเหลือผู้ใช้ให้ค้นพบเนื้อหาทั้งหมดได้อย่างราบรื่น (หวังว่า) ฉันจะเพิ่มการเคลื่อนไหวอย่างมีจุดประสงค์และ อย่างมีเงื่อนไข ตอนนี้ผู้ใช้สามารถระบุค่ากำหนดการเคลื่อนไหวในระบบปฏิบัติการของตนเองได้แล้ว และเราก็ยินดีเป็นอย่างยิ่งที่จะตอบสนองต่อค่ากำหนดของผู้ใช้ในอินเทอร์เฟซของเรา
เราจะลิงก์ขีดเส้นใต้ของแท็บกับตำแหน่งการเลื่อนบทความ การสแนปไม่ได้มีไว้เพื่อการจัดแนวที่สวยงามเท่านั้น แต่ยังเป็นการยึดจุดเริ่มต้นและจุดสิ้นสุดของภาพเคลื่อนไหวด้วย
วิธีนี้ช่วยให้<nav>
ซึ่งทำหน้าที่เหมือนมินิแมปเชื่อมต่อกับเนื้อหาได้
เราจะตรวจสอบค่ากำหนดการเคลื่อนไหวของผู้ใช้จากทั้ง CSS และ JS มี
หลายที่ที่ควรพิจารณา
ลักษณะการเลื่อน
คุณมีโอกาสที่จะปรับปรุงลักษณะการเคลื่อนไหวของทั้ง :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 รู้วิธีรับข้อมูลดังกล่าว ดังนั้นเราจะวนซ้ำใน children ด้วยตนเองและดึงค่าที่คำนวณแล้วในขณะรันไทม์
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);
};
หากสมมติว่าการเลื่อนหยุดลง การหารตำแหน่งการเลื่อนปัจจุบันจากความกว้าง ของพื้นที่เลื่อนควรให้ผลลัพธ์เป็นจำนวนเต็ม ไม่ใช่ทศนิยม จากนั้นฉันจะพยายาม ดึงรายการนำทางจากแคชของเราผ่านดัชนีที่คำนวณนี้ และหากพบ รายการใด ฉันจะส่งรายการที่ตรงกันเพื่อตั้งค่าให้ใช้งานอยู่
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