Po dłuższej przerwie powracamy do serii Podstawy jQuery. Dzisiejszy artykuł poświęcimy praktycznemu zastosowaniu poznanej wiedzy.

Założenia projektu

Celem projektu będzie stworzenie skryptu, który pozwoli na utworzenie kolejki wczytywania obrazów na stronie. W internecie możemy znaleźć wiele rozmaitych stron zawierających jedynie memy czy inne obrazy. Częstym problemem takich stron jest powolne buforowanie obrazów, szczególnie gifów. Przedstawione tu rozwiązanie nie eliminuje problemu, jednak uzyskany efekt pozwoli poświęcić całe łącze użytkownika na wczytywanie każdego kolejnego obrazu, pozwalając na zachowanie płynności przeglądania.

Wstęp teoretyczny

Doskonale znana nam składnia znacznika img zakłada obecność atrybutu src. Jest to oczywiście adres internetowy obrazu, który zostanie umieszczony w miejscu znacznika. Domyślnym zachowaniem przeglądarki jest podczas ładowania strony sukcesywne zastępowanie znaczników obrazami. Każdy taki zabieg łączy się z wysłaniem żądania o dany zasób (obrazek) i jego pobranie. Przy ograniczeniach wynikających z łącza lub dużej ilości obrazów pojawia się efekt jednoczesnego, powolnego pobierania obrazów. Znając charakter tego zachowania opracujemy sposób jego eliminacji.

Rozwiązanie teoretyczne

Rozważając problem jednoczesnego wczytywania wielu obrazów mamy świadomość, że główną jego przyczyną jest wysłaniu wielu zapytań jednocześnie. Znając przyczynę, musimy odpowiedzieć sobie na jedno pytanie:

Jak wymusić na przeglądarce wczytywanie obrazów jeden po drugim?

Okazuje się, że wystarczy w kolejnych znacznikach atrybut src ustawiać jeden po drugim. W momencie zmiany tego atrybutu przeglądarka wykonuje zapytanie o zasób spod nowego adresu. Dzięki tej wiedzy powstaje szkic naszego rozwiązania:

  • Wyróżnijmy wszystkie obrazy specjalną klasą
  • W momencie tworzenia dokumentu nadajmy wszystkim obrazom wspólny, tymczasowy src
  • Po wczytaniu strony sukcesywnie podmieniajmy treść atrybutu na właściwą

Pozostaje zastosować odpowiednie techniczne rozwiązania.

Przystępujemy do pracy

W celu realizacji naszego problemu przygotowałem przykładowy dokument. Gotowa realizacja dostępna jest pod pod tym adresem. Kod html:

<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="utf-8">
    <title>Strona główna</title>
    <link rel="stylesheet" href="styl.css">
    <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script src="skrypty.js"></script>
  </head>
  <body>
    <h1>Kolejkowane buforowanie obrazów</h1>
    <a href="#" class="load_trigger">Wczytaj obrazy</a>
    <div id="images_holder"> 
        <img class="image_post_load" src="img/tmp.png" data-img-src="http://1.bp.blogspot.com/-dazqpbQnahc/UaxhFz6mwgI/AAAAAAAAGJQ/pVhtFcqEBiY/s640/Ideal-landscape.jpg">
        <img class="image_post_load" src="img/tmp.png" data-img-src="http://fc03.deviantart.net/fs70/f/2013/006/9/6/landscape___france_by_louis_photos-d5qoxxm.jpg">
        <img class="image_post_load" src="img/tmp.png" data-img-src="http://news.bbcimg.co.uk/media/images/68035000/jpg/_68035249_ac_0003724_burton.jpg">
        <img class="image_post_load" src="img/tmp.png" data-img-src="http://mw2.google.com/mw-panoramio/photos/medium/75822548.jpg">
    </div>
  </body>
</html>

