วิธีสร้าง Snapping Scroll Sections ด้วย CSS Scroll-Snap (ทางเลือก fullpage.js)

เผยแพร่แล้ว: 2022-07-19

ในบทช่วยสอนนี้ เราจะสร้างหน้าที่สแน็ปจากส่วนเต็มหน้าจอหนึ่งไปยังอีกส่วนหนึ่งในขณะที่ผู้ใช้เลื่อน เอฟเฟกต์นี้ได้รับความนิยมจากไลบรารี fullpage.js แต่ตอนนี้ มีตัวเลือกที่ดีกว่าเนื่องจากการเปลี่ยนแปลงใบอนุญาตสำหรับหน้าเต็ม (ตอนนี้คุณต้องจ่ายเงิน) โมดูล CSS scroll-snap ใหม่ และการรองรับเบราว์เซอร์ที่สมบูรณ์ของ API ผู้สังเกตการณ์ทางแยก

ดังนั้น หากคุณกำลังมองหาทางเลือกอื่นของ fullpage.js คุณอาจไม่ต้องการทั้งไลบรารีด้วยซ้ำ คุณสามารถเขียนโค้ดได้ด้วยตัวเอง!

อันที่จริงแล้วเอฟเฟกต์การสแนปคือ 100% CSS JS ใช้เพื่อทำให้องค์ประกอบจางลงเมื่อหน้าจอ "สแนป" เข้าสู่มุมมอง

เราจะใช้ประโยชน์จาก "Dinosaur Park scroll snap demo" โดย Michelle Barker ที่มีอยู่ใน CodePen เพื่อเป็นแรงบันดาลใจ:

https://codepen.io/michellebarker/pen/PowYKXJ ดูการสาธิตการเลื่อนสแน็ปอินของ Pen Dinosaur Park โดย Michelle Barker (@michellebarker) บน CodePen

โปรดทราบ คุณอาจต้องการดูสิ่งนี้บนหน้าจอที่ใหญ่ขึ้นเพื่อสัมผัสกับเอฟเฟกต์ "scroll snap" อย่างเต็มที่

ด้วยการใช้ CSS ร่วมกับ Intersection Observer เราจะสร้างแต่ละส่วนแบบเต็มหน้าจอของหน้า snap-scroll และทำให้เนื้อหาจางลงเมื่อเข้าสู่วิวพอร์ต

ยิ่งไปกว่านั้น โค้ด JS เพียงประมาณ 30 บรรทัดเท่านั้น! แอนิเมชั่นทำด้วย CSS; สิ่งที่เราต้องทำคือเข้าใจเมื่อส่วนและเนื้อหาเข้าสู่วิวพอร์ตของเราด้วย JS

1) สร้างเพจของคุณ

ขั้นแรก จะต้องสร้างเพจหรือกำหนดค่าหน้าที่มีอยู่เพื่อให้เข้ากันได้กับเอฟเฟกต์ง่ายๆ นี้ มีสองสิ่งที่ควรทราบที่นี่:

เรากำลังใช้ <main> องค์ประกอบซึ่งมีส่วนของเรา องค์ประกอบหลักนี้คือ 100vh เช่นเดียวกับแต่ละองค์ประกอบ <section> ภายใน

HTML:

<main> <section class="section section-1"> <div class="section__content" data-content >เนื้อหาภายใน</div> </section> <section class="section section-2"> <div class= "section__content" data-content >เนื้อหาภายใน</div> </section> <section class="section section-3"> <header class="section__header"> <h3 class="section__title">สำรวจและค้นพบ</h3 > </header> <div class="section__content" data-content >เนื้อหาภายใน</div> </section> </main>

ซีเอสเอส:

/* พฤติกรรมการเลื่อน */ @media (ความสูงขั้นต่ำ: 30em) { หลัก { scroll-snap-type: y บังคับ; ความสูง: 100vh; ล้น-y: เลื่อน; } } .section { สี: ขาว; ตำแหน่ง: ญาติ; scroll-snap-align: ศูนย์; จอแสดงผล: ดิ้น; ทิศทางดิ้น: คอลัมน์; ความสูงขั้นต่ำ: 100vh; } @media (ความสูงขั้นต่ำ: 30em) { .section (ความสูง: 100vh; } }

2) กำหนดค่า Snapping ด้วย CSS

image-16-11

