Now You See Me: วิธีชะลอ ขี้เกียจโหลด และลงมือทำด้วย IntersectionObserver
เผยแพร่แล้ว: 2022-03-10กาลครั้งหนึ่ง มีนักพัฒนาเว็บคนหนึ่งซึ่งประสบความสำเร็จในการโน้มน้าวลูกค้าของเขาว่าไซต์ต่างๆ ไม่ควรมีลักษณะเหมือนกันในทุกเบราว์เซอร์ ใส่ใจในการเข้าถึง และเป็นผู้เริ่มนำกริด CSS มาใช้ในช่วงแรกๆ แต่ลึกๆ ในใจของเขาก็คือการแสดงนั่นแหละคือสิ่งที่เขาหลงใหลอย่างแท้จริง: เขาปรับให้เหมาะสม ลดขนาด เฝ้าติดตาม และแม้กระทั่งใช้กลอุบายทางจิตวิทยาในโครงการของเขาอย่างต่อเนื่อง
จากนั้น วันหนึ่ง เขาได้เรียนรู้เกี่ยวกับภาพที่โหลดแบบ Lazy Loading และเนื้อหาอื่นๆ ที่ผู้ใช้มองไม่เห็นในทันที และไม่จำเป็นสำหรับการแสดงเนื้อหาที่มีความหมายบนหน้าจอ มันเป็นจุดเริ่มต้นของรุ่งอรุณ: ผู้พัฒนาเข้าสู่โลกแห่งความชั่วร้ายของปลั๊กอิน jQuery ที่โหลดแบบขี้เกียจ (หรืออาจเป็นโลกที่ไม่เลวร้ายของแอตทริบิวต์ async
และ defer
) บางคนถึงกับบอกว่าเขาเข้าถึงแก่นแท้ของความชั่วร้ายทั้งหมด: โลกของผู้ฟังเหตุการณ์ scroll
เราจะไม่มีทางรู้แน่ชัดว่าเขาลงเอยที่ใด แต่แล้วอีกครั้งผู้พัฒนารายนี้เป็นเรื่องสมมติอย่างยิ่ง และความคล้ายคลึงใด ๆ กับนักพัฒนาซอฟต์แวร์ใด ๆ ก็เป็นเพียงเรื่องบังเอิญ

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

ท่านสุภาพบุรุษและสุภาพสตรี มาพูดถึง Intersection Observer API กัน แต่ก่อนที่เราจะเริ่ม มาดูภูมิทัศน์ของเครื่องมือสมัยใหม่ที่นำเราไปสู่ IntersectionObserver
ปี 2017 เป็นปีที่ดีมากสำหรับเครื่องมือที่สร้างขึ้นในเบราว์เซอร์ของเรา ช่วยให้เราปรับปรุงคุณภาพรวมถึงรูปแบบของฐานโค้ดของเราโดยไม่ต้องใช้ความพยายามมากเกินไป ทุกวันนี้ ดูเหมือนว่าเว็บจะเลิกใช้วิธีแก้ปัญหาแบบประปรายโดยพิจารณาจาก ความแตกต่างอย่างมาก ในการแก้ปัญหา ทั่วไป จนถึงแนวทางที่กำหนดไว้อย่างชัดเจนของอินเทอร์เฟซผู้สังเกตการณ์ (หรือเพียงแค่ “ผู้สังเกตการณ์”): MutationObserver ที่ได้รับการสนับสนุนอย่างดีมีสมาชิกในครอบครัวใหม่ที่รวดเร็ว นำมาใช้ในเบราว์เซอร์ที่ทันสมัย:
- ทางแยกผู้สังเกตการณ์และ
- PerformanceObserver (เป็นส่วนหนึ่งของข้อกำหนด Performance Timeline ระดับ 2)
FetchObserver สมาชิกในครอบครัวที่มีศักยภาพอีกคนหนึ่งคืองานที่กำลังดำเนินการและแนะนำเรามากขึ้นในดินแดนของพร็อกซีเครือข่าย แต่วันนี้ฉันอยากจะพูดถึงส่วนหน้ามากขึ้นแทน

PerformanceObserver
และ IntersectionObserver
มีเป้าหมายเพื่อช่วยให้นักพัฒนาส่วนหน้าปรับปรุงประสิทธิภาพของโครงการในจุดต่างๆ แบบแรกให้เครื่องมือสำหรับการตรวจสอบผู้ใช้จริง ในขณะที่อันหลังเป็นเครื่องมือ ซึ่งช่วยให้เรามีการปรับปรุงประสิทธิภาพที่จับต้องได้ ดังที่ได้กล่าวไว้ก่อนหน้านี้ บทความนี้จะพิจารณาโดยละเอียดที่ส่วนหลัง: IntersectionObserver เพื่อให้เข้าใจกลไกของ IntersectionObserver
โดยเฉพาะ เราควรดูว่า Observer ทั่วไปควรทำงานอย่างไรในเว็บสมัยใหม่
คำแนะนำแบบมือโปร : คุณสามารถข้ามทฤษฎีและดำดิ่งสู่กลไกของ IntersectionObserver ได้ทันที หรือยิ่งไปกว่านั้น ตรงไปยังแอปพลิเคชันที่เป็นไปได้ของ IntersectionObserver
ผู้สังเกตการณ์ vs. เหตุการณ์
“ผู้สังเกตการณ์” ตามความหมายของชื่อ มีวัตถุประสงค์เพื่อสังเกตสิ่งที่เกิดขึ้นในบริบทของหน้า ผู้สังเกตการณ์สามารถดูสิ่งที่เกิดขึ้นบนหน้าเว็บได้ เช่น การเปลี่ยนแปลง DOM พวกเขายังสามารถดูกิจกรรมวงจรชีวิตของเพจได้อีกด้วย ผู้สังเกตการณ์ยังสามารถเรียกใช้ฟังก์ชันการโทรกลับบางอย่างได้ ตอนนี้ผู้อ่านที่เอาใจใส่อาจสังเกตเห็นปัญหาที่นี่ทันทีและถามว่า “แล้วประเด็นคืออะไร? เราไม่ได้มีกิจกรรมเพื่อการนี้อยู่แล้ว? อะไรทำให้ผู้สังเกตการณ์แตกต่างออกไป?” จุดที่ดีมาก! ลองมาดูอย่างใกล้ชิดและจัดเรียงออก

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

