Acum mă vezi: cum să amân, să încarci leneș și să acționezi cu IntersectionObserver
Publicat: 2022-03-10Pe vremuri, a trăit un dezvoltator web care și-a convins cu succes clienții că site-urile nu ar trebui să arate la fel în toate browserele, s-a preocupat de accesibilitate și a fost unul dintre cei mai devreme care a adoptat grilele CSS. Dar în adâncul inimii sale, performanța a fost adevărata lui pasiune: a optimizat, a minimizat, a monitorizat și chiar a folosit trucuri psihologice în proiectele sale.
Apoi, într-o zi, a aflat despre încărcarea leneșă a imaginilor și a altor active care nu sunt imediat vizibile pentru utilizatori și nu sunt esențiale pentru redarea conținutului semnificativ pe ecran. Era începutul zorilor: dezvoltatorul a intrat în lumea diabolică a pluginurilor jQuery cu încărcare leneșă (sau poate în lumea nu atât de diabolică a atributelor async
și defer
). Unii spun chiar că a intrat direct în miezul tuturor relelor: lumea ascultătorilor de evenimente de scroll
. Nu vom ști niciodată sigur unde a ajuns, dar, din nou, acest dezvoltator este absolut fictiv și orice asemănare cu orice dezvoltator este pur și simplu coincidență.

Ei bine, acum puteți spune că cutia Pandorei a fost deschisă și că dezvoltatorul nostru fictiv nu face problema mai puțin reală. În zilele noastre, prioritizarea conținutului de deasupra paginii a devenit extrem de importantă pentru performanța proiectelor noastre web atât din punct de vedere al vitezei, cât și al greutății paginii.
În acest articol, vom ieși din întunericul scroll
și vom vorbi despre modul modern de încărcare leneșă a resurselor. Nu doar încărcarea leneșă a imaginilor, ci și încărcarea oricărui activ. Mai mult decât atât, tehnica despre care vom vorbi astăzi este capabilă de mult mai mult decât doar lazy-loading assets: vom putea oferi orice tip de funcționalitate amânată pe baza vizibilității elementelor pentru utilizatori.

Doamnelor și domnilor, să vorbim despre API-ul Intersection Observer. Dar înainte de a începe, să aruncăm o privire la peisajul instrumentelor moderne care ne-a condus la IntersectionObserver
.
2017 a fost un an foarte bun pentru instrumentele integrate în browserele noastre, ajutându-ne să îmbunătățim calitatea, precum și stilul bazei noastre de cod fără prea mult efort. În zilele noastre, web-ul pare să se îndepărteze de la soluții sporadice bazate pe soluții foarte diferite de soluții foarte tipice către o abordare mai bine definită a interfețelor Observer (sau doar „Observatori”): MutationObserver bine sprijinit a primit noi membri ai familiei care au fost rapid adoptate în browserele moderne:
- IntersectionObserver și
- PerformanceObserver (ca parte a specificației Performance Timeline Level 2).
Încă un potențial membru al familiei, FetchObserver, este o lucrare în desfășurare și ne ghidează mai mult în ținuturile unui proxy de rețea, dar astăzi aș dori să vorbesc mai multe despre front-end.