ที่นี่เราจะเห็นว่ามีหลายส่วน แต่ละส่วนมีความสูง 100vh

<main> องค์ประกอบยังเป็น 100vh โดยมี overflow-y:scroll

สำหรับผู้ใช้ ลักษณะการเลื่อนจะเหมือนกับหน้าเว็บเริ่มต้น

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

ขั้นแรก แบบสอบถาม " @media (min-height: 30em) " จะปิดใช้เอฟเฟกต์การสแนปสำหรับขนาดหน้าจอมือถือที่สั้นในแง่ของความสูง ใต้ส่วนหลัก มีหนึ่งคุณสมบัติที่เปิดใช้งานการสแนป:

main { .... scroll-snap-type: y บังคับ; .... }

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

และภายใต้ส่วนของเรา เรามี:

.section { .... scroll-snap-align: center; .... }

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

เรียนรู้เพิ่มเติมเกี่ยวกับโมดูล CSS scroll-snap ที่นี่:

และมันก็ง่ายอย่างนั้น กำหนดลูกภายในคอนเทนเนอร์ <main> (ทุกส่วน) จะเลื่อนสแนปไปที่กึ่งกลางของคอนเทนเนอร์ <main> เนื่องจากทั้ง Main และ Section เป็น 100vh การเลื่อนจะเลื่อนจากส่วนหนึ่งไปอีกส่วน

3) เฟดเนื้อหาในการใช้ Intersection Observer

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

ในการทำเช่นนี้ เราจะใช้ผู้สังเกตการณ์ทางแยกเพื่อทำความเข้าใจตำแหน่งของส่วนต่างๆ ภายในวิวพอร์ต <main> ของเรา

ส่วน const = [...document.querySelectorAll("section")]; ให้ตัวเลือก = { rootMargin: "0px", เกณฑ์: 0.75, }; const callback = (รายการผู้สังเกตการณ์) => { items.forEach ((รายการ) => { const { target } = รายการ; if (entry.intersectionRatio >= 0.75) { target.classList.add ("is-visible") ; } อื่น { target.classList.remove("is-visible"); } }); }; const ผู้สังเกตการณ์ = IntersectionObserver ใหม่ (โทรกลับ, ตัวเลือก); section.forEach((ส่วน, ดัชนี) => { const sectionChildren = [...section.querySelector("[data-content]")).children]; sectionChildren.forEach((el, index) => { el.style .setProperty("--delay", `${index * 250}ms`); }); observer.observe(section); }); .setProperty("--delay", `${index * 250}ms`); }); ข้อสังเกต.

มีสองสิ่งที่ต้องทำที่นี่ ขั้นแรก จะระบุว่าส่วนใดอยู่ในวิวพอร์ต และเมื่อเข้าไปแล้ว จะเพิ่มคลาส CSS .is-visible เมื่อมันออกจากวิวพอร์ต มันจะลบคลาสนั้นออก

นอกจากนี้ เมื่อส่วนนี้เข้าสู่วิวพอร์ต ตามลำดับ (ทีละรายการ) จะตั้งค่าคุณสมบัติของการหน่วงเวลาเป็น 250ms ในแต่ละองค์ประกอบย่อย สิ่งนี้ทำให้เกิดการจางหายที่เซ

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

ในการทำงานนี้ เราต้องตั้งค่าแอนิเมชั่นเฟดอินสำหรับองค์ประกอบที่มีคลาส .is-visible เพิ่มเข้ามา

@media (ความสูงขั้นต่ำ: 30em) { .section__content > * { ความทึบ: 0; แปลง: translate3d(0, 4rem, 0); การเปลี่ยนแปลง: ความทึบ 800ms var(--delay), เปลี่ยน 800ms cubic-bezier(0.13, 0.07, 0.26, 0.99) var(--delay); } } .is-visible .section__content > * { ความทึบ: 1; แปลง: translate3d(0, 1rem, 0); } .is-visible .section__img { ความทึบ: 0.75; }

หากเราย้อนกลับไปที่ HTML จากตัวอย่างแรก คุณจะเห็นว่ามี div ล้อมรอบเนื้อหาที่มีคลาสของ .section__content และแอตทริบิวต์ data-content

