การสร้างส่วนหัวแบบไดนามิกด้วยผู้สังเกตการณ์ทางแยก

เผยแพร่แล้ว: 2022-03-10
สรุปด่วน ↬ คุณเคยจำเป็นต้องสร้าง UI ที่องค์ประกอบบางอย่างบนหน้าจำเป็นต้องตอบสนองต่อองค์ประกอบขณะที่เลื่อนไปยังเกณฑ์ที่กำหนดภายในวิวพอร์ต หรือบางทีอาจเข้าและออกจากวิวพอร์ตเองหรือไม่ ใน JavaScript การแนบตัวฟังเหตุการณ์เพื่อเรียกใช้การเรียกกลับบน scroll อย่างต่อเนื่องอาจเป็นการเน้นที่ประสิทธิภาพ และหากใช้อย่างไม่ฉลาด อาจสร้างประสบการณ์ผู้ใช้ที่ซบเซาได้ แต่มีวิธีที่ดีกว่าด้วย Intersection Observer

Intersection Observer API เป็น JavaScript API ที่ช่วยให้เราสามารถสังเกตองค์ประกอบและตรวจจับเมื่อผ่านจุดที่ระบุในคอนเทนเนอร์แบบเลื่อน ซึ่งมักจะ (แต่ไม่เสมอไป) วิวพอร์ต ซึ่งจะทริกเกอร์ฟังก์ชันเรียกกลับ

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

การใช้งานพื้นฐาน

ในการใช้ Intersection Observer เราต้องสร้าง Observer ขึ้นมาก่อน ซึ่งต้องใช้พารามิเตอร์สองตัว: วัตถุที่มีตัวเลือกของผู้สังเกตการณ์ และฟังก์ชันเรียกกลับที่เราต้องการดำเนินการเมื่อใดก็ตามที่องค์ประกอบที่เรากำลังสังเกต (เรียกว่าเป้าหมายผู้สังเกตการณ์) ตัดกัน ด้วยรูท (คอนเทนเนอร์แบบเลื่อนซึ่งต้องเป็นบรรพบุรุษขององค์ประกอบเป้าหมาย)

 const options = { root: document.querySelector('[data-scroll-root]'), rootMargin: '0px', threshold: 1.0 } const callback = (entries, observer) => { entries.forEach((entry) => console.log(entry)) } const observer = new IntersectionObserver(callback, options)

เมื่อเราสร้างผู้สังเกตการณ์แล้ว เราต้องสั่งให้ผู้สังเกตการณ์ดูองค์ประกอบเป้าหมาย:

 const targetEl = document.querySelector('[data-target]') observer.observe(targetEl)

ค่าตัวเลือกใดๆ สามารถละเว้นได้ เนื่องจากค่าเหล่านั้นจะถอยกลับไปเป็นค่าเริ่มต้น:

 const options = { rootMargin: '0px', threshold: 1.0 }

หากไม่มีการระบุรูท ระบบจะจัดประเภทเป็นวิวพอร์ตของเบราว์เซอร์ ตัวอย่างโค้ดด้านบนแสดงค่าเริ่มต้นสำหรับทั้ง rootMargin และ threshold สิ่งเหล่านี้อาจมองเห็นได้ยาก ดังนั้นจึงควรอธิบาย:

rootMargin

ค่า rootMargin นั้นเหมือนกับการเพิ่มระยะขอบ CSS ให้กับองค์ประกอบรูท และเช่นเดียวกับระยะขอบ สามารถรับค่าได้หลายค่า รวมถึงค่าลบด้วย องค์ประกอบเป้าหมายจะถือว่าตัดกันเมื่อเทียบกับระยะขอบ

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

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

สี่เหลี่ยมสีส้มตัดกับราก แม้ว่าจะอยู่นอกพื้นที่ที่มองเห็นได้ (ตัวอย่างขนาดใหญ่)

rootMargin ค่าเริ่มต้นเป็น 0px แต่สามารถรับสตริงที่ประกอบด้วยหลายค่าได้ เช่นเดียวกับการใช้คุณสมบัติ margin ใน CSS