โครงสร้างผู้สังเกตการณ์ทั่วไป
โครงสร้างทั่วไปของผู้สังเกตการณ์ (ที่มีอยู่ในขณะที่เขียน) มีลักษณะดังนี้:
/** * Typical Observer's registration */ let observer = new YOUR-TYPE-OF-OBSERVER(function (entries) { // entries: Array of observed elements entries.forEach(entry => { // Here we can do something with each particular entry }); }); // Now we should tell our Observer what to observe observer.observe(WHAT-TO-OBSERVE);
ย้ำอีกครั้งว่า entries
เป็น Array
ของค่า ไม่ใช่รายการเดียว
นี่คือโครงสร้างทั่วไป: การใช้งานของผู้สังเกตการณ์เฉพาะแตกต่างกันในอาร์กิวเมนต์ที่ส่งผ่านไปยังข้อ observe()
และอาร์กิวเมนต์ที่ส่งผ่านไปยังการเรียกกลับ ตัวอย่างเช่น MutationObserver
ควรได้รับอ็อบเจ็กต์การกำหนดค่าเพื่อให้ทราบข้อมูลเพิ่มเติมเกี่ยวกับการเปลี่ยนแปลงใน DOM ที่ควรสังเกต PerformanceObserver
ไม่สังเกตโหนดใน DOM แต่มีชุดประเภทรายการเฉพาะที่สามารถสังเกตได้
ในที่นี้ เรามาจบส่วน "ทั่วไป" ของการสนทนานี้และเจาะลึกเข้าไปในหัวข้อของบทความวันนี้ — IntersectionObserver
แยกโครงสร้างผู้สังเกตการณ์

ก่อนอื่น มาทำความเข้าใจกันก่อนว่า IntersectionObserver
คืออะไร
ตาม MDN:
Intersection Observer API ให้วิธีการสังเกตการเปลี่ยนแปลงแบบอะซิงโครนัสในส่วนตัดขององค์ประกอบเป้าหมายที่มีองค์ประกอบระดับบนสุดหรือด้วยวิวพอร์ตของเอกสารระดับบนสุด
พูดง่ายๆ คือ IntersectionObserver
จะสังเกตการซ้อนทับกันขององค์ประกอบหนึ่งกับอีกองค์ประกอบหนึ่ง มาคุยกันว่าองค์ประกอบเหล่านั้นมีไว้เพื่ออะไรใน IntersectionObserver
IntersectionObserver การเริ่มต้น
ในย่อหน้าก่อนหน้านี้ เราได้เห็นโครงสร้างของ Observer ทั่วไปแล้ว IntersectionObserver
ขยายโครงสร้างนี้เล็กน้อย ประการแรก Observer ประเภทนี้ต้องการการกำหนดค่าที่มีสามองค์ประกอบหลัก:
-
root
: นี่คือองค์ประกอบรากที่ใช้สำหรับการสังเกต มันกำหนด "กรอบจับภาพ" พื้นฐานสำหรับองค์ประกอบที่สังเกตได้ โดยค่าเริ่มต้นroot
ทคือ วิวพอร์ต ของเบราว์เซอร์ของคุณ แต่สามารถเป็นองค์ประกอบใดก็ได้ใน DOM ของคุณ (จากนั้นคุณตั้งค่าroot
ทเป็นบางอย่าง เช่นdocument.getElementById('your-element')
) โปรดจำไว้ว่าองค์ประกอบที่คุณต้องการสังเกตต้อง "อยู่" ในทรี DOM ของroot
ในกรณีนี้

root
กำหนดฐานสำหรับ 'การจับภาพเฟรม' สำหรับองค์ประกอบของเรา-
rootMargin
: กำหนดระยะขอบรอบองค์ประกอบroot
ของคุณที่ ขยาย หรือ ย่อ "เฟรมการจับภาพ" เมื่อขนาดของroot
ทของคุณไม่ให้ความยืดหยุ่นเพียงพอ ตัวเลือกสำหรับค่าของการกำหนดค่านี้คล้ายกับค่าของmargin
ใน CSS เช่นrootMargin: '50px 20px 10px 40px'
(บน ล่างขวา ซ้าย) ค่าต่างๆ สามารถจดชวเลขได้ (เช่นrootMargin: '50px'
) และสามารถแสดงเป็นpx
หรือ%
โดยค่าเริ่มต้นrootMargin: '0px'

rootMargin
ขยาย / ทำสัญญา 'การจับภาพเฟรม' ซึ่งกำหนดโดย root
-
threshold
: ไม่ต้องการตอบสนองทันทีเสมอไปเมื่อองค์ประกอบที่สังเกตพบตัดกับเส้นขอบของ "เฟรมที่จับภาพ" (กำหนดเป็นการรวมกันของroot
และrootMargin
)threshold
กำหนดเปอร์เซ็นต์ของจุดตัดดังกล่าวที่ผู้สังเกตการณ์ควรตอบสนอง สามารถกำหนดเป็นค่าเดียวหรือเป็นอาร์เรย์ของค่าได้ เพื่อให้เข้าใจผลกระทบของthreshold
ดีขึ้น (ฉันรู้ว่าบางครั้งอาจทำให้สับสน) ต่อไปนี้คือตัวอย่างบางส่วน:-
threshold: 0
: ค่าเริ่มต้นIntersectionObserver
ควรตอบสนองเมื่อพิกเซลแรกหรือสุดท้ายขององค์ประกอบที่สังเกตตัดขอบด้านหนึ่งของ "เฟรมการจับภาพ" โปรดทราบว่าIntersectionObserver
ไม่เชื่อเรื่องทิศทาง ซึ่งหมายความว่าจะตอบสนองในทั้งสองสถานการณ์: a) เมื่อองค์ประกอบ เข้ามา และ b) เมื่อมัน ออก จาก "เฟรมการจับภาพ" -
threshold: 0.5
: ผู้สังเกตการณ์ควรถูกไล่ออกเมื่อ 50% ขององค์ประกอบที่สังเกตพบตัดกับ "เฟรมที่จับภาพ"; -
threshold: [0, 0.2, 0.5, 1]
: ผู้สังเกตควรตอบสนองใน 4 กรณี:- พิกเซลแรกสุดขององค์ประกอบที่สังเกตจะเข้าสู่ "เฟรมที่จับภาพ": องค์ประกอบนั้น ยังไม่ อยู่ภายในเฟรมนั้นจริงๆ หรือพิกเซลสุดท้ายขององค์ประกอบที่สังเกตจะออกจาก "เฟรมที่จับภาพ": องค์ประกอบนั้น ไม่ได้ อยู่ในเฟรมอีกต่อไป
- 20% ขององค์ประกอบอยู่ภายใน "เฟรมการจับภาพ" (อีกครั้ง ทิศทางไม่สำคัญสำหรับ
IntersectionObserver
); - 50% ขององค์ประกอบอยู่ใน "เฟรมจับภาพ";
- องค์ประกอบทั้งหมด 100% อยู่ใน "เฟรมที่จับภาพ" ซึ่งตรงข้ามกับ
threshold: 0
-

