Przyznam, że delikatnie irytowało mnie ładowanie głównej strony mojego bloga, gdzie wraz z pierwszym akapitem każdego wpisu ładowany jest film z YT. A ponieważ ładowanych jest kilka artykułów, czyli kilka filmów, strona nie wczytywała się zbyt płynnie i szybko. Nie chciałem się jednocześnie tego pozbywać, aby urozmaicić i nadać wstępny klimat artykułowi. Od początku myślałem o tym, że wykorzystam lazy loading. Natomiast przed napisaniem tego artykułu pomyślałem, że warto przy okazji tej opisać dwie inne ciekawostki.
Czy na pewno leń nic nie robi?
O lazy loading być może słyszałeś. Po krótce jest to metoda wedle, której dany zasób wczytywany jest w momencie gdy zaistnieje potrzeba jego użycia. Wcale nie musi dotyczyć wizualizacji elementu, może to być też również sytuacja kiedy chcemy zainicjować jakiś obiekt lub wywołać zasób z danego URL. Oczywiście zgodnie z tematem tego wpisu, będzie jednak dotyczył pokazywania elementów. Tak więc nasz leń, będzie sobie siedział grzecznie na kanapie, ale w momencie kiedy ktoś go podejrzy, będzie musiał pokazać w co jest ubrany. Nie będziemy od razu ładować wszystkich IFRAME z filmami, ale tylko te, które zaczną być widoczne w miarę przewijania strony.
Leniwy kot Schrödingera.
Wiemy, że kiedy IFRAME powinna być widoczna, to...powinna być widoczna. Ale musi mieć drugi stan przed tym kiedy... nie jest widoczna. Ładowanie zablokujemy poprzez nadpisanie jej zawartości oraz zmianę atrybutu src. Jednak aby ramka nie pozostała pusta przyda się jakiś zapychacz. Tu przyszedł mi pomysł wykorzystania gotowych miniaturek filmu YT. Mamy ich kilka, z różnych fragmentów i o różnych rozdzielczościach.
URL | rozmiar | opis |
---|---|---|
https://img.youtube.com/vi/ID/0.jpg | 480x360 | domyślna |
https://img.youtube.com/vi/ID/1.jpg | 120x90 | początek |
https://img.youtube.com/vi/ID/2.jpg | 120x90 | środek |
https://img.youtube.com/vi/ID/3.jpg | 120x90 | koniec |
https://img.youtube.com/vi/ID/default.jpg | 120x90 | domyślna |
https://img.youtube.com/vi/ID/sddefault.jpg | 640x480 | domyślna |
https://img.youtube.com/vi/ID/mqdefault.jpg | 320x180 | domyślna |
https://img.youtube.com/vi/ID/hqdefault.jpg | 480x360 | domyślna |
https://img.youtube.com/vi/ID/maxresdefault.jpg | 1280x720 | domyślna |
Przeszywający wzrok.
Wiedząc już, że wykorzystamy lazy loading i gotowe miniatury YT jako placeholdery, trzeba jeszcze obmyśleć metodę wykrywania momentu kiedy musi być załadowana oryginalna zawartość IFRAME. Można to zrobić podpinając się pod zdarzenie onscroll i badać gdzie są krawędzie elementu, ale... po co? Wykorzystamy Intersection Observer API. Inicjując obiekt możemy w ramach jakiego kontenera będzie przeprowadzana obserwacja, czy należy uwzględniać marginesy oraz jaki procent pokazania elementu uznajemy za moment kulminacyjny. Możemy podać kilka wartości, wtedy observer będzie informował nas przy przekroczeniu każdej z nich. Ciekawostką jest to, że procent pokazania dotyczy powierzchni elementu, a nie tylko wysokości lub tylko szerokości. Jeśli więc ustalimy próg na 0.25, to przy pokazaniu jakiejkolwiek ćwiartki lub wąskiego paska (1/4 szerokości i pełna wysokość lub pełna szerokość i 1/4 wysokości), czyli po prostu 25% powierzchni, zostanie osiągnęty punkt wywołania. Udostępniony też zostaje callback, który zwraca listę obserwowanych obiektów oraz samego siebie, w momencie osiągniecia granicy. Po inicjalizacji dodajemy do observera elementy, które chcemy obserwować. Dla pojedynczego elementu zwróconej listy dla nas będą istotne głównie jego dwie właściwości: isIntersecting oraz target. Właściwość isIntersecting jest read-only i jeśli ma wartość true, to oznacza, że element przeszedł ze stanu niewidoczności do widoczności, jeśli false, jest odwrotnie. Natomiast target to po prostu referencja na obserwowany obiekt drzewa DOM.
Wygląda to tak:
/* inicjujemy obserwatora */
const observer = new IntersectionObserver(callback, opcje);
+
/* nasz callback,
oczywiście można w tworzeniu
obserwatora użyć funkcji
anonimowej
*/
function callback(entries, observer) {
entries.forEach(function(entry) {
//tu coś robimy z entry
});
});
+
/* a tak dodajemy
element do obserwacji
*/
observer.observe(element);
Pracowity kod Schrödingera.
Czas na skrypt JS. Mamy tu więc IFRAME z URL filmu YT. ID filmu to ostatnia część URL. Kiedy tylko drzewo DOM jest gotowe, to iterujemy po wszystkich IFRAME z filmami, usuwamy zawartość, zapamiętujemy źródło i usuwamy atrybut src. Z URL możemy łatwo wyłuskać ID filmu, bo to ostatni fragment za /. Mając ID tworzymy URL do miniatury. Nie usuwamy ramki, tylko ustawiamy tło na miniaturę z YT. Następnie tworzymy obiekt IntersectionObserver i dodajemy wszystkie ramki z data-orig-src (zapamiętanym src) do listy obserwowanych. W callback obserwatora sprawdzamy czy ramka przeszła ze stanu niewidoczności do widoczności i jeśli tak, to usuwamy tło i przywracamy oryginalne źródło, oraz usuwamy ją z obserwowanych.
<div style="height:150vh">To tylko zapychacz aby odsunąć ramkę na dół</div>
<iframe frameborder="0" height="315" scrolling="no" src="https://www.youtube.com/embed/Nd9hCYWphLw" width="560"></iframe>
+
window.addEventListener("DOMContentLoaded", function() {
const iframes = document.querySelectorAll('iframe[src^="https://www.youtube.com"]')
iframes.forEach(function(iframe) {
const src = iframe.src.split("/").reverse();
iframe.contentWindow.document.open();
iframe.contentWindow.document.write("");
iframe.dataset.origSrc = iframe.src;
iframe.removeAttribute("src");
iframe.style.backgroundImage = "url(https://img.youtube.com/vi/" + src[0] + "/hqdefault.jpg)";
iframe.style.backgroundSize = "cover";
iframe.style.backgroundPosition = "center center";
});
const observer = new IntersectionObserver(
function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
observer.unobserve(entry.target);
entry.target.style.removeProperty("backgroundImage");
entry.target.style.removeProperty("backgroundSize");
entry.target.style.removeProperty("backgroundPosition");
entry.target.src = entry.target.dataset.origSrc;
}
});
}, {
/* jeśli widać minimum 50% obiektu */
threshold: 0.5
}
);
document.querySelectorAll("iframe[data-orig-src]").forEach(function(iframe) {
observer.observe(iframe);
});
});
I w działaniu (ten sam skrypt użyty jest do ładowania filmów z YT na tym blogu):
API możesz wykorzystać również przy ładowaniu obrazków na stronie czy też doczytywaniu treści. Powodzenia!
Przydatne linki:
IntersectionObserver API