IntersectionObserver – čo to je a načo to je
Keď chceme na stránke spraviť efekt, že sa prvok ukáže až vtedy, keď k nemu používateľ doscrolluje, často sa spomína <strong>IntersectionObserver</strong>.
Je to JavaScript nástroj, ktorý sleduje, či sa nejaký prvok dostal do viditeľnej časti obrazovky. Čiže úplne jednoducho: povieme prehliadaču, aby nám strážil nejaký prvok a oznámil nám, keď sa objaví vo viewporte.
A to je jeho veľká výhoda. Nemusíme ručne stále sledovať scroll a prepočítavať, kde sa prvok nachádza. Prehliadač to sleduje za nás a JavaScript sa ozve až vtedy, keď nastane zmena, ktorá nás zaujíma.
Načo sa IntersectionObserver používa
Najčastejšie sa používa na animácie pri scrollovaní. Napríklad: sekcia sa jemne zobrazí, karta vyskočí, obrázok sa odhalí alebo sa začne nejaký efekt až vtedy, keď sa prvok dostane do obrazovky.
Ale tým to nekončí. IntersectionObserver sa používa aj na lazy loading, načítanie ďalšieho obsahu, spustenie počítadla, zvýraznenie aktívnej sekcie alebo rôzne efekty, ktoré nechceme spúšťať hneď pri načítaní stránky.
Prečo nie obyčajný scroll event
Bez IntersectionObservera sa to často robilo cez scroll event. Teda: používateľ scrolluje, JavaScript sa stále spúšťa a my stále dokola meriame, kde sa prvok nachádza. To fungovať môže, ale býva to neprehľadné a zbytočne ťažkopádne.
IntersectionObserver funguje krajšie. My len povieme: tento prvok sleduj. A keď sa dostane do viditeľnej oblasti, sprav toto.
Základná myšlienka
Celé to stojí na tom, že máme:
1. prvok, ktorý chceme sledovať,
2. pravidlá, podľa ktorých sa sleduje,
3. akciu, ktorá sa má vykonať.
Veľmi často je tá akcia len pridanie triedy .show.
Prečo je dobré pridávať triedu .show
Toto je veľmi dobrý zvyk. JavaScript by nemal riešiť vzhľad viac, než musí. Lepšie je, keď JavaScript len povie: „tento prvok je už viditeľný“ a CSS sa postará o to, ako má vyzerať.
Čiže prvok môže mať na začiatku nižšiu priehľadnosť, posun dole alebo mierne rozmazanie. A keď dostane triedu .show, CSS ho pekne zobrazí.
Výhoda je jednoduchá: logika ostáva v JavaScripte a vzhľad v CSS. Keď neskôr zmeníš animáciu, nemusíš hrabať do JavaScriptu. Stačí upraviť CSS.
Najjednoduchší príklad
Najprv si pripravíme HTML:
<body>
<h1>IntersectionObserver – čo to je a načo to je</h1>
<div class="ukazka">
<section class="box hidden">Prvý blok</section>
<section class="box hidden">Druhý blok</section>
<section class="box hidden">Tretí blok</section>
</div>
</body>
Tu chceme dosiahnuť jednoduchú vec: boxy budú na začiatku schované a keď vojdú do viditeľnej časti obrazovky, dostanú triedu show.
const boxes = document.querySelectorAll(".hidden");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("show");
}
});
});
boxes.forEach((box) => observer.observe(box));
Tu sa deje toto: najprv si vyberieme všetky prvky s triedou .hidden. Potom vytvoríme observer. A nakoniec povieme, že každý z týchto prvkov sa má sledovať.
Keď sa niektorý prvok dostane do sledovanej oblasti, callback funkcia dostane záznam a my zistíme, že je viditeľný cez entry.isIntersecting. Potom mu pridáme triedu show.
Čo je callback a čo je entries
Callback je funkcia, ktorú observer zavolá vtedy, keď sa niečo zmení.
Parameter entries je pole záznamov. Každý záznam predstavuje jeden sledovaný prvok, pri ktorom nastala zmena. Preto tam často býva forEach, aby sme prešli všetky prvky, ktoré treba spracovať.
Najdôležitejšie veci v entry bývajú:
entry.target – konkrétny prvok,entry.isIntersecting – či je vo viditeľnej oblasti,entry.intersectionRatio – aká časť prvku je viditeľná.
Čo je root
root určuje, voči čomu sa má viditeľnosť sledovať.
Keď dáme:
root: null
znamená to: sleduj to voči celej obrazovke, teda voči viewportu. To je najčastejšie riešenie.
Ak ale máme nejaký vlastný scrollovací box, môžeme ako root nastaviť konkrétny prvok. Vtedy sa nebude sledovať celé okno, ale len vnútro toho boxu.
const wrapper = document.querySelector(".scroll-box");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("show");
}
});
}, {
root: wrapper
});
Čiže: ak chceme sledovať prvky vo vnútri nejakého scrollovateľného kontajnera, musíme to povedať cez root.
Čo je rootMargin
rootMargin je vec, ktorá veľa ľudí mätie, ale pritom je veľmi užitočná.
Predstav si, že viewport má nejakú hranicu. Observer normálne reaguje podľa tejto hranice. Lenže cez rootMargin ju vieme umelo posunúť.
Je to podobné, ako keby sme sledovaciu oblasť trochu zväčšili alebo zmenšili.
{
root: null,
rootMargin: "0px 0px -100px 0px"
}
Toto znamená: spodnú hranicu si posuň o 100 pixelov vyššie.
Prakticky: prvok sa síce už možno objavil dole na obrazovke, ale observer ešte chvíľu počká. Zareaguje až keď vojde hlbšie do view.
Toto je presne to, keď nechceš animáciu hneď, ako sa prvok len dotkne spodku obrazovky.
Úplne po lopate:
kladný margin vie reakciu posunúť skôr,
záporný margin vie reakciu posunúť neskôr.
Čo je threshold
threshold hovorí, koľko z prvku musí byť vidieť, aby observer zareagoval.
{
threshold: 0
}
To znamená približne: stačí, že sa prvok len dotkne sledovanej oblasti.
{
threshold: 0.5
}
To znamená: reaguj až keď je viditeľná asi polovica prvku.
{
threshold: 1
}
To znamená: reaguj až keď je viditeľný celý prvok.
A tu býva časté prekvapenie. Človek dá threshold: 1 a aj tak má pocit, že animácia ide skoro. Lenže záleží aj od veľkosti prvku, veľkosti obrazovky a od rootMargin. Ak je prvok malý, môže sa celý zmestiť do obrazovky skôr, než čakáš.
Preto sa v praxi často kombinuje threshold a rootMargin.
Príjemné praktické nastavenie
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("show");
}
});
}, {
root: null,
rootMargin: "0px 0px -80px 0px",
threshold: 0.15
});
Toto býva celkom príjemné nastavenie na bežné sekcie stránky. Nereaguje to úplne hneď, ale ani nie príliš neskoro.
Jednorazové zobrazenie prvku
Často nechceme, aby sa animácia púšťala stále dokola. Chceme len: prvok sa ukáže raz a hotovo.
Vtedy je dobré po zobrazení prestať prvok sledovať.
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("show");
obs.unobserve(entry.target);
}
});
});
To znamená: keď sa prvok ukáže, dostane triedu a observer sa od neho odpojí.
Ak chceme prvok zase schovávať
Niekedy nechceme jednorazový efekt, ale správanie: vojde do view = ukáž, odíde z view = schovaj.
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("show");
} else {
entry.target.classList.remove("show");
}
});
});
Toto sa hodí na opakované efekty, ale pri bežných sekciách to môže pôsobiť rušivo. Preto sa pri obsahových blokoch často používa skôr jednorazové zobrazenie.
Jedna trieda na sledovanie, druhá na výsledok
Veľmi dobrý zvyk je rozdeliť si triedy.
Napríklad:
.js-reveal = prvok, ktorý sledujeme,.show = stav, ktorý nastane po zobrazení.
To je lepšie, lebo hneď vidíš rozdiel medzi tým: tento prvok je určený na sledovanie a tento prvok je už zobrazený.
Ukážka s lepším pomenovaním tried
<div class="ukazka">
<div class="card js-reveal">Obsah 1</div>
<div class="card js-reveal">Obsah 2</div>
<div class="card js-reveal">Obsah 3</div>
</div>
const items = document.querySelectorAll(".js-reveal");
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add("show");
obs.unobserve(entry.target);
});
}, {
root: null,
rootMargin: "0px 0px -60px 0px",
threshold: 0.2
});
items.forEach((item) => observer.observe(item));
Všimni si, že JavaScript tu zase len pridáva triedu. Vzhľad si rieši CSS.
CSS k tomu
.card {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.7s ease, transform 0.7s ease;
}
.card.show {
opacity: 1;
transform: translateY(0);
}
A toto je presne tá pekná spolupráca. HTML drží obsah, JavaScript rieši okamih a CSS rieši vzhľad.
Častá chyba – robiť všetko v JavaScripte
Začiatočník často začne meniť štýly priamo cez JavaScript. Napríklad opacity, transform, transition a podobne. Fungovať to môže, ale rýchlo sa v tom začne robiť neporiadok.
Keď neskôr zmeníš dizajn, musíš hrabať aj do JavaScriptu. Pri triede .show len upravíš CSS a logika ostáva rovnaká.
threshold môže byť aj pole
threshold nemusí byť len jedno číslo. Môže to byť aj pole.
{
threshold: [0, 0.25, 0.5, 0.75, 1]
}
Vtedy callback reaguje pri viacerých úrovniach viditeľnosti. To sa hodí pri zložitejších efektoch, keď chceš sledovať, ako veľmi je prvok vo view.
Kedy root nie je null
root nastavujeme na konkrétny prvok vtedy, keď nesledujeme celé okno, ale len nejaký scrollovateľný kontajner. Napríklad slider, bočný panel, chat okno alebo box s vlastným scrollom.
Prečo sa animácia spustí „divne skoro“
Toto býva veľmi častá otázka. Dôvod býva väčšinou jeden z týchto:
prvok je menší, než si myslíš,
threshold je príliš nízky,
rootMargin posúva hranicu sledovania,
alebo sleduješ viewport, hoci by si mal sledovať konkrétny kontajner.
Keď chceš, aby sa efekt pustil neskôr, veľmi často pomôže práve záporný spodný rootMargin.
IntersectionObserver a lazy loading
Hoci dnes máme pri obrázkoch aj natívne loading="lazy", IntersectionObserver sa stále hodí na rôzne vlastné lazy logiky.
Napríklad: nenačítať iframe hneď, nespustiť video hneď, nenačítať ďalšie karty hneď, alebo nespustiť ťažší komponent skôr, než sa k nemu používateľ vôbec dostane.
IntersectionObserver verzus nové CSS scroll animácie
Moderné CSS už pozná aj scroll-driven animácie. Sem patria vlastnosti ako scroll-timeline, view-timeline, animation-timeline alebo animation-range.
To znamená, že animácia môže byť naviazaná priamo na scroll bez JavaScriptu. Znie to super, ale je tu jeden dôležitý háčik: podpora ešte nie je úplne rovnaká vo všetkých prehliadačoch.
A práve preto má IntersectionObserver stále veľký zmysel. Keď chceme spoľahlivý efekt typu: „prvok vojde do obrazovky a zobrazí sa“ JavaScript je stále veľmi praktické riešenie.
Hlavne pri Firefoxe si treba dať pozor, lebo nové scroll-driven CSS vlastnosti ešte nemusia byť plne použiteľné tak, ako by človek čakal. Čiže ak chceme kompatibilnejšie riešenie dnes, IntersectionObserver je stále veľmi bezpečná voľba.
Kedy použiť čo
Ak chceme: sekcia sa zobrazí, keď k nej prídem, IntersectionObserver je veľmi dobrý.
Ak chceme: animácia sa má priebežne hýbať podľa scrollu, tam už pozeráme aj na moderné CSS scroll animácie alebo na iné JavaScript riešenia.
Zhrnutie po lopate
IntersectionObserver je strážnik.
Povieš mu: tento prvok sleduj. Keď sa objaví tam, kde ma to zaujíma, daj mi vedieť.
root = voči čomu sa viditeľnosť sleduje,rootMargin = posunutie hranice sledovania,threshold = koľko z prvku musí byť vidieť,.show = praktický spôsob, ako oddeliť logiku od vzhľadu.
A tá hlavná praktická myšlienka je: JavaScript nech oznámi stav, CSS nech urobí vizuál.
Kompletná ukážka
<body>
<h1>IntersectionObserver – čo to je a načo to je</h1>
<div class="ukazka">
<div class="blok js-reveal">Lorem ipsum dolor sit amet.</div>
<div class="blok js-reveal">Consectetur adipisicing elit.</div>
<div class="blok js-reveal">Quasi, itaque, illum. Molestiae.</div>
</div>
<script>
const items = document.querySelectorAll(".js-reveal");
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add("show");
obs.unobserve(entry.target);
});
}, {
root: null,
rootMargin: "0px 0px -80px 0px",
threshold: 0.15
});
items.forEach((item) => observer.observe(item));
</script>
</body>
CSS ukážka k tomu
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.ukazka {
border: 1px solid #a50000;
padding: 20px;
}
.blok {
margin-bottom: 80px;
padding: 24px;
border-radius: 16px;
background: #ececec;
opacity: 0;
transform: translateY(40px);
transition: opacity 0.7s ease, transform 0.7s ease;
}
.blok.show {
opacity: 1;
transform: translateY(0);
}
Súvisí to aj s tým, čo robím
Pozrite sa, s čím viem pomôcť
Ak riešite nový web, úpravy existujúcej stránky alebo audit SEO, GEO, prístupnosti a výkonu, pozrite si moje služby.
Jednoduchý web na mieru
Pre menšie prezentácie, firemné weby a stránky, ktoré majú byť prehľadné, rýchle a ľahko použiteľné.
Pozrieť službuZložitejší web na mieru a webová aplikácia
Keď už nestačí len jednoduchá prezentácia a web potrebuje vlastnú logiku, správu dát alebo rozšírené funkcie.
Pozrieť službuSEO, GEO a prístupnostný audit
Kontrola technického stavu webu, viditeľnosti vo vyhľadávaní, prístupnosti a celkovej použiteľnosti.
Pozrieť službuÚpravy a rozšírenie webu
Keď web už existuje, ale potrebuje opravy, vylepšenia, zrýchlenie alebo nové časti.
Pozrieť službu