threshold

threshold สามารถประกอบด้วยค่าเดียวหรืออาร์เรย์ของค่าระหว่าง 0 ถึง 1 ซึ่งแสดงถึง สัดส่วนขององค์ประกอบที่ต้องอยู่ภายในขอบเขตของรากเพื่อให้ถือว่าตัดกัน การใช้ค่าเริ่มต้นเป็น 1 การเรียกกลับจะเริ่มทำงานเมื่อ 100% ขององค์ประกอบเป้าหมายมองเห็นได้ภายในรูท

เกณฑ์ 1, 0 และ 0.5 ตามลำดับส่งผลให้การโทรกลับเริ่มทำงานเมื่อมองเห็นได้ 100%, 0% และ 50% (ตัวอย่างขนาดใหญ่)

ไม่ใช่เรื่องง่ายที่จะเห็นภาพเมื่อองค์ประกอบจะถูกจัดประเภทเป็นมองเห็นได้โดยใช้ตัวเลือกเหล่านี้ ฉันได้สร้างเครื่องมือเล็กๆ เพื่อช่วยในการจับกับ Intersection Observer

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

การสร้างส่วนหัว

เมื่อเข้าใจหลักการพื้นฐานแล้ว มาเริ่มสร้างส่วนหัวแบบไดนามิกกันเถอะ เราจะเริ่มต้นด้วยหน้าเว็บที่แบ่งออกเป็นส่วนต่างๆ ภาพนี้แสดงเลย์เอาต์ที่สมบูรณ์ของเพจที่เราจะสร้าง:

(ตัวอย่างขนาดใหญ่)

ฉันได้รวมการสาธิตไว้ที่ส่วนท้ายของบทความนี้ ดังนั้นอย่าลังเลที่จะข้ามไปที่มันโดยตรงหากคุณต้องการยกเลิกการเลือกโค้ด (นอกจากนี้ยังมีที่เก็บ Github)

แต่ละส่วนมีความสูงขั้นต่ำ 100vh (แม้ว่าจะยาวกว่านี้ก็ได้ ขึ้นอยู่กับเนื้อหา) ส่วนหัวของเราได้รับการแก้ไขที่ด้านบนของหน้าและยังคงอยู่ในขณะที่ผู้ใช้เลื่อน (โดยใช้ position: fixed ) ส่วนต่าง ๆ มีพื้นหลังสีต่างกัน และเมื่อตรงกับส่วนหัว สีของส่วนหัวจะเปลี่ยนเพื่อเสริมสีของส่วนนั้น นอกจากนี้ยังมีเครื่องหมายเพื่อแสดงส่วนปัจจุบันที่ผู้ใช้อยู่ ซึ่งจะเลื่อนไปมาเมื่อส่วนถัดไปมาถึง เพื่อให้เราตรงไปยังโค้ดที่เกี่ยวข้องได้ง่ายขึ้น ฉันได้ตั้งค่าการสาธิตขั้นต่ำด้วยจุดเริ่มต้นของเรา (ก่อนที่เราจะเริ่มใช้ Intersection Observer API) ในกรณีที่คุณต้องการปฏิบัติตาม

มาร์กอัป

เราจะเริ่มต้นด้วย HTML สำหรับส่วนหัวของเรา นี่จะเป็นส่วนหัวที่ค่อนข้างง่ายพร้อมโฮมลิงค์และการนำทาง ไม่มีอะไรพิเศษเป็นพิเศษ แต่เราจะใช้แอตทริบิวต์ข้อมูลสองสามอย่าง: data-header สำหรับส่วนหัวเอง (เพื่อให้เราสามารถกำหนดเป้าหมายองค์ประกอบด้วย JS) และลิงก์สมอสามลิงก์ที่มีแอตทริบิวต์ data-link ซึ่งจะเลื่อนผู้ใช้ไปยังส่วนที่เกี่ยวข้องเมื่อคลิก:

 <header data-header> <nav class="header__nav"> <div class="header__left-content"> <a href="#0">Home</a> </div> <ul class="header__list"> <li> <a href="#about-us" data-link>About us</a> </li> <li> <a href="#flavours" data-link>The flavours</a> </li> <li> <a href="#get-in-touch" data-link>Get in touch</a> </li> </ul> </nav> </header>