threshold
กำหนดโดยองค์ประกอบที่ควรตัด 'เฟรมการจับภาพ' ของเราก่อนที่ผู้สังเกตการณ์จะถูกไล่ออก เพื่อแจ้ง IntersectionObserver
ของเราถึงการกำหนดค่าที่ต้องการ เราเพียงแค่ส่งอ็อบเจ็กต์ config
ของเราไปที่ Constructor ของ Observer พร้อมกับฟังก์ชันเรียกกลับดังนี้:
const config = { root: null, // avoiding 'root' or setting it to 'null' sets it to default value: viewport rootMargin: '0px', threshold: 0.5 }; let observer = new IntersectionObserver(function(entries) { … }, config);
ตอนนี้ เราควรให้ IntersectionObserver
เป็นองค์ประกอบจริงที่จะสังเกต ทำได้โดยส่งองค์ประกอบไปที่ฟังก์ชั่น observe()
:
… const img = document.getElementById('image-to-observe'); observer.observe(image);
สองสามสิ่งที่ควรทราบเกี่ยวกับองค์ประกอบที่สังเกตได้นี้:
- มีการกล่าวถึงก่อนหน้านี้ แต่ควรกล่าวถึงอีกครั้ง: ในกรณีที่คุณตั้งค่า
root
เป็นองค์ประกอบใน DOM องค์ประกอบที่สังเกตควรอยู่ภายใน DOM tree ของroot
-
IntersectionObserver
สามารถรับได้เพียงองค์ประกอบเดียวสำหรับการสังเกตในแต่ละครั้ง และไม่สนับสนุนการจัดหาชุดงานสำหรับการสังเกต ซึ่งหมายความว่าหากคุณต้องการสังเกตองค์ประกอบหลายอย่าง (สมมติว่าหลายภาพในหน้าเดียว) คุณต้องวนซ้ำทุกองค์ประกอบและสังเกตแต่ละองค์ประกอบแยกกัน:
… const images = document.querySelectorAll('img'); images.forEach(image => { observer.observe(image); });
- เมื่อโหลดหน้าที่มี Observer เข้าที่ คุณอาจสังเกตเห็นว่าการเรียกกลับของ
IntersectionObserver
ถูกเรียกใช้สำหรับองค์ประกอบที่สังเกตทั้งหมดพร้อมกัน แม้แต่ที่ไม่ตรงกับการกำหนดค่าที่ให้มา “ก็… ไม่ใช่อย่างที่ฉันคาดไว้จริงๆ” เป็นความคิดปกติเมื่อประสบกับสิ่งนี้เป็นครั้งแรก แต่อย่าสับสนในที่นี้ ไม่ได้หมายความว่าองค์ประกอบที่สังเกตพบจะตัดกับ "เฟรมที่จับภาพ" ในขณะโหลดหน้าเว็บ

IntersectionObserver
จะถูกไล่ออกสำหรับองค์ประกอบที่สังเกตทั้งหมดเมื่อลงทะเบียนแล้ว แต่ไม่ได้หมายความว่าองค์ประกอบทั้งหมดจะตัดกัน 'เฟรมการจับภาพ' ของเรา ความหมายก็คือ รายการสำหรับองค์ประกอบนี้ได้รับการเริ่มต้นและขณะนี้ถูกควบคุมโดย IntersectionObserver
ของคุณ การทำเช่นนี้อาจเพิ่มสัญญาณรบกวนที่ไม่จำเป็นให้กับฟังก์ชันการโทรกลับของคุณ และเป็นความรับผิดชอบของคุณในการตรวจจับว่าองค์ประกอบใดที่ตัดกับ "เฟรมที่จับภาพ" และเรายังไม่จำเป็นต้องคำนึงถึง เพื่อให้เข้าใจวิธีการตรวจจับนั้น ให้เจาะลึกลงไปอีกเล็กน้อยเกี่ยวกับกายวิภาคของฟังก์ชันเรียกกลับของเรา และดูว่ารายการดังกล่าวประกอบด้วยอะไรบ้าง
ทางแยกผู้สังเกตการณ์โทรกลับ
ก่อนอื่น ฟังก์ชันเรียกกลับสำหรับ IntersectionObserver
รับ 2 อาร์กิวเมนต์ และเราจะพูดถึงสิ่งเหล่านี้ในลำดับที่กลับกันโดยเริ่มจากอาร์กิวเมนต์ ที่สอง นอกเหนือจาก Array
ของรายการที่สังเกตได้ข้างต้น ตัดกัน "เฟรมการจับภาพ" ของเราแล้ว ฟังก์ชันเรียกกลับทำให้ Observer เป็นอาร์กิวเมนต์ ที่สอง
อ้างอิงถึงผู้สังเกตการณ์เอง
new IntersectionObserver(function(entries, SELF) {…});
การอ้างอิงถึง Observer นั้นมีประโยชน์ในหลาย ๆ สถานการณ์เมื่อคุณต้องการหยุดการสังเกตองค์ประกอบบางอย่างหลังจากที่ IntersectionObserver
ตรวจพบองค์ประกอบในครั้งแรก สถานการณ์ต่างๆ เช่น การโหลดรูปภาพแบบ Lazy Loading การดึงเนื้อหาอื่นๆ ที่รอการตัดบัญชี เป็นต้น เป็นลักษณะนี้ เมื่อคุณต้องการหยุดการสังเกตองค์ประกอบใด ๆ IntersectionObserver
จัดเตรียมเมธอด unobserve(element-to-stop-observing)
ที่สามารถเรียกใช้ในฟังก์ชันเรียกกลับหลังจากดำเนินการบางอย่างกับองค์ประกอบที่สังเกตได้ (เช่นการโหลดรูปภาพที่ขี้เกียจจริงเช่น ).
บางส่วนของสถานการณ์เหล่านี้จะได้รับการตรวจสอบเพิ่มเติมในบทความ แต่ด้วยข้อโต้แย้งที่สองนี้ ให้มาที่ตัวแสดงหลักของการเล่นการโทรกลับนี้
ทางแยกผู้สังเกตการณ์ทางเข้า
new IntersectionObserver(function(ENTRIES, self) {…});
entries
เราได้รับในฟังก์ชันเรียกกลับของเราในฐานะ Array
นั้นเป็นประเภทพิเศษ: IntersectionObserverEntry
อินเทอร์เฟซนี้ให้ชุดคุณสมบัติที่กำหนดไว้ล่วงหน้าและคำนวณล่วงหน้าเกี่ยวกับองค์ประกอบที่สังเกตแต่ละรายการโดยเฉพาะ เรามาดูสิ่งที่น่าสนใจที่สุดกันดีกว่า
อย่างแรกเลย รายการของประเภท IntersectionObserverEntry
มาพร้อมกับข้อมูลเกี่ยวกับสี่เหลี่ยมที่แตกต่างกันสามรูป — กำหนดพิกัดและขอบเขตขององค์ประกอบที่เกี่ยวข้องในกระบวนการ:
-
rootBounds
: สี่เหลี่ยมผืนผ้าสำหรับ "เฟรมการจับภาพ" (root
+rootMargin
); -
boundingClientRect
: สี่เหลี่ยมผืนผ้าสำหรับองค์ประกอบที่สังเกตได้เอง -
intersectionRect
: พื้นที่ของ "เฟรมที่จับภาพ" ตัดกันโดยองค์ประกอบที่สังเกต

สิ่งที่ยอดเยี่ยมจริงๆ เกี่ยวกับสี่เหลี่ยมเหล่านี้ที่คำนวณสำหรับเราแบบอะซิงโครนัสก็คือมันให้ข้อมูลสำคัญเกี่ยวกับตำแหน่งขององค์ประกอบโดยที่เราไม่ต้องเรียก getBoundingClientRect()
, offsetTop
, offsetLeft
และคุณสมบัติและวิธีการกำหนดตำแหน่งราคาแพงอื่นๆ ชนะเพื่อประสิทธิภาพอย่างแท้จริง!

คุณสมบัติอื่นของอินเทอร์เฟซ IntersectionObserverEntry
ที่น่าสนใจสำหรับเราคือ isIntersecting
นี่เป็นคุณสมบัติอำนวยความสะดวกที่ระบุว่าองค์ประกอบที่สังเกตได้กำลังตัดกัน "เฟรมที่จับภาพ" หรือไม่ แน่นอนว่าเราสามารถรับข้อมูลนี้ได้โดยดูที่ intersectionRect
(หากสี่เหลี่ยมผืนผ้านี้ไม่ใช่ 0×0 องค์ประกอบกำลังตัดกัน "เฟรมที่จับภาพ") แต่การคำนวณล่วงหน้าสำหรับเรานั้นค่อนข้างสะดวก
isIntersecting
สามารถใช้เพื่อค้นหาว่าองค์ประกอบที่สังเกตได้นั้นเพิ่งเข้าสู่ "เฟรมที่จับภาพ" หรือออกจากองค์ประกอบนั้นไปแล้ว หากต้องการทราบสิ่งนี้ ให้บันทึกค่าของคุณสมบัตินี้เป็นแฟล็กส่วนกลาง และเมื่อรายการใหม่สำหรับองค์ประกอบนี้มาถึงฟังก์ชันเรียกกลับของคุณ ให้เปรียบเทียบ isIntersecting
ใหม่กับแฟล็กส่วนกลางนั้น:
- หากเป็น
false
และตอนนี้เป็นtrue
แสดงว่าองค์ประกอบนั้นเข้าสู่ "เฟรมการจับภาพ" - หากเป็นสิ่งที่ตรงกันข้ามและตอนนี้
false
ในขณะที่อดีตเคยเป็นtrue
องค์ประกอบนั้นก็จะออกจาก "กรอบการจับภาพ"
isIntersecting
เป็นคุณสมบัติที่ช่วยให้เราแก้ปัญหาที่เราได้พูดคุยกันก่อนหน้านี้ กล่าวคือ แยกรายการสำหรับองค์ประกอบที่ตัดกัน "เฟรมการจับภาพ" จริงๆ ออกจากสัญญาณรบกวนที่เป็นเพียงการเริ่มต้นของรายการ
let isLeaving = false; let observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { // we are ENTERING the "capturing frame". Set the flag. isLeaving = true; // Do something with entering entry } else if (isLeaving) { // we are EXITING the "capturing frame" isLeaving = false; // Do something with exiting entry } }); }, config);
หมายเหตุ : ใน Microsoft Edge 15 ไม่ได้ใช้งานคุณสมบัติ isIntersecting
ส่งคืน undefined
แม้จะรองรับ IntersectionObserver
อย่างเต็มรูปแบบก็ตาม สิ่งนี้ได้รับการแก้ไขในเดือนกรกฎาคม 2017 และมีให้บริการตั้งแต่ Edge 16
อินเทอร์เฟซ IntersectionObserverEntry
จัดเตรียมคุณสมบัติความสะดวกที่คำนวณไว้ล่วงหน้าอีกหนึ่งรายการ: intersectionRatio
พารามิเตอร์นี้สามารถใช้เพื่อวัตถุประสงค์เดียวกับ isIntersecting
แต่ให้การควบคุมที่ละเอียดยิ่งขึ้น เนื่องจากเป็นตัวเลขทศนิยมแทนที่จะเป็นค่าบูลีน ค่าของ intersectionRatio
ระบุว่าพื้นที่ขององค์ประกอบที่สังเกตพบนั้นตัดกับ "เฟรมที่จับภาพ" เท่าใด (อัตราส่วนของพื้นที่ intersectionRect
กับพื้นที่ boundingClientRect
) อีกครั้ง เราสามารถคำนวณได้เองโดยใช้ข้อมูลจากสี่เหลี่ยมเหล่านั้น แต่เป็นการดีที่จะทำเพื่อเรา

intersectionRatio
คล้ายกับคุณสมบัติ threshold
ของการกำหนดค่า Observer ข้อแตกต่างคือตัวหลังกำหนด *เมื่อ* ในการเปิด Observer ส่วนแรกระบุสถานการณ์ของทางแยกจริง (ซึ่งแตกต่างจาก threshold
เล็กน้อยเนื่องจากลักษณะไม่ตรงกันของ Observer) target
เป็นคุณสมบัติอีกอย่างหนึ่งของอินเทอร์เฟซ IntersectionObserverEntry
ที่คุณอาจต้องเข้าถึงบ่อยๆ แต่ไม่มีเวทย์มนตร์ที่นี่ – เป็นเพียงองค์ประกอบดั้งเดิมที่ส่งผ่านไปยังฟังก์ชั่น observe()
ของ Observer ของคุณ เช่นเดียวกับ event.target
ที่คุณเคยชินกับงานกิจกรรม
หากต้องการรับรายการคุณสมบัติทั้งหมดสำหรับอินเทอร์เฟซ IntersectionObserverEntry
ให้ตรวจสอบข้อกำหนด
การใช้งานที่เป็นไปได้
ฉันรู้ว่าคุณน่าจะมาที่บทความนี้เพราะบทนี้มากที่สุด: ใครสนใจเกี่ยวกับกลไกเมื่อเรามีตัวอย่างโค้ดสำหรับการคัดลอกและวาง ดังนั้นจะไม่รบกวนคุณเมื่อมีการอภิปรายเพิ่มเติม: เรากำลังเข้าสู่ดินแดนแห่งรหัสและตัวอย่าง ฉันหวังว่าความคิดเห็นที่รวมอยู่ในโค้ดจะทำให้สิ่งต่างๆ ชัดเจนขึ้น
ฟังก์ชั่นรอการตัดบัญชี
ก่อนอื่น มาทบทวนตัวอย่างที่เปิดเผยหลักการพื้นฐานที่อยู่ภายใต้แนวคิดของ IntersectionObserver
สมมติว่าคุณมีองค์ประกอบที่ต้องคำนวณหลายอย่างเมื่ออยู่บนหน้าจอ ตัวอย่างเช่น โฆษณาของคุณควรลงทะเบียนการดูก็ต่อเมื่อได้แสดงให้ผู้ใช้เห็นจริงเท่านั้น แต่ตอนนี้ สมมติว่าคุณมีองค์ประกอบภาพหมุนที่เล่นอัตโนมัติอยู่ใต้หน้าจอแรกบนหน้าเว็บของคุณ

โดยทั่วไปแล้วการเรียกใช้ม้าหมุนนั้นเป็นงานที่หนักหน่วง โดยปกติแล้ว มันจะเกี่ยวข้องกับตัวจับเวลา JavaScript การคำนวณเพื่อเลื่อนดูองค์ประกอบต่างๆ โดยอัตโนมัติ ฯลฯ งานทั้งหมดนี้จะโหลดเธรดหลัก และเมื่อเสร็จสิ้นในโหมดเล่นอัตโนมัติ เป็นเรื่องยากสำหรับเราที่จะทราบว่าเธรดหลักของเราได้รับผลกระทบนี้เมื่อใด เมื่อเรากำลังพูดถึงการจัดลำดับความสำคัญของเนื้อหาในหน้าจอแรกของเรา และต้องการกด First Meaningful Paint และ Time To Interactive โดยเร็วที่สุด เธรดหลักที่ถูกบล็อกจะกลายเป็นคอขวดสำหรับประสิทธิภาพของเรา
ในการแก้ไขปัญหา เราอาจเลื่อนการเล่นภาพหมุนดังกล่าวออกไปจนกว่าจะถึงวิวพอร์ตของเบราว์เซอร์ สำหรับกรณีนี้ เราจะใช้ความรู้และตัวอย่างของเราสำหรับพารามิเตอร์ isIntersecting
ของอินเทอร์เฟซ IntersectionObserverEntry
const carousel = document.getElementById('carousel'); let isLeaving = false; let observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { isLeaving = true; entry.target.startCarousel(); } else if (isLeaving) { isLeaving = false; entry.target.stopCarousel(); } }); } observer.observe(carousel);
ที่นี่ เราเล่นภาพหมุนก็ต่อเมื่อเข้าไปในวิวพอร์ตของเราเท่านั้น สังเกตว่าไม่มี config
object ที่ส่งผ่านไปยังการเริ่มต้นของ IntersectionObserver
ซึ่งหมายความว่าเราใช้ตัวเลือกการกำหนดค่าเริ่มต้น เมื่อภาพหมุนออกจากวิวพอร์ตของเรา เราควรหยุดเล่นเพื่อไม่ให้ใช้ทรัพยากรกับองค์ประกอบที่ไม่สำคัญอีกต่อไป
ขี้เกียจโหลดสินทรัพย์
นี่อาจเป็นกรณีการใช้งานที่ชัดเจนที่สุดสำหรับ IntersectionObserver
: เราไม่ต้องการใช้ทรัพยากรเพื่อดาวน์โหลดสิ่งที่ผู้ใช้ไม่ต้องการในตอนนี้ ซึ่งจะให้ประโยชน์มหาศาลแก่ผู้ใช้ของคุณ: ผู้ใช้จะไม่ต้องดาวน์โหลด และอุปกรณ์เคลื่อนที่ก็ไม่จำเป็นต้องแยกวิเคราะห์และรวบรวมข้อมูลที่ไม่มีประโยชน์มากมายที่ไม่ต้องการในขณะนี้ ไม่น่าแปลกใจเลยที่มันจะช่วยประสิทธิภาพของแอพของคุณ