PerformanceObserver
și IntersectionObserver
urmăresc să ajute dezvoltatorii front-end să îmbunătățească performanța proiectelor lor în diferite puncte. Primul ne oferă instrumentul pentru Monitorizarea utilizatorului real, în timp ce cel de-al doilea este instrumentul, oferindu-ne o îmbunătățire tangibilă a performanței. După cum am menționat anterior, acest articol va arunca o privire detaliată exact asupra celui din urmă: IntersectionObserver . Pentru a înțelege în special mecanica IntersectionObserver
, ar trebui să ne uităm la modul în care ar trebui să funcționeze un Observer generic în web-ul modern.
Sfat profesionist : puteți sări peste teoria și să vă scufundați în mecanica IntersectionObserver imediat sau, chiar mai departe, direct la posibilele aplicații ale IntersectionObserver
.
Observator vs. Eveniment
Un „Observator”, după cum sugerează și numele, este destinat să observe ceva care se întâmplă în contextul unei pagini. Observatorii pot urmări ceva ce se întâmplă pe o pagină, cum ar fi modificările DOM. Ei pot urmări, de asemenea, evenimentele ciclului de viață ale paginii. Observatorii pot rula și unele funcții de apel invers. Acum, cititorul atent ar putea identifica imediat problema aici și ar putea întreba: „Deci, ce rost are? Nu avem deja evenimente în acest scop? Ce îi face pe Observatori diferiți?” Foarte bun punct! Să aruncăm o privire mai atentă și să rezolvăm.

Diferența crucială dintre Eveniment obișnuit și Observer este că, implicit, primul reacționează sincron pentru fiecare apariție a Evenimentului, afectând capacitatea de răspuns a firului principal, în timp ce cel din urmă ar trebui să reacționeze asincron fără a afecta atât de mult performanța. Cel puțin, acest lucru este valabil pentru observatorii prezentați în prezent: toți se comportă asincron și nu cred că acest lucru se va schimba în viitor.
Acest lucru duce la diferența principală în gestionarea apelurilor inverse ale observatorilor, care ar putea deruta începătorii: natura asincronă a observatorilor poate duce la trecerea mai multor observabile către o funcție de apel invers în același timp. Din acest motiv, funcția de apel invers ar trebui să se aștepte nu la o singură intrare, ci la un Array
de intrări (chiar dacă uneori Array va conține o singură intrare în ea).
Mai mult decât atât, unii observatori (în special cel despre care vorbim astăzi) oferă proprietăți pre-calculate foarte utile, pe care, altfel, obișnuiam să ne calculăm folosind metode și proprietăți scumpe (din punct de vedere al performanței) atunci când folosim evenimente obișnuite. Pentru a clarifica acest punct, vom ajunge la un exemplu puțin mai târziu în articol.
Deci, dacă este greu pentru cineva să se îndepărteze de paradigma evenimentului, aș spune că Observatorii sunt evenimente pe steroizi. O altă descriere ar fi: Observatorii reprezintă un nou nivel de aproximare pe deasupra evenimentelor. Dar indiferent de definiția pe care o preferați, ar trebui să fie de la sine înțeles că Observatorii nu sunt menționați să înlocuiască evenimente (cel puțin nu încă); există suficiente cazuri de utilizare pentru ambele și pot trăi fericiți unul lângă altul.