ถัดไป HTML สำหรับส่วนที่เหลือของหน้าเว็บของเรา ซึ่งแบ่งออกเป็นส่วนต่างๆ เพื่อความกระชับ ฉันได้รวมเฉพาะส่วนที่เกี่ยวข้องกับบทความ แต่มาร์กอัปแบบเต็มรวมอยู่ในการสาธิต แต่ละส่วนมีแอตทริบิวต์ข้อมูลที่ระบุชื่อสีพื้นหลัง และ id ที่สอดคล้องกับลิงก์สมอตัวใดตัวหนึ่งในส่วนหัว:

 <main> <section data-section="raspberry"> <!--Section content--> </section> <section data-section="mint"> <!--Section content--> </section> <section data-section="vanilla"> <!--Section content--> </section> <section data-section="chocolate"> <!--Section content--> </section> </main>

เราจะวางตำแหน่งส่วนหัวของเราด้วย CSS เพื่อให้คงที่ที่ด้านบนของหน้าเมื่อผู้ใช้เลื่อน:

 header { position: fixed; width: 100%; }

เราจะกำหนดความสูงขั้นต่ำของส่วนต่างๆ และจัดกึ่งกลางเนื้อหาด้วย (โค้ดนี้ไม่จำเป็นสำหรับ Intersection Observer ในการทำงาน แต่มีไว้สำหรับการออกแบบเท่านั้น)

 section { padding: 5rem 0; min-height: 100vh; display: flex; justify-content: center; align-items: center; }

คำเตือน iframe

ขณะสร้างตัวอย่าง Codepen นี้ ฉันพบปัญหาที่น่าสับสนซึ่งโค้ด Intersection Observer ของฉันที่ น่าจะ ทำงานได้อย่างสมบูรณ์ล้มเหลวในการเรียกกลับที่จุดที่ถูกต้องของทางแยก แต่แทนที่จะยิงเมื่อองค์ประกอบเป้าหมายตัดกับขอบวิวพอร์ต หลังจากเกาหัวเล็กน้อย ฉันก็รู้ว่านี่เป็นเพราะใน Codepen เนื้อหาถูกโหลดภายใน iframe ซึ่งได้รับการปฏิบัติแตกต่างออกไป (ดูส่วนเอกสาร MDN เกี่ยวกับการตัดและสี่เหลี่ยมทางแยกสำหรับรายละเอียดทั้งหมด)

ในการแก้ปัญหาชั่วคราว ในการสาธิต เราสามารถรวมมาร์กอัปของเราไว้ในองค์ประกอบอื่น ซึ่งจะทำหน้าที่เป็นคอนเทนเนอร์การเลื่อน — รูทในตัวเลือก IO ของเรา — แทนที่จะเป็นวิวพอร์ตของเบราว์เซอร์ อย่างที่เราคาดไว้:

 <div class="scroller" data-scroller> <header data-header> <!--Header content--> </header> <main> <!--Sections--> </main> </div>

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

