Ładowanie obrazów <img>, dopiero po przescrollowaniu

0

Mam stronę typu SPA. Jest długa, bo jest na niej dużo tekstu. Ale planuję także dodać galerię liczącą około 100 zdjęć. Oczywiście będą to miniaturki, po kliknięciu na które w postaci okna modalnego wyświetli się większy obrazek. Kłopot jednak w tym, że jak ktoś wejdzie na tę stronę, to te obrazki muszą się tak czy inaczej załadować.
Jak opóźnić proces ładowania tych obrazków tak, by pojawiły się one dopiero, jak scrollem przewinę stronę na tą galerię? Fajnie by też było dodać jakiś efekt animacji. Można to osiągnąć czystym JS/CSS, czy potrzebne są jakieś biblioteki typu Bootstrap?

1

na czym oparte spa? angular? vue? react? szukasz pod hasłem "image lazy loading for angular" i dostajesz jak to ogarnąć w konkretnym frameworku.

1

@kosmonauta80: naucz się odpowiadać na główny watek w wątku a nie komentarzu

kosmonauta80 napisał(a):

Czysty HTML/CSS/JS

To takie dvpa a nie spa. Spróbuj na czystym html https://www.w3schools.com/tags/att_img_loading.asp

2
kosmonauta80 napisał(a):

Mam stronę typu SPA. Jest długa, bo jest na niej dużo tekstu. Ale planuję także dodać galerię liczącą około 100 zdjęć. Oczywiście będą to miniaturki, po kliknięciu na które w postaci okna modalnego wyświetli się większy obrazek. Kłopot jednak w tym, że jak ktoś wejdzie na tę stronę, to te obrazki muszą się tak czy inaczej załadować.
Jak opóźnić proces ładowania tych obrazków tak, by pojawiły się one dopiero, jak scrollem przewinę stronę na tą galerię? Fajnie by też było dodać jakiś efekt animacji. Można to osiągnąć czystym JS/CSS, czy potrzebne są jakieś biblioteki typu Bootstrap?

Taki wymiatacz na forum, i nie potrafi rozkiłać takiego prostego zagadnienia?

  1. Ustaw <img> z jakimś stylem, który chcesz wanimować (np opacity:0, height:0, scale(0), etc.) i bez src, a prawdziwy link wstaw np do <img date-src
  2. Zapnij scroll handler, i złap nim moment w którym user wscrolluje <img> w viewport
  3. Odczytaj data-src i ustaw taki src, zapnij też event handler "on load" w <img>
  4. Kiedy obrazek się wczyta, ustaw doanimowany styl, np opacity:1, height:100px lub scale(1). Możesz też po prostu nadać klasę loaded, i zapnij odpowiedni styl w CSS.
  5. Popraw scroll handler, tak żeby nie próbował animować obrazków które już mają ustawiony src.
kosmonauta80 napisał(a):

[...] Można to osiągnąć czystym JS/CSS, czy potrzebne są jakieś biblioteki typu Bootstrap?

Na 99.99999% pytań w stylu "Można to osiągnąć czystym X, czy potrzebne są biblioteki typu Y", odpowiedź brzmi "Tak". Siłą rzeczy, Boostrap też jest napisany w JS i CSS.

3

Tutaj dokładnie opisałem jak wstawić obrazek na stronę WWW:

A tutaj sam mechanizm lazy-load:

z przykładami i kolorowymi obrazkami...

@ehhhhh: dlaczego uważasz, że Czysty HTML/CSS/JS to jak napisałeś: dvpa a nie spa?
Żeby zrobić stronę/aplikację SPA wystarczy kilkanaście linijek kodu w JS i odpowiednie serwowanie danych z serwera. To załatwia sprawę.

0

Myślicie, że https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect będzie ok do wykrycia, czy obrazek lub jego fragment jest znajduje się w viewport?
Bo wychodzi na to, że tę metodę trzeba będzie wywoływać każdorazowo przy scrolowaniu, aż każdy obrazek będzie widoczny.

0

Tak. Pytanie jednak w czym to jest lepsze/gorsze od implementacji w JS.

1
kosmonauta80 napisał(a):

Myślicie, że https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect będzie ok do wykrycia, czy obrazek lub jego fragment jest znajduje się w viewport?
Bo wychodzi na to, że tę metodę trzeba będzie wywoływać każdorazowo przy scrolowaniu, aż każdy obrazek będzie widoczny.

Właśnie dlatego żeby każdorazowo nic nie wywoływać lepiej użyć to: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Pojawienie się obrazka w viewport wcale nie musi być skutkiem scroll. To tylko jedna z możliwości.

0

Spróbuję zaimplementować obie metody. Dobre ćwiczenie wbrew pozorom.

0

Udało mi się uzyskać fajny efekt wykorzystując .getBoundingClientRect(). Poniżej kod źródłowy. Mam nadzieję, że nie budzi on zastrzeżeń (nazwa funkcji, spacje w kodzie html itp.).