ก่อนหน้านี้ เพื่อชะลอการดาวน์โหลดและประมวลผลทรัพยากรจนกว่าผู้ใช้อาจได้รับมันบนหน้าจอ เรากำลังติดต่อกับผู้ฟังเหตุการณ์ในเหตุการณ์เช่น scroll
ปัญหานี้ชัดเจน: สิ่งนี้กระตุ้นผู้ฟังบ่อยเกินไป ดังนั้นเราจึงต้องคิดที่จะควบคุมหรือหักล้างการดำเนินการของการโทรกลับ แต่ทั้งหมดนี้เพิ่มความกดดันให้กับเธรดหลักของเราในการบล็อกเมื่อเราต้องการมากที่สุด
ดังนั้น เมื่อกลับไปที่ IntersectionObserver
ในสถานการณ์โหลดแบบ Lazy Loading เราควรจับตาดูอะไรบ้าง? มาดูตัวอย่างง่ายๆ ของภาพที่โหลดแบบ Lazy Loading
ดูการโหลด Pen Lazy ใน IntersectionObserver โดย Denys Mishunov (@mishunov) บน CodePen
ลองเลื่อนหน้านั้นไปที่ "หน้าจอที่สาม" อย่างช้าๆ และดูหน้าต่างการตรวจสอบที่มุมบนขวา: มันจะแจ้งให้คุณทราบจำนวนภาพที่ถูกดาวน์โหลดจนถึงตอนนี้
ในแกนหลักของมาร์กอัป HTML สำหรับงานนี้ มีการจัดลำดับรูปภาพอย่างง่าย:
… <img data-src="https://blah-blah.com/foo.jpg"> …
อย่างที่คุณเห็น รูปภาพควรมาโดยไม่มีแท็ก src
: เมื่อเบราว์เซอร์เห็นแอตทริบิวต์ src
มันจะเริ่มดาวน์โหลดรูปภาพนั้นทันทีซึ่งตรงกันข้ามกับความตั้งใจของเรา ดังนั้น เราไม่ควรใส่แอตทริบิวต์นั้นบนรูปภาพของเราใน HTML และแทน เราอาจใช้แอตทริบิวต์ data-
เช่น data-src
ที่นี่
อีกส่วนหนึ่งของโซลูชันนี้คือ JavaScript มาเน้นที่บิตหลักที่นี่:
const images = document.querySelectorAll('[data-src]'); const config = { … }; let observer = new IntersectionObserver(function (entries, self) { entries.forEach(entry => { if (entry.isIntersecting) { … } }); }, config); images.forEach(image => { observer.observe(image); });
โครงสร้างที่ชาญฉลาด ไม่มีอะไรใหม่ที่นี่: เราได้กล่าวถึงทั้งหมดนี้มาก่อน:
- เราได้รับข้อความทั้งหมดที่มีแอตทริบิวต์
data-src
ของเรา - ตั้งค่า
config
: สำหรับสถานการณ์นี้คุณต้องการขยาย "การจับภาพเฟรม" เพื่อตรวจจับองค์ประกอบที่ต่ำกว่าด้านล่างของวิวพอร์ตเล็กน้อย - ลงทะเบียน
IntersectionObserver
ด้วยการกำหนดค่านั้น - ทำซ้ำภาพของเราและเพิ่มทั้งหมดเพื่อให้เห็นโดย
IntersectionObserver
นี้
ส่วนที่น่าสนใจเกิดขึ้นภายในฟังก์ชันเรียกกลับที่เรียกใช้ในรายการ มีสามขั้นตอนสำคัญที่เกี่ยวข้อง
ก่อนอื่น เราประมวลผลเฉพาะรายการที่ตัดกับ "กรอบการจับภาพ" ของเราเท่านั้น ข้อมูลโค้ดนี้น่าจะคุ้นเคยกับคุณอยู่แล้ว
entries.forEach(entry => { if (entry.isIntersecting) { … } });
จากนั้น เราประมวลผลรายการโดยแปลงภาพของเราด้วย
data-src
เป็น<img src="…">
ของจริงif (entry.isIntersecting) { preloadImage(entry.target); … }
preloadImage()
เป็นฟังก์ชันที่ง่ายมากที่ไม่ควรกล่าวถึงในที่นี้ แค่อ่านที่มาขั้นตอนถัดไปและขั้นตอนสุดท้าย: เนื่องจากการโหลดแบบ Lazy Loading เป็นการดำเนินการเพียงครั้งเดียว และเราไม่จำเป็นต้องดาวน์โหลดภาพทุกครั้งที่องค์ประกอบเข้าสู่ "เฟรมที่จับภาพ" ของเรา เราจึงไม่ควร
unobserve
ภาพที่ประมวลผลแล้ว เช่นเดียวกับที่เราควรทำด้วยelement.removeEventListener()
สำหรับเหตุการณ์ปกติของเราเมื่อไม่ต้องการสิ่งเหล่านั้นอีกต่อไป เพื่อป้องกันหน่วยความจำรั่วไหลในโค้ดของเราif (entry.isIntersecting) { preloadImage(entry.target); // Observer has been passed as
self
to our callback self.unobserve(entry.target); }
บันทึก. แทนที่จะ unobserve(event.target)
เรายังสามารถเรียก disconnect()
ได้: มันยกเลิกการเชื่อมต่อ IntersectionObserver
ของเราโดยสมบูรณ์ และจะไม่สังเกตภาพอีกต่อไป สิ่งนี้มีประโยชน์หากสิ่งเดียวที่คุณสนใจคือการโจมตีครั้งแรกสำหรับผู้สังเกตการณ์ของคุณ ในกรณีของเรา เราต้องการให้ Observer คอยตรวจสอบรูปภาพ ดังนั้นเราไม่ควรตัดการเชื่อมต่อในตอนนี้
อย่าลังเลที่จะแยกตัวอย่างและเล่นกับการตั้งค่าและตัวเลือกต่างๆ มี สิ่งที่น่าสนใจอย่างหนึ่งที่จะพูดถึง ว่าเมื่อคุณต้องการขี้เกียจโหลดภาพโดยเฉพาะ คุณควรเก็บกล่องที่สร้างขึ้นโดยองค์ประกอบที่สังเกตไว้ในใจเสมอ! หากคุณตรวจสอบตัวอย่าง คุณจะสังเกตเห็นว่า CSS สำหรับรูปภาพในบรรทัดที่ 41–47 มีลักษณะที่ซ้ำซากตามที่คาดคะเน รวมถึง min-height: 100px
สิ่งนี้ทำเพื่อให้ตัวยึดตำแหน่งรูปภาพ ( <img>
ไม่มีแอตทริบิวต์ src
) มีมิติแนวตั้งบางส่วน เพื่ออะไร?
- หากไม่มีขนาดแนวตั้ง แท็ก
<img>
ทั้งหมดจะสร้างช่อง 0×0; - เนื่องจากแท็ก
<img>
สร้างกล่องinline-block
บางประเภทตามค่าเริ่มต้น กล่อง 0×0 ทั้งหมดจะถูกจัดวางเรียงต่อกันในบรรทัดเดียวกัน - ซึ่งหมายความว่า
IntersectionObserver
ของคุณจะลงทะเบียนรูปภาพทั้งหมด (หรือขึ้นอยู่กับความเร็วที่คุณเลื่อน เกือบ ทั้งหมด) ในครั้งเดียว — อาจไม่ใช่สิ่งที่คุณต้องการบรรลุ
ไฮไลท์ของส่วนปัจจุบัน
IntersectionObserver
เป็นมากกว่าแค่การโหลดแบบ Lazy Loading แน่นอน นี่เป็นอีกตัวอย่างหนึ่งของการแทนที่เหตุการณ์การ scroll
ด้วยเทคโนโลยีนี้ ในสถานการณ์นี้ เรามีสถานการณ์ทั่วไป: บนแถบการนำทางคงที่ เราควรเน้นส่วนปัจจุบันตามตำแหน่งการเลื่อนของเอกสาร
ดูส่วนปัจจุบันของการเน้นปากกาใน IntersectionObserver โดย Denys Mishunov (@mishunov) บน CodePen
โครงสร้างจะคล้ายกับตัวอย่างสำหรับภาพที่โหลดแบบ Lazy Loading และมีโครงสร้างพื้นฐานเหมือนกันโดยมีข้อยกเว้นดังต่อไปนี้:
- ตอนนี้เราต้องการสังเกตไม่ใช่ภาพ แต่ส่วนต่างๆ ในหน้า;
- เห็นได้ชัดว่าเรายังมีฟังก์ชันที่แตกต่างกันในการประมวลผลรายการในการเรียกกลับของเรา (
intersectionHandler(entry)
) แต่อันนี้ไม่น่าสนใจ แค่สลับคลาส CSS
สิ่งที่น่าสนใจที่นี่คือวัตถุ config
แม้ว่า:
const config = { rootMargin: '-50px 0px -55% 0px' };
ทำไมไม่เป็นค่าเริ่มต้นของ 0px
สำหรับ rootMargin
คุณถาม? เพียงเพราะการเน้นส่วนปัจจุบันและการโหลดรูปภาพแบบ Lazy Loading นั้นค่อนข้างแตกต่างในสิ่งที่เราพยายามทำให้สำเร็จ ด้วยการโหลดแบบ Lazy Loading เราต้องการเริ่มโหลดก่อนที่ภาพจะเข้าสู่มุมมอง ด้วยเหตุนี้ เราจึงขยาย "กรอบการจับภาพ" ขึ้น 50px ที่ด้านล่าง ในทางกลับกัน เมื่อเราต้องการเน้นส่วนปัจจุบัน เราต้องแน่ใจว่าส่วนนั้นมองเห็นได้บนหน้าจอจริง และไม่เพียงเท่านั้น: เราต้องแน่ใจว่าผู้ใช้กำลังอ่านหรือกำลังจะอ่านส่วนนี้อย่างแน่นอน ดังนั้นเราจึงต้องการให้ส่วนใดส่วนหนึ่งเพิ่มมากกว่าครึ่งหนึ่งของวิวพอร์ตจากด้านล่างเล็กน้อย ก่อนที่เราจะประกาศเป็นส่วนที่ใช้งานอยู่ได้ นอกจากนี้ เราต้องการพิจารณาความสูงของแถบนำทางด้วย ดังนั้นเราจึงลบความสูงของแถบนั้นออกจาก "กรอบการจับภาพ"