CSS

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

 :root { --mint: #5ae8d5; --chocolate: #573e31; --raspberry: #f2308e; --vanilla: #faf2c8; --headerText: var(--vanilla); --headerBg: var(--raspberry); }

เราจะใช้คุณสมบัติที่กำหนดเองเหล่านี้ในส่วนหัวของเรา:

 header { background-color: var(--headerBg); color: var(--headerText); }

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

 [data-section="raspberry"] { background-color: var(--raspberry); color: var(--vanilla); } [data-section="mint"] { background-color: var(--mint); color: var(--chocolate); } [data-section="vanilla"] { background-color: var(--vanilla); color: var(--chocolate); } [data-section="chocolate"] { background-color: var(--chocolate); color: var(--vanilla); }

นอกจากนี้เรายังสามารถกำหนดรูปแบบบางอย่างสำหรับส่วนหัวของเราเมื่อแต่ละส่วนอยู่ในมุมมอง:

 /* Header */ [data-theme="raspberry"] { --headerText: var(--raspberry); --headerBg: var(--vanilla); } [data-theme="mint"] { --headerText: var(--mint); --headerBg: var(--chocolate); } [data-theme="chocolate"] { --headerText: var(--chocolate); --headerBg: var(--vanilla); }

มีกรณีที่ชัดเจนกว่าสำหรับการใช้แอตทริบิวต์ data ที่นี่ เนื่องจากเราจะสลับแอตทริบิวต์ data-theme ของส่วนหัวในแต่ละทางแยก

การสร้างผู้สังเกตการณ์

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

 const header = document.querySelector('[data-header]') const sections = [...document.querySelectorAll('[data-section]')] const scrollRoot = document.querySelector('[data-scroller]') const options = { root: scrollRoot, rootMargin: `${header.offsetHeight * -1}px`, threshold: 0 }

เรากำลังตั้งค่าขีดจำกัด 0 เนื่องจากเราต้องการให้เริ่มทำงานหากส่วน ใด ส่วนหนึ่งของส่วนตัดกับระยะขอบราก

ก่อนอื่น เราจะสร้างการเรียกกลับเพื่อเปลี่ยนค่า data-theme ของส่วนหัว (สิ่งนี้ตรงไปตรงมามากกว่าการเพิ่มและลบคลาส โดยเฉพาะอย่างยิ่งเมื่อองค์ประกอบส่วนหัวของเราอาจมีการใช้คลาสอื่น)

 /* The callback that will fire on intersection */ const onIntersect = (entries) => { entries.forEach((entry) => { const theme = entry.target.dataset.section header.setAttribute('data-theme', theme) }) }

จากนั้นเราจะสร้างผู้สังเกตการณ์เพื่อดูส่วนที่ตัดกัน:

 /* Create the observer */ const observer = new IntersectionObserver(onIntersect, options) /* Set our observer to observe each section */ sections.forEach((section) => { observer.observe(section) })

ตอนนี้เราควรจะเห็นสีส่วนหัวของเราอัปเดตเมื่อแต่ละส่วนตรงกับส่วนหัว

See the Pen [ร้านไอศกรีม Happy Face – ขั้นตอนที่ 2] (https://codepen.io/smashingmag/pen/poPgpjZ) โดย Michelle Barker

ดูร้านไอศกรีม Pen Happy Face - ขั้นตอนที่ 2 โดย Michelle Barker

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

ค้นหาทิศทางการเลื่อน

เราจะตั้งค่าตัวแปรใน JS ของเราสำหรับทิศทางของการเลื่อนด้วยค่าเริ่มต้น 'up' และอีกค่าหนึ่งสำหรับตำแหน่งการเลื่อนที่ทราบล่าสุด ( prevYPosition ) จากนั้น ภายในการเรียกกลับ หากตำแหน่งเลื่อนมากกว่าค่าก่อนหน้า เราสามารถตั้งค่า direction เป็น 'down' หรือ 'up' หากกลับกัน

 let direction = 'up' let prevYPosition = 0 const setScrollDirection = () => { if (scrollRoot.scrollTop > prevYPosition) { direction = 'down' } else { direction = 'up' } prevYPosition = scrollRoot.scrollTop } const onIntersect = (entries, observer) => { entries.forEach((entry) => { setScrollDirection() /* ... */ }) }

นอกจากนี้ เราจะสร้างฟังก์ชันใหม่เพื่ออัปเดตสีของส่วนหัว โดยส่งผ่านไปยังส่วนเป้าหมายเป็นอาร์กิวเมนต์:

 const updateColors = (target) => { const theme = target.dataset.section header.setAttribute('data-theme', theme) } const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() updateColors(entry.target) }) }

