Grafika na stronie

Niby nie Javascript, ale warto dodać. Jeżeli wrzucasz na stronę swoje zdjęcia, warto wcześniej pozbyć się informacji EXIF. Zawierają one informacje kiedy i gdzie zostało wykonane zdjęcie, więc dzięki nim łatwo ciebie namierzyć. I ja popełniłem w tym kursie taki błąd, aż jeden z użytkowników zwrócił mi na to uwagę (za co bardzo dziękuję). W Windowsie wystarczy kliknąć prawym klawiszem na zdjęcie i wybrać właściwości. W zakładce szczegóły na dole będzie link, który umożliwi ci usunięcie niepotrzebnych danych.
Możesz też skorzystać z licznych czyścicieli. Wystarczy w internecie poszukać frazy "exif remove".

Przypuśćmy, że mamy obrazek img na stronie:

Kartofel w wietnamie


    <img src="./wietnam.jpg" class="img" alt="Kartofel w wietnamie" width="400" height="400">

Żeby móc na nim pracować, pobierzmy go i zbadajmy w konsoli (zrób to teraz):


const img = document.querySelector(".img");
console.dir(img);
Widok części debugera po zbadaniu grafiki

Jak widzisz w konsoli debugera (mam nadzieję, że masz ją otwartą), dla tak pobranej grafiki możemy ustawiać mnóstwo właściwości i korzystać z wielu metod.

Wśród nich jednymi z najczęściej używanymi są:

width szerokość grafiki,
height wysokość grafiki,
alt alternatywny opis grafiki (widoczny gdy się nie wczyta),
title tekst, który pojawi się po najechaniu kursorem na element,
src adres do grafiki

const obr = document.querySelector(".img");
console.log(`Szerokość: ${img.width}, wysokość: ${img.height}, src: ${img.src}`);

No dobrze - w praktyce używa się tego co pasuje w danej sytuacji. Poniżej zajmiemy się częstymi przypadkami, które są spotykane przy pracy z grafiką w js.

Konstruktor Image()

W Javascript obrazki możemy tworzyć na kilka sposobów. Można tutaj wykorzystać np. document.createElement("img"), ale też możemy skorzystać z konstruktora Image(). Przyjmuje on dwa opcjonalne parametry określające szerokość i wysokość.


const img = new Image();
img.src = "test.jpg";

//lub
const img = document.createElement("img");
img.src = "test.jpg";

Zdarzenie load

Obiekty wczytywane (np. window, image, iframe itp.) posiadają zdarzenie load, które wykrywa, czy dany obiekt został w pełni załadowany.

Dla obiektu window zdarzenie to oznacza wczytanie całego dokument dom oraz wszystkich grafik (w przeciwieństwie do DOMContentLoaded, które odpalane jest po wczytaniu drzewa DOM, ale przed wczytaniem grafik).
Dla grafiki oznacza to wczytanie grafiki:


img.addEventListener("load", e => {
    console.log("Dana grafika została załadowana");
});

img.src = "lorem.jpg"; //wpierw ustawiamy zdarzenie, potem ustawiamy src

Jeżeli w powyższym skrypcie zdarzenie load ustawilibyśmy po ustawieniu src, skrypt mógł by zadziałać nieprawidłowo. Może zdarzyć się sytuacja, że obrazek wczyta się natychmiastowo po ustawieniu src (np. siedzi już w cache przeglądarki). Oznacza to, że obrazek wczyta się przed odpaleniem nasłuchiwania eventu load.

Są jednak sytuacje (szczególnie przy bardziej skomplikowanych skryptach), że nie jesteśmy w stanie zagwarantować takiej kolejności. Przykładowo system podpina wszystkie eventy, a gdzieś dalej dopiero budowane są src grafik w systemie. W takim przypadku warto sprawdzić właściwość complete każdej grafiki, która wskazuje czy dany obrazek jest wczytany czy nie. Jeżeli tak się stanie odpalimy dla niej zdarzenie load.


img.src = "lorem.jpg";

img.addEventListener("load", e => {
    console.log("Dana grafika została załadowana");
});

if (img.complete) {
    //odpalam ręcznie event load
    img.dispatchEvent(new Event("load"));
}

Postęp wczytywania grafik na stronie

Wyobraź sobie, że naszym zadaniem będzie zrobienie paska ładowania grafik użytych na stronie.

Aby to zrealizować, zacznijmy od przygotowania elementu z paskiem postępu:


.loading-bar {
    margin: 30px 0;
    height: 10px;
    border-radius: 10px;
    background: #eee;
    display: flex;
    overflow:hidden;
}

.loading-bar__progress {
    background: tomato;
}

<div id="loading-bar">
    <div class="loading-bar__progress"></div>
</div>

Sam skrypt będzie raczej prosty. Tworzymy tablicę ze ścieżkami do grafik oraz pustą tablicę, do której będziemy wrzucać elementy Images(), które będą miały ustawione src takie jak poszczególne elementy w tablicy.