Structura generală a observatorului
Structura generică a unui Observator (oricare dintre cele disponibile la momentul scrierii) arată similar cu aceasta:
/** * 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);
Din nou, rețineți că entries
este o Array
de valori, nu o singură intrare.
Aceasta este structura generică: Implementările anumitor observatori diferă prin argumentele care sunt transmise în observe()
și argumentele trecute în apelul său invers. De exemplu, MutationObserver
ar trebui să obțină, de asemenea, un obiect de configurare pentru a ști mai multe despre ce modificări în DOM trebuie observate. PerformanceObserver
nu observă noduri în DOM, ci are setul dedicat de tipuri de intrare pe care le poate observa.
Aici, să încheiem partea „generică” a acestei discuții și să ne aprofundăm subiectul articolului de astăzi — IntersectionObserver
.
Deconstruirea IntersectionObserver

Mai întâi de toate, haideți să înțelegem ce este IntersectionObserver
.
Potrivit MDN:
Intersection Observer API oferă o modalitate de a observa în mod asincron schimbările în intersecția unui element țintă cu un element strămoș sau cu fereastra de vizualizare a unui document de nivel superior.
Mai simplu spus, IntersectionObserver
observă în mod asincron suprapunerea unui element cu un alt element. Să vorbim despre ce sunt acele elemente în IntersectionObserver
.
Inițializare IntersectionObserver
Într-unul din paragrafele anterioare, am văzut structura unui observator generic. IntersectionObserver
extinde puțin această structură. În primul rând, acest tip de Observator necesită o configurație cu trei elemente principale:
-
root
: Acesta este elementul rădăcină utilizat pentru observație. Acesta definește „cadru de captare” de bază pentru elementele observabile. În mod implicit,root
este portul de vizualizare al browserului dvs., dar poate fi într-adevăr orice element din DOM (apoi setațiroot
la ceva de genuldocument.getElementById('your-element')
). Rețineți totuși că elementele pe care doriți să le observați trebuie să „trăiască” în arborele DOM alroot
în acest caz.

root
definește baza pentru „cadru de captare” pentru elementele noastre.-
rootMargin
: definește marginea din jurul elementuluiroot
care extinde sau micșorează „cadru de captare” atunci când dimensiunileroot
nu oferă suficientă flexibilitate. Opțiunile pentru valorile acestei configurații sunt similare cu cele alemargin
în CSS, cum ar firootMargin: '50px 20px 10px 40px'
(sus, dreapta jos, stânga). Valorile pot fi scurtate (cum ar firootMargin: '50px'
) și pot fi exprimate fie înpx
, fie în%
. În mod implicit,rootMargin: '0px'
.

rootMargin
extinde/contractează „cadru de captare” care este definit de root
.-
threshold
: Nu se dorește întotdeauna să reacționeze instantaneu atunci când un element observat intersectează o margine a „cadrului de captare” (definit ca o combinație deroot
șirootMargin
).threshold
definește procentul de astfel de intersecție la care Observer ar trebui să reacționeze. Poate fi definit ca o singură valoare sau ca o matrice de valori. Pentru a înțelege mai bine efectulthreshold
(știu că uneori poate fi confuz), iată câteva exemple:-
threshold: 0
: Valoarea implicităIntersectionObserver
ar trebui să reacționeze atunci când primul sau ultimul pixel al unui element observat intersectează una dintre marginile „cadului de captare”. Rețineți căIntersectionObserver
este independent de direcție, ceea ce înseamnă că va reacționa în ambele scenarii: a) când elementul intră și b) când iese din „cadru de captare”. -
threshold: 0.5
: Observatorul ar trebui să fie tras când 50% dintr-un element observat intersectează „cadru de captare”; -
threshold: [0, 0.2, 0.5, 1]
: Observatorul ar trebui să reacționeze în 4 cazuri:- Primul pixel al unui element observat intră în „cadru de captare”: elementul nu se află încă în acel cadru, sau ultimul pixel al elementului observat iese din „cadru de captare”: elementul nu se mai află în cadrul;
- 20% din element se află în „cadru de captare” (din nou, direcția nu contează pentru
IntersectionObserver
); - 50% din element se află în „cadru de captare”;
- 100% din element se află în „cadru de capturare”. Acesta este strict opus
threshold: 0
.
-

threshold
definește cât de mult ar trebui să intersecteze elementul „cadru de captare” înainte ca Observatorul să fie demis. Pentru a ne informa IntersectionObserver
despre configurația dorită, pur și simplu trecem obiectul nostru de config
în constructorul nostru Observer împreună cu funcția noastră de apel invers, astfel:
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);
Acum, ar trebui să dăm IntersectionObserver
elementul real de observat. Acest lucru se face pur și simplu prin trecerea elementului la funcția observe()
:
… const img = document.getElementById('image-to-observe'); observer.observe(image);
Câteva lucruri de remarcat despre acest element observat:
- A fost menționat anterior, dar merită menționat din nou: în cazul în care setați
root
ca element în DOM, elementul observat ar trebui să fie localizat în arborele DOM alroot
. -
IntersectionObserver
poate accepta doar un element pentru observare la un moment dat și nu acceptă furnizarea de lot pentru observații. Aceasta înseamnă că, dacă trebuie să observați mai multe elemente (să spunem mai multe imagini pe o pagină), trebuie să repetați peste toate și să le observați separat:
… const images = document.querySelectorAll('img'); images.forEach(image => { observer.observe(image); });
- Când încărcați o pagină cu Observer la locul său, este posibil să observați că apelul invers al lui
IntersectionObserver
a fost declanșat pentru toate elementele observate simultan. Chiar și cele care nu se potrivesc cu configurația furnizată. „Ei bine... nu chiar ceea ce mă așteptam”, este gândul obișnuit când experimentez asta pentru prima dată. Dar nu vă confundați aici: acest lucru nu înseamnă neapărat că acele elemente observate intersectează cumva „cadru de captare” în timp ce pagina se încarcă.

IntersectionObserver
va fi declanșat pentru toate elementele observate odată ce acestea sunt înregistrate, dar nu înseamnă că toate se intersectează „cadrul nostru de captare”. Ceea ce înseamnă totuși, este că intrarea pentru acest element a fost inițializată și este acum controlată de IntersectionObserver
. Totuși, acest lucru ar putea adăuga zgomot inutil la funcția de apel invers și devine responsabilitatea dvs. să detectați ce elemente intersectează într-adevăr „cadru de captare” și pentru care încă nu trebuie să luăm în considerare. Pentru a înțelege cum să facem acea detectare, să aprofundăm puțin în anatomia funcției noastre de apel invers și să aruncăm o privire la în ce constau astfel de intrări.
IntersectionObserver Callback
În primul rând, funcția de apel invers pentru un IntersectionObserver
ia două argumente și vom vorbi despre acestea în ordine inversă începând cu al doilea argument. Împreună cu Array
de intrări observate menționată mai sus, care intersectează „cadrul nostru de captare”, funcția de apel invers primește Observatorul însuși ca al doilea argument.
Referire la observatorul însuși
new IntersectionObserver(function(entries, SELF) {…});
Obținerea referinței la Observatorul în sine este utilă în multe scenarii când doriți să opriți observarea unui element după ce acesta a fost detectat de IntersectionObserver
pentru prima dată. Scenariile precum încărcarea leneșă a imaginilor, preluarea amânată a altor active etc. sunt de acest fel. Când doriți să opriți observarea unui element, IntersectionObserver
oferă o metodă unobserve(element-to-stop-observing)
care poate fi rulată în funcția de apel invers după efectuarea unor acțiuni asupra elementului observat (cum ar fi încărcarea leneșă reală a unei imagini, de exemplu ).
Unele dintre aceste scenarii vor fi analizate în continuare în articol, dar cu acest al doilea argument în afara drumului nostru, să ajungem la actorii principali ai acestei piese de apel invers.
IntersectionObserverEntry
new IntersectionObserver(function(ENTRIES, self) {…});
entries
pe care le primim în funcția noastră de apel invers ca Array
sunt de tip special: IntersectionObserverEntry
. Această interfață ne oferă un set predefinit și precalculat de proprietăți referitoare la fiecare element observat. Să aruncăm o privire la cele mai interesante.
În primul rând, intrările de tip IntersectionObserverEntry
vin cu informații despre trei dreptunghiuri diferite - definind coordonatele și limitele elementelor implicate în proces:
-
rootBounds
: Un dreptunghi pentru „cadru de captare” (root
+rootMargin
); -
boundingClientRect
: Un dreptunghi pentru elementul observat în sine; -
intersectionRect
: O zonă a „cadru de captare” intersectată de elementul observat.

Lucrul cu adevărat cool despre aceste dreptunghiuri care sunt calculate pentru noi în mod asincron este că ne oferă informații importante legate de poziționarea elementului, fără să apelăm getBoundingClientRect()
, offsetTop
, offsetLeft
și alte proprietăți și metode scumpe de poziționare care declanșează thrashing layout. Câștig pur pentru performanță!

O altă proprietate a interfeței IntersectionObserverEntry
care este interesantă pentru noi este isIntersecting
. Aceasta este o proprietate de confort care indică dacă elementul observat intersectează în prezent „cadru de captare” sau nu. Am putea, desigur, să obținem aceste informații uitându-ne la intersectionRect
(dacă acest dreptunghi nu este 0×0, elementul intersectează „cadru de captare”), dar este destul de convenabil să avem acest lucru precalculat.
isIntersecting
poate fi folosit pentru a afla dacă elementul observat tocmai intră în „cadru de captare” sau îl părăsește deja. Pentru a afla acest lucru, salvați valoarea acestei proprietăți ca indicator global și când noua intrare pentru acest element ajunge la funcția dvs. de apel invers, comparați noul isIntersecting
cu acel indicator global:
- Dacă a fost
false
și acum estetrue
, atunci elementul intră în „cadru de captare”; - Dacă este opusul și este
false
acum, în timp ce eratrue
înainte, atunci elementul părăsește „cadru de captare”.
isIntersecting
este exact proprietatea care ne ajută să rezolvăm problema pe care am discutat mai devreme, adică intrări separate pentru elementele care intersectează cu adevărat „cadru de captare” de zgomotul celor fiind doar inițializarea intrării.
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);
NOTĂ : În Microsoft Edge 15, proprietatea isIntersecting
nu a fost implementată, revenind undefined
, în ciuda suportului complet pentru IntersectionObserver
. Acest lucru a fost remediat în iulie 2017 și este disponibil începând cu Edge 16.
Interfața IntersectionObserverEntry
oferă încă o proprietate de confort precalculată: intersectionRatio
. Acest parametru poate fi folosit în aceleași scopuri ca isIntersecting
, dar oferă un control mai granular, deoarece este un număr în virgulă mobilă în loc de o valoare booleană. Valoarea intersectionRatio
indică cât de mult din aria elementului observat intersectează „cadru de captare” (raportul dintre zona intersectionRect
și zona boundingClientRect
). Din nou, am putea face acest calcul noi înșine folosind informațiile din acele dreptunghiuri, dar este bine să-l facem pentru noi.

intersectionRatio
este similară cu proprietatea threshold
din configurația lui Observer. Diferența este că cel din urmă definește *când* să pornească Observer, primul indică situația reală a intersecției (care este ușor diferită de threshold
datorită naturii asincrone a Observer). target
este încă o proprietate a interfeței IntersectionObserverEntry
la care ar putea fi necesar să o accesați destul de des. Dar nu există absolut nicio magie aici – este doar elementul original care a fost transmis pentru funcția observe()
a observatorului tău. La fel ca event.target
cu care te-ai obișnuit atunci când lucrezi cu evenimente.
Pentru a obține lista completă de proprietăți pentru interfața IntersectionObserverEntry
, verificați specificația.
Aplicații posibile
Îmi dau seama că cel mai probabil ați ajuns la acest articol tocmai din cauza acestui capitol: cui îi pasă de mecanică când avem fragmente de cod pentru copiere și inserare până la urmă? Deci nu vă voi deranja cu mai multe discuții acum: intrăm în țara codului și a exemplelor. Sper că comentariile incluse în cod vor clarifica lucrurile.
Funcționalitate amânată
În primul rând, să revizuim un exemplu care dezvăluie principiile de bază care stau la baza ideii de IntersectionObserver
. Să presupunem că aveți un element care trebuie să facă o mulțime de calcule odată ce este pe ecran. De exemplu, anunțul dvs. ar trebui să înregistreze o vizualizare numai atunci când a fost afișat efectiv unui utilizator. Dar acum, să ne imaginăm că aveți un element carusel redat automat undeva sub primul ecran de pe pagina dvs.

Conducerea unui carusel, în general, este o sarcină grea. De obicei, implică temporizatoare JavaScript, calcule pentru derularea automată a elementelor etc. Toate aceste sarcini încarcă firul principal, iar când se face în modul de redare automată, ne este greu să știm când firul nostru principal primește această lovitură. Când vorbim despre prioritizarea conținutului de pe primul nostru ecran și vrem să atingem First Meaningful Paint și Time To Interactive cât mai curând posibil, firul principal blocat devine un blocaj pentru performanța noastră.
Pentru a remedia problema, este posibil să amânăm redarea unui astfel de carusel până când intră în fereastra de vizualizare a browserului. În acest caz, ne vom folosi cunoștințele și exemplul pentru parametrul isIntersecting
al interfeței 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);
Aici, jucăm carusel numai când intră în fereastra noastră. Observați absența obiectului de config
transmis inițializării lui IntersectionObserver
: aceasta înseamnă că ne bazăm pe opțiunile implicite de configurare. Când caruselul iese din fereastra noastră de vizualizare, ar trebui să încetăm să-l mai jucăm pentru a nu cheltui resurse pe elementele care nu mai sunt importante.
Încărcare leneșă a activelor
Acesta este, probabil, cel mai evident caz de utilizare pentru IntersectionObserver
: nu dorim să cheltuim resurse pentru a descărca ceva de care utilizatorul nu are nevoie acum. Acest lucru va oferi un beneficiu imens utilizatorilor dvs.: utilizatorii nu vor trebui să descarce, iar dispozitivele lor mobile nu vor trebui să analizeze și să compileze o mulțime de informații inutile de care nu au nevoie în acest moment. Deloc surprinzător, va ajuta și performanța aplicației dvs.

Anterior, pentru a amâna descărcarea și procesarea resurselor până în momentul în care utilizatorul le-ar putea afișa pe ecran, aveam de-a face cu ascultători de evenimente la evenimente precum scroll
. Problema este evidentă: acest lucru i-a declanșat pe ascultători mult prea des. Așa că a trebuit să venim cu ideea de a reduce sau anula execuția callback-ului. Dar toate acestea au adăugat multă presiune asupra firului nostru principal, blocându-l atunci când aveam cea mai mare nevoie.
Deci, revenind la IntersectionObserver
într-un scenariu de încărcare leneșă, la ce ar trebui să fim atenți? Să verificăm un exemplu simplu de imagini cu încărcare leneșă.
Vedeți încărcarea Pen Lazy în IntersectionObserver de Denys Mishunov (@mishunov) pe CodePen.
Încercați să defilați încet acea pagină la „al treilea ecran” și urmăriți fereastra de monitorizare din colțul din dreapta sus: vă va anunța câte imagini au fost descărcate până acum.
În nucleul de markup HTML pentru această sarcină se află o secvență simplă de imagini:
… <img data-src="https://blah-blah.com/foo.jpg"> …
După cum puteți vedea, imaginile ar trebui să vină fără etichete src
: odată ce un browser vede atributul src
, va începe să descarce imediat acea imagine, care este opusă intențiilor noastre. Prin urmare, nu ar trebui să punem acel atribut pe imaginile noastre în HTML și, în schimb, ne-am putea baza pe un atribut de data-
precum data-src
aici.
O altă parte a acestei soluții este, desigur, JavaScript. Să ne concentrăm aici pe principalele biți:
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); });
Din punct de vedere al structurii, nu este nimic nou aici: am acoperit toate acestea înainte:
- Primim toate mesajele cu atributele noastre
data-src
; - Set
config
: pentru acest scenariu doriți să extindeți „cadru de captare” pentru a detecta elemente puțin mai jos decât partea de jos a ferestrei de vizualizare; - Înregistrați
IntersectionObserver
cu acea configurație; - Repetați imaginile noastre și adăugați-le pe toate pentru a fi observate de acest
IntersectionObserver
;
Partea interesantă se întâmplă în cadrul funcției de apel invers invocate pe intrări. Sunt trei pași esențiali implicați.
În primul rând, procesăm doar elementele care se intersectează cu adevărat „cadru de captare”. Acest fragment ar trebui să vă fie familiar până acum.
entries.forEach(entry => { if (entry.isIntersecting) { … } });
Apoi, procesăm cumva intrarea prin conversia imaginii noastre cu
data-src
într-un<img src="…">
real.if (entry.isIntersecting) { preloadImage(entry.target); … }
preloadImage()
este o funcție foarte simplă care nu merită menționată aici. Citiți doar sursa.Următorul și ultimul pas: deoarece încărcarea leneșă este o acțiune unică și nu trebuie să descărcam imaginea de fiecare dată când elementul intră în „cadru de captare”, ar trebui să ne
unobserve
imaginea deja procesată. Același mod în care ar trebui să o facem cuelement.removeEventListener()
pentru evenimentele noastre obișnuite când acestea nu mai sunt necesare pentru a preveni scurgerile de memorie în codul nostru.if (entry.isIntersecting) { preloadImage(entry.target); // Observer has been passed as
self
to our callback self.unobserve(entry.target); }
Notă. În loc de unobserve(event.target)
, am putea apela și disconnect()
: deconectează complet IntersectionObserver
și nu ar mai observa imaginile. Acest lucru este util dacă singurul lucru la care îți pasă este prima lovitură pentru observatorul tău. În cazul nostru, avem nevoie ca Observer să continue să monitorizeze imaginile, așa că nu ar trebui să ne deconectăm încă.
Simțiți-vă liber să treceți la exemplu și să jucați cu diferite setări și opțiuni. Există un lucru interesant de menționat, totuși, atunci când doriți să încărcați leneș imaginile în special. Ar trebui să păstrați întotdeauna cutia, generată de elementul observat! Dacă verificați exemplul, veți observa că CSS-ul pentru imaginile de pe liniile 41–47 conține stiluri presupuse redundante, inclusiv. min-height: 100px
. Acest lucru se face pentru a da substituenților imaginii ( <img>
fără atributul src
) o dimensiune verticală. Pentru ce?
- Fără dimensiuni verticale, toate etichetele
<img>
ar genera caseta 0×0; - Deoarece eticheta
<img>
generează un fel de casetă deinline-block
în mod implicit, toate aceste casete 0×0 ar fi aliniate una lângă alta pe aceeași linie; - Aceasta înseamnă că
IntersectionObserver
va înregistra toate (sau, în funcție de cât de repede derulați, aproape toate) imaginile simultan - probabil că nu este exact ceea ce doriți să obțineți.
Evidențierea secțiunii curente
IntersectionObserver
este mult mai mult decât încărcare leneșă, desigur. Iată un alt exemplu de înlocuire a evenimentului de scroll
cu această tehnologie. În acesta avem un scenariu destul de comun: pe bara de navigare fixă ar trebui să evidențiem secțiunea curentă în funcție de poziția de defilare a documentului.
Consultați secțiunea curentă Evidențierea stiloului din IntersectionObserver de Denys Mishunov (@mishunov) pe CodePen.
Din punct de vedere structural, este similar cu exemplul pentru imaginile cu încărcare leneră și are aceeași structură de bază, cu următoarele excepții:
- Acum vrem să observăm nu imagini, ci secțiunile din pagină;
- În mod evident, avem și o funcție diferită pentru a procesa intrările din callback (
intersectionHandler(entry)
). Dar acesta nu este interesant: tot ce face este să comute între clasa CSS.
Totuși, ceea ce este interesant aici este obiectul de config
:
const config = { rootMargin: '-50px 0px -55% 0px' };
De ce nu valoarea implicită de rootMargin
0px
vă întrebați? Ei bine, pur și simplu pentru că evidențierea secțiunii curente și încărcarea leneșă a unei imagini sunt destul de diferite în ceea ce încercăm să realizăm. Cu încărcarea leneșă, dorim să începem încărcarea înainte ca imaginea să intre în vizualizare. Prin urmare, în acest scop, ne-am extins „cadru de captare” cu 50 px în partea de jos. Dimpotrivă, atunci când vrem să evidențiem secțiunea curentă, trebuie să ne asigurăm că secțiunea este de fapt vizibilă pe ecran. Și nu numai atât: trebuie să fim siguri că acel utilizator citește sau va citi exact această secțiune. Prin urmare, dorim ca o secțiune să parcurgă puțin mai mult de jumătate din fereastra de vizualizare din partea de jos înainte de a o putea declara secțiunea activă. De asemenea, dorim să luăm în considerare înălțimea barei de navigare și, prin urmare, eliminăm înălțimea barei din „cadru de capturare”.

De asemenea, rețineți că în cazul evidențierii elementului de navigare curent, nu dorim să încetăm să observăm nimic. Aici ar trebui să păstrăm întotdeauna la conducere IntersectionObserver
, prin urmare nu veți găsi nici disconnect()
nici unobserve()
aici.
rezumat
IntersectionObserver
este o tehnologie foarte simplă. Are un suport destul de bun în browserele moderne și dacă doriți să îl implementați pentru browsere care încă (sau nu îl vor suporta deloc), desigur, există un polyfill pentru asta. Dar, per total, aceasta este o tehnologie grozavă care ne permite să facem tot felul de lucruri legate de detectarea elementelor dintr-o fereastră de vizualizare, ajutând în același timp la obținerea unui spor de performanță foarte bun.
De ce este IntersectionObserver bun pentru tine?
-
IntersectionObserver
este un API asincron care nu blochează! -
IntersectionObserver
înlocuiește ascultătorii noștri scumpi la evenimentele descroll
sauresize
. -
IntersectionObserver
face toate calculele costisitoare precumgetClientBoundingRect()
pentru tine, astfel încât să nu fie nevoie. -
IntersectionObserver
urmează modelul structural al altor observatori, așa că, teoretic, ar trebui să fie ușor de înțeles dacă ești familiarizat cu modul în care funcționează alți observatori.
Lucruri de reținut
Dacă comparăm capacitățile lui IntersectionObserver cu lumea window.addEventListener('scroll')
de unde a venit totul, va fi greu să vedem orice dezavantaje în acest Observer. Așadar, să notăm câteva lucruri de care trebuie să ținem cont:
- Da,
IntersectionObserver
este un API asincron care nu blochează. Este grozav de știut! Dar este și mai important să înțelegeți că codul pe care îl rulați în apelurile dvs. inverse nu va fi rulat asincron în mod implicit, chiar dacă API-ul în sine este asincron. Deci, există încă șansa de a elimina toate beneficiile pe care le obțineți de laIntersectionObserver
dacă calculele funcției de apel invers fac ca firul principal să nu răspundă. Dar aceasta este o altă poveste. - Dacă utilizați
IntersectionObserver
pentru încărcarea leneră a activelor (cum ar fi imaginile, de exemplu), rulați.unobserve(asset)
după ce materialul a fost încărcat. IntersectionObserver
poate detecta intersecții doar pentru elementele care apar în structura de formatare a documentului. Pentru a fi clar: elementele observabile ar trebui să genereze o casetă și să afecteze cumva aspectul. Iată doar câteva exemple pentru a vă oferi o mai bună înțelegere:- Elemente cu
display: none
este exclus; -
opacity: 0
sauvisibility:hidden
creează caseta (deși invizibilă), astfel încât acestea să fie detectate; - Elemente poziționate absolut cu
width:0px; height:0px
width:0px; height:0px
sunt în regulă. 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.
- Elemente cu

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.