จนถึงตอนนี้ เราไม่ควรเห็นการเปลี่ยนแปลงพฤติกรรมของส่วนหัวของเรา แต่ตอนนี้เรารู้ทิศทางการเลื่อนแล้ว เราก็สามารถส่งผ่านไปยังเป้าหมายอื่นสำหรับ updateColors() ของเราได้ หากทิศทางการเลื่อนขึ้น เราจะใช้เป้าหมายรายการ ถ้ามันลง เราจะใช้ส่วนถัดไป (ถ้ามี)

 const getTargetSection = (target) => { if (direction === 'up') return target if (target.nextElementSibling) { return target.nextElementSibling } else { return target } } const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() const target = getTargetSection(entry.target) updateColors(target) }) }

อย่างไรก็ตาม ยังมีปัญหาอีกประการหนึ่ง: ส่วนหัวจะอัปเดตไม่เฉพาะเมื่อส่วนนั้นกระทบกับส่วนหัว แต่เมื่อองค์ประกอบถัดไปปรากฏให้เห็นที่ด้านล่างของวิวพอร์ต นี่เป็นเพราะว่าผู้สังเกตการณ์ของเราทำการเรียกกลับสองครั้ง: หนึ่งครั้งเมื่อองค์ประกอบเข้ามา และอีกครั้งเมื่อออกจากองค์ประกอบ

เพื่อตรวจสอบว่าส่วนหัวควรอัปเดตหรือไม่ เราสามารถใช้คีย์ isIntersecting จากออบเจ็กต์ entry มาสร้างฟังก์ชันอื่นเพื่อคืนค่าบูลีนว่าสีส่วนหัวควรอัปเดตหรือไม่:

 const shouldUpdate = (entry) => { if (direction === 'down' && !entry.isIntersecting) { return true } if (direction === 'up' && entry.isIntersecting) { return true } return false }

เราจะอัปเดต onIntersect() ของเราตามลำดับ:

 const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() /* Do nothing if no need to update */ if (!shouldUpdate(entry)) return const target = getTargetSection(entry.target) updateColors(target) }) }

ตอนนี้สีของเราควรอัปเดตอย่างถูกต้อง เราสามารถตั้งค่าการเปลี่ยน CSS เพื่อให้เอฟเฟกต์ดีขึ้นเล็กน้อย:

 header { transition: background-color 200ms, color 200ms; } 