//tablica z nazwami obrazków do załadowania
const imgNames = [
    "obrazek1.gif",
    "obrazek2.gif",
    "obrazek3.gif",
    "obrazek4.gif",
    "obrazek5.gif",
    "obrazek6.gif"
];

const loadingStep = (100 / imgNames.length); //szerokość oznaczająca % paska po załadowaniu 1 obrazka

const images = []; //tablica będzie zawierała obiekty Image

const loading = document.querySelector(".loading");
const loadingBar = loading.querySelector(".loading-progress");

//funkcja rozpoczynająca ładowanie obrazków
function startLoading(cb) {
    imgNames.forEach(name => {
        const img = new Image();
        //po wczytaniu grafiki wrzucam nowe Image() do tablicy images
        //sprawdzam ile elementów ma ta tablica i na tej podstawie ustawiam szerokość paska postępu

        img.addEventListener("load", e => {
            images.push(img);
            loadingBar.style.width = `${images.length * loadingStep}%`; //zmieniamy szerokość paska w %

            if (images.length >= imgNames.length) {
                cb(); //odpalam jakąś funkcję po zakończeniu wczytywania
            }
        });
        img.src = name;

        if (img.complete) {
            img.dispatchEvent(new Event("load"));
        }
    });
}

startLoading(function() {
    alert("Zakończono wczytywanie")
});

Powyższy przykład możesz zobaczyć w działaniu tutaj.

Wczytywanie grafiki i obietnice

Wczytywanie to proces asynchroniczny, więc jest to idealne miejsce by zastosować obietnice lub async/await.

Napiszmy prostą funkcję, która zwróci nam obietnicę, który zostanie rozwiązany gdy grafika zostanie wczytana. W przeciwnym razie zostanie zwrócony błąd. Tak naprawdę są to powyżej zebrane informacje okryte za pomocą Promise:


function loadImage(name, src) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        //zwracamy obiekt, który będzie miał 2 klucze:
        //name - nazwa którą będziemy podawać przy odwoływaniu się do tej grafiki
        //img - właściwy element img, który będzie zwracany
        img.addEventListener("load",  () => resolve({name : name, img : img}));
        img.addEventListener("error", e => reject(e));
        img.src = src;

        if (img.complete) {
            const event = new Event("load");
            img.dispatchEvent(event);
        }
    });
}

Wykorzystajmy ją w funkcji wczytującej kilka grafik na raz:


const graphicCollection = [
    {name : "hero" , src : "graphic-to-load/aniolek.jpg"},
    {name : "icon" , src : "graphic-to-load/aniolek2.jpg"},
    {name : "cats" , src : "graphic-to-load/book.jpg"},
    {name : "pies" , src : "graphic-to-load/bum.jpg"}
];

const loadImages = function(sources) {
    const promises = sources.map(img => loadImage(img.name, img.src));
    return Promise.all(promises);
}

Od tej pory oczekiwanie na wczytanie wszystkich grafik będzie bardzo proste:


loadImages(graphicCollection).then(data => {
    //kluczami będą nazwy grafik - np. grafika1, grafika-moja2, grafika-inna3
    data.forEach(img => {
        images.set(img.name, img.img);
    });

    console.log("hero -> ", images.get("hero"));
    console.log("icon -> ", images.get("icon"));
    console.log("cats -> ", images.get("cats"));
    console.log("pies -> ", images.get("pies"));
});

Lazy loading

Lazy loading to metoda wczytywania obrazków polegająca na tym, że dany obrazek wczytywany jest dopiero w momencie gdy pojawi się na ekranie.

W nowych przeglądarkach wczytywanie takie możemy uzyskać za pomocą dodatkowego atrybutu lazy dla elementów img:


<img src="image.png" loading="lazy" alt="...">
<iframe src="https://example.com" loading="lazy"></iframe>

Atrybut ten może przyjąć jedną z wartości:

auto domyślna wartość dla leniwego wczytywania. Równa temu, jakbyśmy w ogóle tego atrybutu nie używali.
lazy opóźnione wczytywanie grafiki gdy osiągnie ona wskazany dystans od ekranu (zależy od typu zasobów, szybkości łącza itp.)
eager zdjęcie ma być wczytywane natychmiast po wejściu na stronę

W przypadku, gdy dana przeglądarka nie obsługuje powyższego atrybutu, możemy skorzystać z kilku metod naprawy tego stanu.

Pierwszą z nich jest użycie tak zwane IntersectObserver, który nasłuchuje, czy dane elementy znalazły się w zadanym obszarze.

Drugą jest użycie jednego z eventów resize, scroll i orientationchange.

Nie będę tutaj duplikował czyjegoś kodu i zwyczajnie odeślę was pod ten adres, gdzie został pokazany stosowny kod.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem. Aha - i ta strona korzysta z ciasteczek.

Menu