Şimdi Beni Görüyorsun: IntersectionObserver ile Nasıl Ertelenir, Tembel Yüklenir ve Hareket Edilir
Yayınlanan: 2022-03-10Bir zamanlar, sitelerin tüm tarayıcılarda aynı görünmemesi gerektiğine müşterilerini başarıyla ikna eden, erişilebilirliğe önem veren ve CSS ızgaralarını erken benimseyen bir web geliştiricisi vardı. Ama kalbinin derinliklerinde gerçek tutkusu performanstı: Projelerinde sürekli olarak optimize etti, küçülttü, izledi ve hatta psikolojik hileler kullandı.
Ardından, bir gün, kullanıcılar tarafından hemen görülemeyen ve ekranda anlamlı içerik oluşturmak için gerekli olmayan, geç yüklenen resimler ve diğer varlıklar hakkında bilgi edindi. Şafağın başlangıcıydı: Geliştirici, tembel yüklenen jQuery eklentilerinin şeytani dünyasına (veya belki de async
ve defer
özelliklerinin o kadar da kötü olmayan dünyasına) girdi. Hatta bazıları, tüm kötülüklerin doğrudan özüne girdiğini söylüyor: scroll
olayı dinleyicileri dünyası. Nerede olduğunu asla kesin olarak bilemeyeceğiz, ancak yine de bu geliştirici kesinlikle hayalidir ve herhangi bir geliştiriciyle herhangi bir benzerlik sadece tesadüfidir.
Artık Pandora'nın kutusunun açıldığını ve hayali geliştiricimizin sorunu daha az gerçek yapmadığını söyleyebilirsiniz. Günümüzde, web projelerimizin hem hız hem de sayfa ağırlığı açısından performansı için ekranın üst kısmındaki içeriğe öncelik vermek son derece önemli hale geldi.
Bu yazıda, scroll
karanlığından çıkıp, kaynakları tembel yüklemenin modern yolu hakkında konuşacağız. Yalnızca tembel yükleme görüntüleri değil, aynı zamanda bu konuda herhangi bir varlık yükleme. Dahası, bugün bahsedeceğimiz teknik, varlıkları tembelce yüklemekten çok daha fazlasını yapabilir: Öğelerin kullanıcılara görünürlüğüne dayalı olarak herhangi bir türde ertelenmiş işlevsellik sağlayabileceğiz.
Bayanlar ve baylar, Kesişme Gözlemcisi API'sinden bahsedelim. Ancak başlamadan önce, bizi IntersectionObserver
yönlendiren modern araçların manzarasına bir göz atalım.
2017, tarayıcılarımızda yerleşik olarak bulunan araçlar için çok iyi bir yıldı ve çok fazla çaba harcamadan kod tabanımızın kalitesini ve stilini iyileştirmemize yardımcı oldu. Bu günlerde, web, çok tipik olan ve daha iyi tanımlanmış bir Observer arayüzleri yaklaşımına (veya sadece “Gözlemcilere”) çok farklı çözümlere dayanan düzensiz çözümlerden uzaklaşıyor gibi görünüyor: İyi desteklenmiş MutationObserver, hızlı bir şekilde yeni aile üyelerine sahip oldu. modern tarayıcılarda kabul edildi:
- Kavşak Gözlemcisi ve
- PerformanceObserver (Performans Zaman Çizelgesi Düzey 2 belirtiminin bir parçası olarak).
Bir potansiyel aile üyesi daha, FetchObserver, devam eden bir çalışmadır ve bizi bir ağ proxy'sinin topraklarına daha fazla yönlendirir, ancak bugün bunun yerine ön uç hakkında daha fazla konuşmak istiyorum.
PerformanceObserver
ve IntersectionObserver
, ön uç geliştiricilerin projelerinin performansını farklı noktalarda iyileştirmelerine yardımcı olmayı amaçlar. İlki bize Gerçek Kullanıcı İzleme için bir araç sağlarken, ikincisi bize somut performans iyileştirmesi sağlayan araçtır. Daha önce de belirtildiği gibi, bu makale tam olarak ikincisine ayrıntılı olarak bakacaktır: IntersectionObserver . Özellikle IntersectionObserver
mekaniğini anlamak için, genel bir Observer'ın modern web'de nasıl çalışması gerektiğine bir göz atmalıyız.
Profesyonel İpucu : Teoriyi atlayabilir ve hemen IntersectionObserver'ın mekaniğine dalabilirsiniz veya daha da ilerisi, doğrudan IntersectionObserver
olası uygulamalarına geçebilirsiniz.
Gözlemci ve Olay
Adından da anlaşılacağı gibi bir "Gözlemci", bir sayfa bağlamında gerçekleşen bir şeyi gözlemlemeyi amaçlar. Gözlemciler, DOM değişiklikleri gibi bir sayfada olup bitenleri izleyebilir. Ayrıca sayfanın yaşam döngüsü olaylarını da izleyebilirler. Gözlemciler ayrıca bazı geri arama işlevlerini de çalıştırabilir. Şimdi dikkatli okuyucu buradaki sorunu hemen fark edebilir ve “Peki, amaç ne? Zaten bu amaçla etkinliklerimiz yok mu? Observers'ı farklı kılan nedir?” Çok iyi nokta! Daha yakından bakalım ve çözelim.
Normal Olay ve Gözlemci arasındaki en önemli fark, varsayılan olarak, birincisinin Olay'ın her oluşumu için eşzamanlı olarak tepki vermesi ve ana iş parçacığının yanıt verme hızını etkilemesi, ikincisinin ise performansı çok fazla etkilemeden eşzamansız olarak tepki vermesidir. En azından, bu şu anda sunulan Gözlemciler için geçerlidir: Hepsi asenkron olarak davranır ve bunun gelecekte değişeceğini sanmıyorum.
Bu, yeni başlayanların kafasını karıştırabilecek Gözlemcilerin geri aramalarının ele alınmasında ana farklılığa yol açar: Gözlemcilerin zaman uyumsuz yapısı, aynı anda birkaç gözlemlenebilirin bir geri arama işlevine geçirilmesine neden olabilir. Bu nedenle, geri arama işlevi tek bir giriş değil, bir Array
giriş beklemelidir (bazen Dizi içinde yalnızca bir giriş içerse bile).
Ayrıca, bazı Gözlemciler (özellikle bugün bahsettiğimiz), çok kullanışlı önceden hesaplanmış özellikler sağlar, aksi takdirde düzenli olayları kullanırken pahalı (performans açısından) yöntemler ve özellikler kullanarak kendimizi hesaplardık. Bu noktayı açıklığa kavuşturmak için, makalenin biraz ilerisinde bir örneğe geçeceğiz.
Bu nedenle, Olay paradigmasından ayrılmak biri için zorsa, Gözlemcilerin steroidlerle ilgili olaylar olduğunu söyleyebilirim. Başka bir tanım şöyle olacaktır: Gözlemciler, olayların üstünde yeni bir yaklaşım düzeyidir. Ancak hangi tanımı tercih ederseniz edin, Gözlemcilerin olayların yerini alması amaçlanmadığını söylemeye gerek yok (en azından henüz değil); her ikisi için de yeterli kullanım durumu vardır ve yan yana mutlu bir şekilde yaşayabilirler.
Genel Gözlemcinin Yapısı
Bir Gözlemcinin genel yapısı (yazma sırasında mevcut olanlardan herhangi biri) şuna benzer:
/** * 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);
Yine, entries
tek bir giriş değil, bir değerler Array
olduğunu unutmayın.
Bu genel yapıdır: Belirli Gözlemcilerin uygulamaları, observe()
iletilen argümanlarda ve geri çağrısına iletilen argümanlarda farklılık gösterir. Örneğin MutationObserver
, DOM'da hangi değişikliklerin gözlemleneceği hakkında daha fazla bilgi edinmek için bir yapılandırma nesnesi de almalıdır. PerformanceObserver
, DOM'deki düğümleri gözlemlemez, bunun yerine gözlemleyebileceği özel giriş türleri kümesine sahiptir.
Burada, bu tartışmanın "genel" kısmını bitirelim ve bugünün makalesinin konusu olan IntersectionObserver
daha derinlerine inelim.
IntersectionObserver'ı Deconstructing
Öncelikle IntersectionObserver
ne olduğunu çözelim.
MDN'ye göre:
Intersection Observer API, bir hedef öğenin bir üst öğeyle veya bir üst düzey belgenin görünüm penceresiyle kesişimindeki değişiklikleri eşzamansız olarak gözlemlemek için bir yol sağlar.
Basitçe söylemek gerekirse, IntersectionObserver
bir öğenin başka bir öğeyle örtüşmesini eşzamansız olarak gözlemler. Şimdi bu öğelerin IntersectionObserver
ne işe yaradığından bahsedelim.
IntersectionObserver Başlatma
Önceki paragraflardan birinde, genel bir Observer'ın yapısını gördük. IntersectionObserver
bu yapıyı biraz genişletir. Her şeyden önce, bu tür bir Gözlemci, üç ana öğeye sahip bir yapılandırma gerektirir:
-
root
: Bu, gözlem için kullanılan kök elemandır. Gözlenebilir öğeler için temel "yakalama çerçevesini" tanımlar. Varsayılan olarak,root
, tarayıcınızın görünüm alanıdır, ancak gerçekten DOM'nizdeki herhangi bir öğe olabilir (o zamanroot
,document.getElementById('your-element')
gibi bir şeye ayarlarsınız). Ancak, bu durumda gözlemlemek istediğiniz öğelerinroot
DOM ağacında “yaşaması” gerektiğini unutmayın.
-
rootMargin
:root
öğenizin boyutları yeterli esneklik sağlamadığında "yakalama çerçevesini" genişleten veya daraltanroot
öğenizin etrafındaki kenar boşluğunu tanımlar. Bu konfigürasyonun değerleri için seçenekler,rootMargin: '50px 20px 10px 40px'
(üst, sağ alt, sol) gibi, CSS'dekimargin
değerlerine benzer. Değerler kısaltılabilir (rootMargin: '50px'
gibi) vepx
veya%
olarak ifade edilebilir. Varsayılan olarakrootMargin: '0px'
.
-
threshold
: Gözlenen bir öğe "yakalama çerçevesinin" (root
verootMargin
birleşimi olarak tanımlanır) bir sınırıyla kesiştiğinde anında tepki vermek her zaman istenmez.threshold
, Observer'ın tepki vermesi gereken bu tür kesişme yüzdesini tanımlar. Tek bir değer veya bir dizi değer olarak tanımlanabilir.threshold
etkisini daha iyi anlamak için (bazen kafa karıştırıcı olabileceğini biliyorum), işte bazı örnekler:-
threshold: 0
:IntersectionObserver
varsayılan değeri , gözlemlenen bir öğenin en ilk veya en son pikseli "yakalama çerçevesinin" sınırlarından biriyle kesiştiğinde tepki vermelidir.IntersectionObserver
yönden bağımsız olduğunu, yani her iki senaryoda da tepki vereceğini unutmayın: a) öğe girdiğinde ve b) "yakalama çerçevesinden" ayrıldığında . -
threshold: 0.5
: Gözlenen öğenin %50'si “yakalama çerçevesi” ile kesiştiğinde, gözlemci tetiklenmelidir; -
threshold: [0, 0.2, 0.5, 1]
: Gözlemci 4 durumda tepki vermelidir:- Gözlenen bir öğenin ilk pikseli "yakalama çerçevesine" girer: öğe hala gerçekten o karenin içinde değildir veya gözlenen öğenin en son pikseli "yakalama çerçevesini" terk eder: öğe artık çerçeve içinde değildir;
- Elemanın %20'si “yakalama çerçevesi” içindedir (yine,
IntersectionObserver
için yön önemli değildir); - Öğenin %50'si "yakalama çerçevesi" içindedir;
- Öğenin %100'ü "yakalama çerçevesi" içindedir. Bu,
threshold: 0
.
-
IntersectionObserver
istediğimiz konfigürasyonu bildirmek için, config
nesnemizi Observer'ın yapıcısına aşağıdaki gibi geri çağırma fonksiyonumuzla birlikte iletiyoruz:
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);
Şimdi, IntersectionObserver
gözlemlenecek asıl öğeyi vermeliyiz. Bu, yalnızca öğeyi observe()
işlevine geçirerek yapılır:
… const img = document.getElementById('image-to-observe'); observer.observe(image);
Bu gözlemlenen öğe hakkında not edilmesi gereken birkaç şey:
- Daha önce bahsedildi ama tekrar değinmekte fayda var: DOM'da bir element olarak
root
ayarladıysanız, gözlemlenen elementroot
DOM ağacında yer almalıdır. -
IntersectionObserver
, bir seferde gözlem için yalnızca bir öğeyi kabul edebilir ve gözlemler için toplu tedariki desteklemez. Bu, birkaç öğeyi (bir sayfada birkaç resim diyelim) gözlemlemeniz gerekiyorsa, hepsini yinelemeniz ve her birini ayrı ayrı gözlemlemeniz gerektiği anlamına gelir:
… const images = document.querySelectorAll('img'); images.forEach(image => { observer.observe(image); });
- Observer yerindeyken bir sayfa yüklerken,
IntersectionObserver
geri çağrısının gözlemlenen tüm öğeler için aynı anda tetiklendiğini fark edebilirsiniz. Sağlanan konfigürasyona uymayanlar bile. “Pekala… gerçekten beklediğim gibi değil”, bunu ilk kez yaşarken olağan düşüncedir. Ancak burada kafanız karışmasın: bu, gözlenen öğelerin sayfa yüklenirken bir şekilde “yakalama çerçevesi” ile kesiştiği anlamına gelmez.
Bunun anlamı, bu öğenin girişinin başlatıldığı ve şimdi IntersectionObserver
tarafından kontrol edildiğidir. Ancak bu, geri arama işlevinize gereksiz gürültü ekleyebilir ve hangi öğelerin gerçekten "yakalama çerçevesi" ile kesiştiğini ve hangilerini hesaba katmamız gerekmediğini saptamak sizin sorumluluğunuzdadır. Bu saptamanın nasıl yapıldığını anlamak için, geri arama işlevimizin anatomisine biraz daha derine inelim ve bu tür girişlerin nelerden oluştuğuna bir göz atalım.
KavşakGözlemci Geri Arama
Her şeyden önce, bir IntersectionObserver
için geri çağırma işlevi iki argüman alır ve bunlar hakkında ikinci argümandan başlayarak ters sırada konuşacağız. Yukarıda bahsedilen gözlemlenen girdiler Array
ile birlikte, "yakalama çerçevemiz" ile kesişen geri arama işlevi, ikinci argüman olarak Gözlemcinin kendisini alır.
Gözlemcinin Kendisine Referans
new IntersectionObserver(function(entries, SELF) {…});
Observer'ın kendisine referansı almak, IntersectionObserver
tarafından ilk kez tespit edildikten sonra bazı elementleri gözlemlemeyi durdurmak istediğinizde birçok senaryoda kullanışlıdır. Görüntülerin geç yüklenmesi, diğer varlıkların ertelenmiş getirilmesi vb. gibi senaryolar bu türdendir. Bir öğeyi gözlemlemeyi durdurmak istediğinizde, IntersectionObserver
, gözlemlenen öğe üzerinde bazı eylemler gerçekleştirdikten sonra (örneğin bir görüntünün gerçek tembel yüklenmesi gibi) geri çağırma işlevinde çalıştırılabilen bir unobserve(element-to-stop-observing)
yöntemi sağlar. ).
Bu senaryolardan bazıları makalede daha ayrıntılı olarak incelenecektir, ancak bu ikinci argüman yolumuzdan çekilince, hadi bu geri çağırma oyununun ana aktörlerine geçelim.
KavşakGözlemciGirişi
new IntersectionObserver(function(ENTRIES, self) {…});
Bir Array
olarak geri çağırma fonksiyonumuzda aldığımız entries
özel tiptedir: IntersectionObserverEntry
. Bu arayüz bize, gözlemlenen her bir elemente ilişkin önceden tanımlanmış ve önceden hesaplanmış bir dizi özellik sağlar. En ilginçlerine bir göz atalım.
Her şeyden önce, IntersectionObserverEntry
türündeki girişler, sürece dahil olan öğelerin koordinatlarını ve sınırlarını tanımlayan üç farklı dikdörtgen hakkında bilgi ile birlikte gelir:
-
rootBounds
: “yakalama çerçevesi” için bir dikdörtgen (root
+rootMargin
); -
boundingClientRect
: Gözlenen öğenin kendisi için bir dikdörtgen; -
intersectionRect
: Gözlenen öğeyle kesişen "yakalama çerçevesi" alanı.
Bu dikdörtgenlerin bizim için asenkronize olarak hesaplanmasının gerçekten harika yanı, bize getBoundingClientRect()
, offsetTop
, offsetLeft
ve diğer pahalı konumlandırma özelliklerini ve mizanpaj thrashing'i tetikleyen yöntemleri çağırmadan elemanın konumlandırmasıyla ilgili önemli bilgiler vermesidir. Performans için saf galibiyet!
IntersectionObserverEntry
arayüzünün bizim için ilginç olan bir başka özelliği de isIntersecting
. Bu, gözlenen elemanın şu anda "yakalama çerçevesi" ile kesişip kesişmediğini gösteren bir uygunluk özelliğidir. Elbette bu bilgiyi Dikdörtgen intersectionRect
bakarak alabiliriz (eğer bu dikdörtgen 0×0 değilse, eleman “yakalama çerçevesi” ile kesişiyor) ama bunun önceden hesaplanması bizim için oldukça uygundur.
isIntersecting
, gözlemlenen öğenin "yakalama çerçevesine" yeni girip girmediğini veya zaten onu terk edip etmediğini bulmak için kullanılabilir. Bunu bulmak için, bu özelliğin değerini bir genel bayrak olarak kaydedin ve bu öğe için yeni giriş geri arama işlevinize ulaştığında, bunun yeni olduğunu şu genel bayrakla isIntersecting
karşılaştırın:
- Eğer
false
idiyse ve şimditrue
ise, o zaman eleman “yakalama çerçevesine” giriyordur; - Tersiyse ve daha önce
true
şimdifalse
, öğe "yakalama çerçevesini" terk ediyor demektir.
isIntersecting
tam olarak daha önce tartıştığımız sorunu çözmemize yardımcı olan bir özelliktir, yani “yakalama çerçevesi” ile gerçekten kesişen öğeler için girişleri, yalnızca girişin başlatılması olan öğelerin gürültüsünden ayırın.
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 : Microsoft Edge 15'te isIntersecting
özelliği uygulanmadı, aksi takdirde IntersectionObserver
için tam desteğe rağmen undefined
olarak döndürüldü. Bu, Temmuz 2017'de düzeltildi ve Edge 16'dan beri mevcut.
IntersectionObserverEntry
arabirimi, önceden hesaplanmış bir kolaylık özelliği daha sağlar: intersectionRatio
. Bu parametre isIntersecting
ile aynı amaçlar için kullanılabilir ancak boole değeri yerine kayan noktalı bir sayı olduğu için daha ayrıntılı kontrol sağlar. kesişimRatio değeri, gözlemlenen öğenin alanının ne kadarının "yakalama çerçevesi" ile intersectionRatio
gösterir ( intersectionRect
alanının boundingClientRect
alanına oranı). Yine, bu hesaplamayı o dikdörtgenlerden gelen bilgileri kullanarak kendimiz yapabilirdik, ama bizim için yapılması iyi oldu.
target
, IntersectionObserverEntry
arabiriminin oldukça sık erişmeniz gerekebilecek bir özelliğidir. Ancak burada kesinlikle sihir yoktur – bu yalnızca Observer'ınızın observe()
işlevine geçirilen orijinal öğedir. Tıpkı olaylarla çalışırken alıştığınız event.target
gibi.
IntersectionObserverEntry
arabiriminin özelliklerinin tam listesini almak için belirtimi kontrol edin.
Olası Uygulamalar
Bu makaleye büyük olasılıkla tam da bu bölüm yüzünden geldiğinizin farkındayım: Sonuçta kopyala ve yapıştır için kod parçacıklarımız varken mekanik kimin umurunda? Bu yüzden şimdi daha fazla tartışma ile sizi rahatsız etmeyeceğiz: kodlar ve örnekler diyarına giriyoruz. Umarım koda dahil edilen yorumlar işleri daha net hale getirir.
Ertelenmiş İşlevsellik
Öncelikle IntersectionObserver
fikrinin altında yatan temel ilkeleri ortaya koyan bir örneği inceleyelim. Diyelim ki ekranda bir kez çok fazla hesaplama yapması gereken bir öğeniz var. Örneğin, reklamınız yalnızca bir kullanıcıya gerçekten gösterildiğinde bir görüntüleme kaydetmelidir. Ancak şimdi, sayfanızdaki ilk ekranın altında bir yerde otomatik olarak oynatılan bir dönen öğeniz olduğunu düşünelim.
Bir atlıkarınca çalıştırmak, genel olarak ağır bir iştir. Genellikle, JavaScript zamanlayıcıları, öğeler arasında otomatik olarak gezinmek için hesaplamalar vb. içerir. Bu görevlerin tümü ana iş parçacığını yükler ve otomatik oynatma modunda yapıldığında, ana iş parçacığımızın bu isabeti ne zaman alacağını bilmek bizim için zordur. İlk ekranımızda içeriği önceliklendirmekten bahsettiğimizde ve İlk Anlamlı Boyama ve Etkileşim Zamanı'na bir an önce basmak istediğimizde, bloke edilen ana iş parçacığı performansımız için bir darboğaz haline geliyor.
Sorunu çözmek için, böyle bir atlıkarıncanın oynatılmasını, tarayıcının görüş alanına girene kadar erteleyebiliriz. Bu durumda, bilgimizi ve örneğimizi IntersectionObserverEntry
arabiriminin isIntersecting
parametresi için kullanacağız.
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);
Burada, atlıkarıncayı yalnızca görüş alanımıza girdiğinde oynarız. IntersectionObserver
başlatma işlemine iletilen config
nesnesinin yokluğuna dikkat edin: bu, varsayılan yapılandırma seçeneklerine güvendiğimiz anlamına gelir. Karusel görüş alanımızdan çıktığında, artık önemli olmayan unsurlara kaynak harcamamak için oynamayı bırakmalıyız.
Varlıkların Tembel Yüklenmesi
Bu muhtemelen IntersectionObserver
için en belirgin kullanım durumudur: Kullanıcının şu anda ihtiyaç duymadığı bir şeyi indirmek için kaynak harcamak istemiyoruz. Bu, kullanıcılarınıza büyük bir fayda sağlayacaktır: kullanıcıların indirme yapmasına gerek kalmayacak ve mobil cihazlarının, şu anda ihtiyaç duymadıkları pek çok işe yaramaz bilgiyi ayrıştırıp derlemesi gerekmeyecek. Şaşırtıcı olmayan bir şekilde, uygulamanızın performansına da yardımcı olacaktır.
Daha önce, indirme ve işleme kaynaklarını kullanıcının ekrana getirebileceği ana kadar ertelemek için, scroll
gibi olaylarda olay dinleyicileriyle uğraşıyorduk. Sorun açık: Bu, dinleyicileri çok sık tetikledi. Bu nedenle, geri aramanın yürütülmesini azaltma veya geri çağırma fikrini bulmamız gerekiyordu. Ancak tüm bunlar, en çok ihtiyaç duyduğumuz anda onu engelleyen ana iş parçacığımız üzerinde çok fazla baskı yarattı.
Öyleyse, tembel yükleme senaryosunda IntersectionObserver
geri dönersek, nelere dikkat etmeliyiz? Tembel yüklenen görüntülerin basit bir örneğini kontrol edelim.
Bu sayfayı yavaşça "üçüncü ekrana" kaydırmayı deneyin ve sağ üst köşedeki izleme penceresini izleyin: o ana kadar kaç görüntünün indirildiğini size bildirecektir.
Bu görev için HTML işaretlemesinin özünde basit bir resim dizisi bulunur:
… <img data-src="https://blah-blah.com/foo.jpg"> …
Gördüğünüz gibi, görüntüler src
etiketleri olmadan gelmelidir: bir tarayıcı src
özniteliğini gördüğünde, niyetimizin tam tersi olan o görüntüyü hemen indirmeye başlayacaktır. Bu nedenle, HTML'deki resimlerimize bu niteliği koymamalıyız ve bunun yerine, burada data-src
gibi bazı data-
-özniteliklerine güvenebiliriz.
Bu çözümün bir başka parçası da elbette JavaScript'tir. Buradaki ana bitlere odaklanalım:
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); });
Yapısal olarak, burada yeni bir şey yok: bunların hepsini daha önce ele aldık:
- Tüm mesajları
data-src
özniteliklerimizle alıyoruz; - Set
config
: bu senaryo için, görünümün altından biraz daha düşük öğeleri algılamak için “yakalama çerçevenizi” genişletmek istiyorsunuz; -
IntersectionObserver
bu yapılandırmayla kaydedin; - Görüntülerimizi yineleyin ve hepsini bu
IntersectionObserver
tarafından gözlemlenecek şekilde ekleyin;
İlginç kısım, girişlerde çağrılan geri arama işlevinde gerçekleşir. İlgili üç temel adım vardır.
Her şeyden önce, yalnızca “yakalama çerçevemizle” gerçekten kesişen öğeleri işliyoruz. Bu snippet'in şimdiye kadar size aşina olması gerekir.
entries.forEach(entry => { if (entry.isIntersecting) { … } });
Ardından,
data-src
ile imajımızı gerçek bir<img src="…">
haline dönüştürerek girişi bir şekilde işleriz.if (entry.isIntersecting) { preloadImage(entry.target); … }
preloadImage()
burada bahsedilmeye değmeyen çok basit bir fonksiyondur. Sadece kaynağı oku.Sonraki ve son adım: Geç yükleme tek seferlik bir işlem olduğundan ve öğe "yakalama çerçevemize" her girdiğinde görüntüyü indirmemiz gerekmediğinden, önceden işlenmiş görüntüyü
unobserve
. Kodumuzdaki bellek sızıntılarını önlemek için artık gerekli olmadığında normal etkinliklerimiz için bunuelement.removeEventListener()
ile yapmamız gerektiği gibi.if (entry.isIntersecting) { preloadImage(entry.target); // Observer has been passed as
self
to our callback self.unobserve(entry.target); }
Not. unobserve(event.target)
yerine connect( disconnect()
de çağırabiliriz: IntersectionObserver
bağlantısını tamamen keser ve artık görüntüleri gözlemlemez. Önem verdiğiniz tek şey Observer'ınız için ilk isabetse bu kullanışlıdır. Bizim durumumuzda, görüntüleri izlemeye devam etmek için Observer'a ihtiyacımız var, bu yüzden henüz bağlantıyı kesmemeliyiz.
Örneği çatallamaktan ve farklı ayarlar ve seçeneklerle oynamaktan çekinmeyin. Özellikle görüntüleri tembelce yüklemek istediğinizde bahsetmek için ilginç bir şey var. Gözlenen öğe tarafından oluşturulan kutuyu her zaman aklınızda tutmalısınız! Örneği kontrol ederseniz, 41-47 satırlarındaki resimler için CSS'nin sözde gereksiz stiller içerdiğini fark edeceksiniz. min-height: 100px
. Bu, görüntü yer tutucularına ( src
özniteliği olmadan <img>
) bir miktar dikey boyut vermek için yapılır. Ne için?
- Dikey boyutlar olmadan, tüm
<img>
etiketleri 0×0 kutusu oluşturur; -
<img>
etiketi varsayılan olarak bir türinline-block
kutusu oluşturduğundan, bu 0×0 kutularının tümü aynı satırda yan yana hizalanır; - Bu,
IntersectionObserver
tüm (veya ne kadar hızlı kaydırdığınıza bağlı olarak, neredeyse tüm) görüntüleri bir kerede kaydedeceği anlamına gelir - muhtemelen tam olarak elde etmek istediğiniz şey değil.
Geçerli Bölümün Vurgulanması
IntersectionObserver
, elbette tembel yüklemeden çok daha fazlasıdır. İşte scroll
olayını bu teknolojiyle değiştirmenin başka bir örneği. Bunda oldukça yaygın bir senaryomuz var: sabit gezinme çubuğunda, belgenin kaydırma konumuna göre geçerli bölümü vurgulamalıyız.
Yapısal olarak, geç yüklenen görüntüler örneğine benzer ve aşağıdaki istisnalar dışında aynı temel yapıya sahiptir:
- Şimdi görüntüleri değil, sayfadaki bölümleri gözlemlemek istiyoruz;
- Açıkçası, geri aramamızdaki girişleri işlemek için farklı bir işlevimiz de var (
intersectionHandler(entry)
). Ancak bu ilginç değil: tek yaptığı CSS sınıfını değiştirmek.
Burada ilginç olan config
nesnesidir:
const config = { rootMargin: '-50px 0px -55% 0px' };
Neden rootMargin
için varsayılan 0px
değeri olmasın, soruyorsunuz? Eh, çünkü mevcut bölümü vurgulamak ve bir görüntüyü tembelce yüklemek, elde etmeye çalıştığımız şeyde oldukça farklı. Tembel yükleme ile, görüntü görünüme girmeden yüklemeye başlamak istiyoruz. Bu amaçla, "yakalama çerçevemizi" altta 50 piksel genişlettik. Aksine, mevcut bölümü vurgulamak istediğimizde, bölümün gerçekten ekranda göründüğünden emin olmalıyız. Ve sadece bu değil: kullanıcının aslında tam olarak bu bölümü okuduğundan veya okuyacağından emin olmalıyız. Bu nedenle, aktif bölüm olarak ilan etmeden önce bir bölümün alttan görünümün yarısından biraz daha fazla gitmesini istiyoruz. Ayrıca, gezinme çubuğunun yüksekliğini hesaba katmak istiyoruz ve bu nedenle çubuğun yüksekliğini "yakalama çerçevesinden" kaldırıyoruz.
Ayrıca, mevcut gezinme öğesinin vurgulanması durumunda, hiçbir şeyi gözlemlemeyi bırakmak istemediğimizi unutmayın . Burada IntersectionObserver
her zaman sorumlu tutmalıyız, dolayısıyla burada ne disconnect()
ne de unobserve()
bulamazsınız.
Özet
IntersectionObserver
çok basit bir teknolojidir. Modern tarayıcılarda oldukça iyi bir desteği var ve bunu hala (veya hiç desteklemeyen) tarayıcılar için uygulamak istiyorsanız, elbette bunun için bir çoklu dolgu var. Ancak sonuçta, bu, gerçekten iyi bir performans artışı elde etmeye yardımcı olurken, bir görünüm penceresindeki öğeleri algılamayla ilgili her türlü şeyi yapmamızı sağlayan harika bir teknolojidir.
IntersectionObserver Neden Sizin İçin İyi?
-
IntersectionObserver
, zaman uyumsuz, engellemeyen bir API'dir! -
IntersectionObserver
,scroll
veyaresize
olaylarında pahalı dinleyicilerimizin yerini alır. -
IntersectionObserver
,getClientBoundingRect()
gibi tüm pahalı hesaplamaları sizin için yapar, böylece sizin yapmanıza gerek kalmaz. -
IntersectionObserver
, oradaki diğer Observer'ların yapısal modelini takip eder, bu nedenle, teorik olarak, diğer Observer'ların nasıl çalıştığına aşina iseniz, anlaşılması kolay olmalıdır.
Akılda Tutulması Gerekenler
IntersectionObserver'ın yeteneklerini, her şeyin geldiği yerden window.addEventListener('scroll')
dünyası ile karşılaştırırsak, bu Observer'da herhangi bir eksilerini görmek zor olacaktır. Öyleyse, bunun yerine akılda tutulması gereken bazı şeyleri not edelim:
- Evet,
IntersectionObserver
, zaman uyumsuz, engellemeyen bir API'dir. Bunu bilmek harika! Ancak, geri aramalarınızda çalıştırdığınız kodun, API'nin kendisi zaman uyumsuz olsa bile, varsayılan olarak zaman uyumsuz olarak çalıştırılmayacağını anlamak daha da önemlidir. Bu nedenle, geri çağırma işlevinizin hesaplamaları ana iş parçacığını yanıtsız hale getirirse,IntersectionObserver
elde ettiğiniz tüm faydaları ortadan kaldırma şansınız hala vardır. Ama bu farklı bir hikaye. - Varlıkları (örneğin resimler gibi) tembelce yüklemek için
IntersectionObserver
kullanıyorsanız, varlık yüklendikten sonra.unobserve(asset)
çalıştırın. IntersectionObserver
, yalnızca belgenin biçimlendirme yapısında görünen öğeler için kesişmeleri algılayabilir. Açıklığa kavuşturmak için: gözlemlenebilir öğeler bir kutu oluşturmalı ve bir şekilde düzeni etkilemelidir. İşte size daha iyi bir anlayış sağlamak için bazı örnekler:- Gösterilen öğeler
display: none
söz konusu değildir; -
opacity: 0
veyavisibility:hidden
kutuyu oluşturur (görünmez olsa bile) böylece bunlar algılanır; -
width:0px; height:0px
width:0px; height:0px
iyidir. 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.
- Gösterilen öğeler
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.