See the Pen [ร้านไอศกรีม Happy Face – ขั้นตอนที่ 3] (https://codepen.io/smashingmag/pen/bGWEaEa) โดย Michelle Barker

ดูร้านไอศกรีม Pen Happy Face - ขั้นตอนที่ 3 โดย Michelle Barker

การเพิ่มไดนามิกมาร์กเกอร์

ต่อไป เราจะเพิ่มเครื่องหมายที่ส่วนหัวซึ่งจะอัปเดตตำแหน่งเมื่อเราเลื่อนไปยังส่วนต่างๆ เราสามารถใช้องค์ประกอบเทียมสำหรับสิ่งนี้ ดังนั้นเราจึงไม่จำเป็นต้องเพิ่มอะไรใน HTML ของเรา เราจะใส่สไตล์ CSS ง่ายๆ เพื่อจัดตำแหน่งไว้ที่ด้านบนซ้ายของส่วนหัว และกำหนดสีพื้นหลัง เราใช้ currentColor สำหรับสิ่งนี้ เนื่องจากจะใช้ค่าของสีข้อความส่วนหัว:

 header::after { content: ''; position: absolute; top: 0; left: 0; height: 0.4rem; background-color: currentColor; }

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

 header::after { content: ''; position: absolute; top: 0; left: 0; height: 0.4rem; width: var(--markerWidth, 0); background-color: currentColor; transform: translate3d(var(--markerLeft, 0), 0, 0); }

ตอนนี้ เราสามารถเขียนฟังก์ชันที่จะอัปเดตความกว้างและตำแหน่งของเครื่องหมายที่จุดตัด:

 const updateMarker = (target) => { const id = target.id /* Do nothing if no target ID */ if (!id) return /* Find the corresponding nav link, or use the first one */ let link = headerLinks.find((el) => { return el.getAttribute('href') === `#${id}` }) link = link || headerLinks[0] /* Get the values and set the custom properties */ const distanceFromLeft = link.getBoundingClientRect().left header.style.setProperty('--markerWidth', `${link.clientWidth}px`) header.style.setProperty('--markerLeft', `${distanceFromLeft}px`) }

เราสามารถเรียกใช้ฟังก์ชันได้พร้อมๆ กับอัปเดตสี:

 const onIntersect = (entries) => { entries.forEach((entry) => { setScrollDirection() if (!shouldUpdate(entry)) return const target = getTargetSection(entry.target) updateColors(target) updateMarker(target) }) }

นอกจากนี้เรายังต้องกำหนดตำแหน่งเริ่มต้นสำหรับเครื่องหมาย เพื่อไม่ให้ปรากฏโดยไม่ทราบสาเหตุ เมื่อโหลดเอกสาร เราจะเรียก updateMarker() โดยใช้ส่วนแรกเป็นเป้าหมาย:

 document.addEventListener('readystatechange', e => { if (e.target.readyState === 'complete') { updateMarker(sections[0]) } })

สุดท้าย ให้เพิ่มการเปลี่ยน CSS เพื่อให้เครื่องหมายเลื่อนข้ามส่วนหัวจากลิงก์หนึ่งไปยังลิงก์ถัดไป ในขณะที่เรากำลังเปลี่ยนคุณสมบัติ width เราสามารถใช้ will-change เพื่อให้เบราว์เซอร์ทำการเพิ่มประสิทธิภาพได้

 header::after { transition: transform 250ms, width 200ms, background-color 200ms; will-change: width; }

เลื่อนเรียบ

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

 @media (prefers-reduced-motion: no-preference) { .scroller { scroll-behavior: smooth; } }

สาธิตขั้นสุดท้าย

การนำขั้นตอนข้างต้นทั้งหมดมารวมกันส่งผลให้เกิดการสาธิตที่สมบูรณ์

ดูปากกา [ร้านไอศกรีม Happy Face – Intersection Observer ตัวอย่าง](https://codepen.io/smashingmag/pen/XWRXVXQ) โดย Michelle Barker

ดูร้านไอศกรีม Pen Happy Face – Intersection Observer โดย Michelle Barker

รองรับเบราว์เซอร์

Intersection Observer ได้รับการสนับสนุนอย่างกว้างขวางในเบราว์เซอร์สมัยใหม่ ในกรณีที่จำเป็น ก็สามารถ polyfilled สำหรับเบราว์เซอร์รุ่นเก่า — แต่ฉันชอบใช้วิธีการเพิ่มประสิทธิภาพแบบก้าวหน้าถ้าเป็นไปได้ ในกรณีของส่วนหัวของเรา จะไม่ส่งผลเสียอย่างใหญ่หลวงต่อประสบการณ์ของผู้ใช้ในการจัดเตรียมเวอร์ชันที่เรียบง่ายและไม่เปลี่ยนแปลงสำหรับเบราว์เซอร์ที่ไม่สนับสนุน

ในการตรวจสอบว่ารองรับ Intersection Observer หรือไม่ เราสามารถใช้สิ่งต่อไปนี้:

 if ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { /* Code to execute if IO is supported */ } else { /* Code to execute if not supported */ }

ทรัพยากร

อ่านเพิ่มเติมเกี่ยวกับผู้สังเกตการณ์ทางแยก:

  • เอกสารประกอบที่กว้างขวาง พร้อมตัวอย่างการใช้งานจริงจาก MDN
  • เครื่องมือสร้างภาพทางแยกผู้สังเกตการณ์
  • การมองเห็นองค์ประกอบกำหนดเวลาด้วย Intersection Observer API - บทช่วยสอนอื่นจาก MDN ที่พิจารณาว่าสามารถใช้ IO เพื่อติดตามการมองเห็นโฆษณาได้อย่างไร
  • บทความนี้โดย Denys Mishunov ครอบคลุมการใช้งานอื่นๆ สำหรับ IO รวมถึงเนื้อหาที่โหลดแบบ Lazy Loading แม้ว่าตอนนี้จะไม่จำเป็นแล้ว (ต้องขอบคุณแอตทริบิวต์การ loading ) แต่ก็ยังมีอะไรให้เรียนรู้อีกมากที่นี่