นอกจากนี้ โปรดทราบ ว่าในกรณีที่เน้นรายการการนำทางปัจจุบัน เราไม่ต้องการหยุดสังเกตสิ่งใด ที่นี่เราควรดูแล IntersectionObserver
อยู่เสมอ ดังนั้นคุณจะไม่พบว่าไม่มี disconnect()
หรือการ unobserve()
ที่นี่
สรุป
IntersectionObserver
เป็นเทคโนโลยีที่ตรงไปตรงมามาก มีการรองรับที่ค่อนข้างดีในเบราว์เซอร์สมัยใหม่ และหากคุณต้องการนำไปใช้กับเบราว์เซอร์ที่ยังคง (หรือไม่สนับสนุนเลย) แน่นอนว่ามีโพลีฟิลสำหรับสิ่งนั้น แต่ทั้งหมดนี้เป็นเทคโนโลยีที่ยอดเยี่ยมที่ช่วยให้เราทำสิ่งต่างๆ ที่เกี่ยวข้องกับการตรวจจับองค์ประกอบในวิวพอร์ตได้ ในขณะเดียวกันก็ช่วยให้ได้ประสิทธิภาพที่เพิ่มขึ้นอย่างแท้จริง
ทำไม IntersectionObserver ถึงดีสำหรับคุณ?
-
IntersectionObserver
เป็น API ที่ไม่บล็อก async! -
IntersectionObserver
แทนที่ Listener ราคาแพงของเราในการscroll
หรือresize
เหตุการณ์ -
IntersectionObserver
ทำการคำนวณที่มีราคาแพงทั้งหมดเช่นgetClientBoundingRect()
ให้กับคุณเพื่อที่คุณจะได้ไม่ต้องทำ -
IntersectionObserver
เป็นไปตามรูปแบบโครงสร้างของ Observers อื่นๆ ดังนั้น ตามทฤษฎีแล้ว ควรเข้าใจได้ง่าย หากคุณคุ้นเคยกับวิธีการทำงานของ Observers อื่นๆ
สิ่งที่ควรทราบ
หากเราเปรียบเทียบความสามารถของ IntersectionObserver กับโลกของ window.addEventListener('scroll')
จากที่ที่มันทั้งหมดมา มันจะยากที่จะเห็นข้อเสียใดๆ ใน Observer นี้ ดังนั้น ให้สังเกตบางสิ่งที่คุณควรจำไว้แทน:
- ใช่
IntersectionObserver
เป็น API ที่ไม่บล็อก async นี่เป็นเรื่องดีที่รู้! แต่สิ่งสำคัญยิ่งกว่านั้นคือต้องเข้าใจว่าโค้ดที่คุณกำลังเรียกใช้ในการเรียกกลับของคุณจะไม่ถูกเรียกใช้แบบอะซิงโครนัสโดยค่าเริ่มต้นแม้ว่า API นั้นจะเป็นแบบอะซิงโครนัสก็ตาม ดังนั้นจึงยังมีโอกาสที่จะขจัดประโยชน์ทั้งหมดที่คุณได้รับจากIntersectionObserver
หากการคำนวณของฟังก์ชันเรียกกลับของคุณทำให้เธรดหลักไม่ตอบสนอง แต่นี่เป็นเรื่องราวที่แตกต่าง - หากคุณใช้
IntersectionObserver
สำหรับการโหลดเนื้อหาแบบ Lazy Loading (เช่น รูปภาพ เป็นต้น) ให้เรียกใช้.unobserve(asset)
หลังจากที่โหลดเนื้อหาแล้ว IntersectionObserver
สามารถตรวจจับทางแยกสำหรับองค์ประกอบที่ปรากฏในโครงสร้างการจัดรูปแบบของเอกสารเท่านั้น เพื่อให้ชัดเจน: องค์ประกอบที่สังเกตได้ควรสร้างกล่องและส่งผลต่อการจัดวาง นี่เป็นเพียงตัวอย่างบางส่วนที่จะช่วยให้คุณเข้าใจมากขึ้น:- องค์ประกอบที่มี
display: none
สิ่งใดที่เป็นปัญหา -
opacity: 0
หรือvisibility:hidden
สร้างกล่อง (แม้ว่าจะมองไม่เห็น) ดังนั้นสิ่งเหล่านี้จะถูกตรวจพบ - องค์ประกอบที่วางตำแหน่งไว้อย่างแน่นอนด้วย
width:0px; height:0px
width:0px; height:0px
ก็ใช้ได้ Though, it has to be noted that absolutely positioned elements fully positioned outside of parent's borders (with negative margins or negativetop
,left
, etc.) and are cut out by parent'soverflow: hidden
won't be detected: their box is out of scope for the formatting structure.
- องค์ประกอบที่มี

I know it was a long article, but if you're still around, here are some links for you to get an even better understanding and different perspectives on the Intersection Observer API:
- Intersection Observer API on MDN;
- IntersectionObserver polyfill;
- IntersectionObserver polyfill as
npm
module; - Lazy-Loading Images with IntersectionObserver [video] by amazing Paul Lewis;
- Basic and short (just 01:39), but very informative introduction to IntersectionObserver [video] by Surma.
With this, I would like to make a pause in our discussion to give you an opportunity to play with this technology and realize all of its convenience. So, go play with it. The article is finally over. This time I really mean it.