Dla wygody testowania dokument posiada, poza zestawem obrazów, przycisk Wczytaj obrazy. Jak widać, pierwszy krok rozwiązania został zrealizowany przez zastosowanie klasy image_post_load. Każdy z obrazów przed wczytaniem strony posiada atrybut src ustawiony na obrazek tymczasowy co powoduje, że wczytanie strony przebiega szybko. Nie wymaga ono wykonywania serii zapytań o obrazy. Pozostaje realizacja kroku trzeciego, do czego - jak czytelnik z pewnością się domyśla - posłuży atrybut data-img-src.

Dla wygodniejszej analizy przyda się, aby nasz dokument posiadał bardziej uporządkowany styl. Z programistycznego punktu widzenia treść arkusza stylu nie ma znaczenia, więc nie będziemy go analizować. Arkusz zastosowany w przykładzie można zobaczyć pod tym adresem.

Najważniejszym z naszego punktu widzenia jest prosty skrypt jQuery

function loadImage(img){
    $img=$(img);
    $img.attr("src", $img.attr("data-img-src")).load(function(){
        $next = $img.next("img.image_post_load");
        if($next.size()){
            loadImage($next);
        }
    });
}

$(function(){
    $(document).on("click", "a.load_trigger", function(e){
        loadImage("img.image_post_load:first");
        e.preventDefault();
    });
});

Cały skrypt opiera się o rekurencyjną funkcję loadImage. Zanim jednak o niej, omówmy kod według kolejności jego wykonywania. Jak pamiętamy z początków serii od linii $(function(){ rozpoczyna się kod wykonywany w momencie załadowania dokumentu.

Tworzymy obsługę zdarzenia kliknięcia przycisku a.load_trigger, która polegać będzie jedynie na wywołaniu naszej głównej funkcji i powstrzymaniu przeglądarki przed podążaniem za linkiem.

Sedno sprawy

Funkcja loadImage jako argument przyjmuje tekst, będący selektorem pierwszego z obrazów, których wczytanie kolejkujemy. Z tego powodu wywołanie funkcji zawiera selektor :first w argumencie. Funkcja w swojej pierwszej linii interpretuje selektor jako obiekt w dokumencie. Dzięki temu bieżący obrazek jest reprezentowany jako obiekt jQuery pod referencją $img. W drugiej linii następuje realizacja trzeciego punktu rozwiązania:

Po wczytaniu strony sukcesywnie podmieniajmy treść atrybutu na właściwą

Interpretując od najbardziej zagnieżdżonego wywołania:

  • $img.attr("data-img-src") reprezentuje docelowy adres, czyli adres obrazka który faktycznie ma zostać wczytany
  • $img.attr("src", WARTOŚĆ) ustawia wartość atrybutu src na zadaną - jak pokazano wyżej - adres docelowy

Funkcja nie może jednak skończyć swojego działania na tym etapie . Musimy zadbać, żeby wczytane zostały wszystkie obrazy. W tym celu w dalszej części kodu wywołamy loadImage($next);. Dlaczego jednak nie bezpośrednio w trzeciej linijce funkcji? Powód jest prosty - wczytanie następnego obrazka od razu nie da zamierzonego efektu, ponieważ jQuery działa asynchronicznie. Mówiąc krótko - kolejna komenda nie czeka aż poprzednia skończy działanie.

Mając na uwadze tę właściwość frameworku musimy kazać skryptowi poczekać na pełne wczytanie obrazka. W tym celu obsługujemy zdarzenie load. Więcej o obsłudze zdarzeń pisałem już części drugiej.

Wewnątrz metody obsługującej zdarzenie wczytania obrazu skrypt lokalizuje kolejny element, który pasuje do naszej wyróżnionej klasy a pod warunkiem jego znalezienia ($next.size() będzie równe 0 gdy nie znaleziono kolejnego elementu) wywołuje funkcję rekurencyjnie.

Zakończenie

To proste analityczne rozumowanie pozwoliło nam pogłębić i wykorzystać w praktyce znajomość jQuery. Jeśli skrypt okaże się przydatny - nie krępuj się użyć go na swojej stronie.