<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel = "stylesheet" href = "lazy.css">
    <script src = "lazy.js" defer></script>
    <title>getBoundingClientRect()</title>
</head>
<body>
    <div id="text">
        ...
    </div>
    <div id="gallery">
        <img class="lazyImageHidden" alt="blue" date-src="images/blue.jpg">
        <img class="lazyImageHidden" alt="yellow" date-src="images/yellow.jpg">
        <img class="lazyImageHidden" alt="red" date-src="images/red.jpg">
        <img class="lazyImageHidden" alt="green" date-src="images/green.jpg">
        <img class="lazyImageHidden" alt="lemon" date-src="images/lemon.jpg">
        <img class="lazyImageHidden" alt="brown" date-src="images/brown.jpg">
        <img class="lazyImageHidden" alt="grey" date-src="images/grey.jpg">
        <img class="lazyImageHidden" alt="orange" date-src="images/orange.jpg">
        <img class="lazyImageHidden" alt="violet" date-src="images/violet.jpg">
    </div>  
</body>
</html>
#text
{
    margin: auto;
    margin-bottom: 100px;
    text-align: justify;
    width: 80%;
}
#gallery
{
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    margin: auto;
    width: 80%;
}
.lazyImageShown
{
    height: 300px;
    margin: 50px;
    opacity: 1;
    transition: opacity 0.75s ease-in-out;
    width: 300px;
}
.lazyImageHidden
{
    margin: 50px;
    opacity: 0;
    height: 300px;
    width: 300px;
}
window.addEventListener('scroll', () => 
{
    let images = document.getElementsByClassName("lazyImageHidden");
    [...images].forEach((el) =>
    {
        if (isInViewport(el))
        {
            showImage(el);
        }
    });
});

function isInViewport(el)
{
    return (el.getBoundingClientRect().bottom < window.innerHeight)
}

function showImage(el)
{
    let path = el.getAttribute('date-src');
    if (!el.hasAttribute("src"))
    {
        el.setAttribute("src", path);
        el.setAttribute("class", "lazyImageShown");
    }
}

Teraz spróbuję to samo przy pomocy Intersection Observer API. A jeżeli chodzi o loading="lazy", to również działa, testowałem na różnych komputerach.

0
katakrowa napisał(a):
kosmonauta80 napisał(a):

Myślicie, że https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect będzie ok do wykrycia, czy obrazek lub jego fragment jest znajduje się w viewport?
Bo wychodzi na to, że tę metodę trzeba będzie wywoływać każdorazowo przy scrolowaniu, aż każdy obrazek będzie widoczny.

Właśnie dlatego żeby każdorazowo nic nie wywoływać lepiej użyć to: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Pojawienie się obrazka w viewport wcale nie musi być skutkiem scroll. To tylko jedna z możliwości.

Oto kod z tego manuala:

let callback = (entries, observer) => {
  entries.forEach((entry) => {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

Po co przekazywać do callback argument observer? Bez niego i tak to działa.

0

Ale jaja. Przeczytałem manual, a następnie zmodyfikowałem kod @katakrowa dodając swoją funkcję na zasadzie "wklejam na pałę, może zadziała" i... Działa! Całość wkleję jak ogarnę swój kod, bo na razie pod względem czytelności to typowe spagetti. A poniżej moja "uniwersalna" funkcja.

function showImage(el)
{
    let path = el.getAttribute('date-src');
    if (!el.hasAttribute("src"))
    {
        el.setAttribute("src", path);
        el.setAttribute("class", "lazyImageShown");
    }
}
0

Oto kod w wersji "laboratoryjnej", czyli do eksperymentowania:

window.addEventListener('load', () => 
{
    let images = document.getElementsByClassName("lazyImageHidden");

    let options = 
    {
      root: null,
      rootMargin: '0px',
      threshold: 0.75
    }

    let callback = (entries) => 
    {
      entries.forEach((entry) => 
      {
        if  (entry.isIntersecting)
        {
          showImage(entry.target);
        }
      });
    };

    let observer = new IntersectionObserver(callback, options);

    [...images].forEach(image => observer.observe(image));
});

function showImage(el)
{
  let path = el.getAttribute('date-src');
  if (!el.hasAttribute("src"))
  {
    el.setAttribute("src", path);
    el.setAttribute("class", "lazyImageShown");
  }
}

Ciekawe jest to, że teoretycznie nie ma potrzeby używania observer.unobserve(), bo kolekcja images sama się zeruje?

Chwilę po załadowaniu strony (obrazki nie są widoczne):
screenshot-20220906135932.png

Chwilę po przescrollowaniu strony (kilka obrazków się pojawiło):
screenshot-20220906140046.png

getBoundingClientRect()

screenshot-20220906141349.png

Observer API
screenshot-20220906141628.png

0

Zauważyłem ciekawą różnicę pomiędzy getBoundingClientRect(), a Observer API. Ta pierwsza działa liniowo, a druga wielowątkowo. Czyli jak tylko 3 obrazki pojawią się w viewport, to albo pojawią się jeden za drugim, albo jednocześnie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1