<main> <section class="section section-1"> <div class="section__content" data-content >เนื้อหาภายใน</div> </section> <section class="section section-2"> <div class= "section__content" data-content >เนื้อหาภายใน</div> </section> <section class="section section-3"> <header class="section__header"> <h3 class="section__title">สำรวจและค้นพบ</h3 > </header> <div class="section__content" data-content >เนื้อหาภายใน</div> </section> </main>

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

สุดท้าย การ delay ที่ใช้อย่างต่อเนื่องกับองค์ประกอบย่อยแต่ละรายการจะทำให้การจางหายไป

หมายเหตุส่วนที่สาม ส่วนหัวของส่วนไม่อยู่ในส่วนข้อมูล-เนื้อหา ดังนั้นจะไม่จางหายไป (จะมีอยู่แล้ว)

The Code ทั้งหมด

นี่คือตัวอย่าง:

isotropic-2022-07-15-at-15-00-58

นี่คือรหัสทั้งหมดที่เราใช้ในการสาธิตของเรา:

HTML

<main> <section class="section section-1"> <div class="section__content" data-content> <img class="section__img section__img--left" src="https://s3-us-west-2 .amazonaws.com/s.cdpn.io/85648/trex.svg" alt="T-rex" /> <h2>เนื้อหาภายใน</h2> <p>บลา บลา บลา</p> </div> < /section> <section class="section section-2"> <div class="section__content" data-content> <h2>เนื้อหาภายใน</h2> <p>blah blah blah</p> </div> </ section> <section class="section section-3"> <header class="section__header"> <h3 class="section__title">สำรวจและค้นพบ</h3> </header> <div class="section__content" data-content > <h2>เนื้อหาภายใน</h2> <p>blah blah blah</p> </div> </section> </main>

CSS

@media (ความสูงขั้นต่ำ: 30em) { หลัก { ประเภทเลื่อนสแน็ป: y บังคับ; ความสูง: 100vh; ล้น-y: เลื่อน; } } .section { สี: ขาว; ตำแหน่ง: ญาติ; scroll-snap-align: ศูนย์; จอแสดงผล: ดิ้น; ทิศทางดิ้น: คอลัมน์; ความสูงขั้นต่ำ: 100vh; } @media (ความสูงขั้นต่ำ: 30em) { .section (ความสูง: 100vh; } } @media (ความสูงต่ำสุด: 30em) { .section__content > * { ความทึบ: 0; แปลง: translate3d(0, 4rem, 0); การเปลี่ยนแปลง: ความทึบ 800ms var(--delay), เปลี่ยน 800ms cubic-bezier(0.13, 0.07, 0.26, 0.99) var(--delay); } } .is-visible .section__content > * { ความทึบ: 1; แปลง: translate3d(0, 1rem, 0); } .is-visible .section__img { ความทึบ: 0.75; }

JS

ส่วน const = [...document.querySelectorAll("section")]; ให้ตัวเลือก = { rootMargin: "0px", เกณฑ์: 0.75, }; const callback = (รายการ, ผู้สังเกตการณ์) => { entries.forEach ((entry) => { const { target } = รายการ; if (entry.intersectionRatio >= 0.75) { target.classList.add ("is-visible") ; } อื่น ๆ { target.classList.remove("is-visible"); } }); }; const ผู้สังเกตการณ์ = IntersectionObserver ใหม่ (โทรกลับ, ตัวเลือก); section.forEach((ส่วน, ดัชนี) => { const sectionChildren = [...section.querySelector("[data-content]")).children]; sectionChildren.forEach((el, index) => { el.style .setProperty("--delay", `${index * 250}ms`); }); observer.observe(section); }); .setProperty("--delay", `${index * 250}ms`); }); ข้อสังเกต.

บทสรุป

นี่เป็นทางเลือก fullpage.js ที่สมบูรณ์แบบซึ่งไม่ได้หยุดเพียงแค่การสแนประหว่างส่วนต่างๆ แบบเต็มหน้าจอ นอกจากนี้เรายังใช้ API ผู้สังเกตการณ์ทางแยกเพื่อทำความเข้าใจเมื่อส่วนใดส่วนหนึ่งเข้าสู่หน้า จากนั้นจึงใช้ภาพเคลื่อนไหวที่เซแบบไดนามิก คุณสามารถใช้ตัวอย่างพื้นฐานนี้เพื่อสร้างและสร้างเอฟเฟกต์สุดเจ๋งด้วย vanilla JS และ CSS ที่น้อยที่สุด