Images czyli grafika na stronie

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, 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śc 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('Szerokosc: ' + img.width + ', wysokosc: ' + img.height + ', src:' + img.src);

No dobrze - w praktyce używa się tego co pasuje w danej sytuacji...

Efekt rollover

Do efektu rollover powinien być stosowany zwykły CSS (:hover).
Jeżeli nie masz powodu by wykorzystywać do tego JS - nie rób tego.

Aby podmienić grafikę na inną musimy zmienić atrybut src danej grafiki:


const obr = document.getElementById('obrazek');

img.addEventListener('mouseover', function() {
    this.src = 'obrazek_2.jpg';
});

img.addEventListener('mouseout', function() {
    this.src = 'obrazek_1.jpg';
});

Efekt rollower z wcześniejszym załadowaniem obrazków

Powyższa metoda ma poważny błąd. Nowa grafika zostaje ściągana dopiero, gdy wskażemy nasz obrazek kursorem. Przy wolnych łączach lub dużych grafikach spowoduje to brak płynności.
Aby temu zapobiec musimy wszystkie grafiki biorące udział w efekcie rollover załadować do cache przeglądarki (czyli je wcześniej pobrać). Wówczas, w chwili wskazania obrazka, skrypt szybko podmieni jeden obrazek na drugi.

Aby załadować grafiki do cache możemy skorzystać z konstruktora Image, który przy tworzeniu nowego obiektu wymaga podania szerokości oraz wysokości nowego obrazka. Podstawowa deklaracja tego typu obiektu ma postać:


const newImage = new Image(200, 300); //tworzymy nowy obiekt typu Image
newImage.src = "obrazek_on.jpg" //podajemy jego src

document.querySelector('body').appendChild(newImage);

Drugi sposób już poznałeś - to stworzenie nowego elementu na stronie - w tym przypadku img:


const newImage = document.createElement('img');
newImage.width = 200;
newImage.height = 300;
newImage.src = "obrazek_on.jpg" //podajemy jego src

document.querySelector('body').appendChild(newImage);

Gdy utworzymy już stosowne obiekty, możemy odwoływać się do ich właściwości src. Poniższy skrypt realizuje całe zadanie:


const imageOff = new Image();
imageOff.src = '/images/obrazek1.jpg';

const imageOn = new Image();
imageOn.src = '/images/obrazek2.jpg';


const img = document.querySelector('.pictureOnPage');

img.addEventListener('mouseover', function() {
    this.src = imageOn.src;
});
img.addEventListener('mouseout', function() {
    this.src = imageOff.src;
});

Oczywiście nie tylko zdarzeniem mouseover człowiek żyje.
Można przecież także i click wykorzystać:


<img src="/images/karto_1.jpg" width="334" height="231" alt="Kliknij!" title="Kliknij!" id="obrazekKarto" />
<span id="zastrzel_tekst">Zastrzel kartofla!</span>

const imageKill = new Image();
imageKill.src = 'karto_2.jpg';

const img = document.getElementById('obrazekKarto');
img.addEventListener('click', function() {
    this.src = imageKill.src;
    document.querySelector('#shootText').innerHTML = 'I Ty przeciwko Fantomasowi?'
});

Kliknij!
Zastrzel kartofla!

Efekt rollower dla większej ilości obrazków

Deklarowanie dla każdej grafiki obu stanów w podany powyżej sposób może być problematyczne - zwłasza dla większej liczby grafik. O wiele lepszym rozwiązaniem jest zastosowanie tablicy do przechowywania nazw obrazków, a następnie za pomocą pętli dynamiczne tworzenie obiektów Image, które będziemy trzymać w dodatkowej tablicy:


const names = [
    'obrazek.jpg',
    'kartofelek.jpg',
    'piesek.jpg',
    'kotek.jpg',
    'czekolada.jpg'
];
const images = [];

for (let x=0; x<names.length; x++) {
    const images[x] = new Image();
    images[x].src = names[x];
}

Pasek wczytywania

Obiekty takie jak image czy window 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).
Dla grafiki oznacza to wczytanie grafiki:


img.addEventListener('load', function() {
    console.log('Dana grafika została załadowana');
});

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

Proste prawda? Nie do końca. Okazuje się, że jeżeli dany element ma już wcześniej ustawione src (np. jest zdefiniowany w dokumencie html), a obrazek został wcześniej już wczytany i trafił do cache przeglądarki (miejsca, gdzie przeglądarka przetrzymuje już wcześniej wczytane rzeczy), zdarzenie to nie zostanie odpalone.
Na szczęście js udostępnia właściwość complete, która mówi nam, czy dana grafika została załadowana:


const img = document.getElementById('someImageOnPage');
    img.addEventListener('load', function() {
    console.log('Dana grafika została załadowana');
});

if (img.complete) {
    img.dispatchEvent('load'); //odpalamy event click
}

Do odpalenia eventu użyliśmy metody dispatchEvent, która odpala dany event (np. symuluje kliknięcie na element gdy użyjemy dispatchEvent('click'))

Pasek wczytywania

Nazwy obrazków do załadowania podajemy w formie tablicy. Następnie wykonujemy pętlę po tej tablicy, tworząc obiekty Image z odpowiednim src. Dla każdego obiektu definiujemy zdarzenie load. Będzie ono wywoływało funkcję, która sprawdza ile obiektów zostało już załadowanych i odpowiednio ustawiało długość paska ładowania (w procentach). Aby móc sprawdzać ile obiektów zostało załadowanych, musimy posłużyć się dodatkową zmienną ile_zaladowano.


.loading-bg {
    margin:30px 0;
    width:600px;
    height:40px;
    background:#eee;
    border:1px solid #ddd;
    overflow:hidden;
}
.loading-bg .progress {
    width:0;
    height:100%;
    background:#EC185D;
    overflow:hidden
}

    <div id="progressCnt"></div>

(function() {
    //tablica z nazwami obrazków do załadowania
    const imgNames = [
        'obrazek1.gif',
        'obrazek2.gif',
        'obrazek3.gif',
        'obrazek4.gif',
        'obrazek5.gif',
        'obrazek6.gif'
    ];
    let howLoaded = 0; //ile obiektów Images już załadowano do pamięci
    const loadingStep = (100 / imgNames.length); //szerokość oznaczająca % paska po załadowaniu 1 obrazka
    const images = []; //tablica będzie zawierała obiekty Image
    const loadingBarBg = null; //zmienna pod którą utworzymy dynamicznie div zawierającego div-pasek postępu
    const loadingBar = null; //zmienna pod którą utworzymy dynamicznie div-pasek postępu
    const pageToRedirect = 'index.php'; //strona na którą przeniesie po zakończonym ładowaniu

    //funkcja odpalana dla każdego obiektu Image, które wcześniej stworzyliśmy.
    //Sprawdza ile obiektów zostało załadowanych i ustawia odpowiednią szerokość paska.
    function setLoadingBar() {
        howLoaded++;
        loadingBar.style.width = howLoaded * loadingStep + "%"; //zmianiamy szerokość paska (podaną w %)

        if (howLoaded >= imgNames.length) {
            setTimeout(function() {
                location.href = pageToRedirect; //po załadowaniu wszystkich grafik czekamy 2s i przenosimy na stronę
            }, 2000)
        }
    }

    //funkcja rozpoczynająca ładowanie obrazków
    function startLoading() {
        const div = document.querySelector('#progressCnt');

        loadingBarBg = document.createElement('div');
        loadingBarBg.className = 'loading-bg';    //dzięki temu skorzystamy ze zdefiniowanych styli

        loadingBar = document.createElement('div');
        loadingBar.className = 'progress';

        loadingBarBg.appendChild(loadingBar);

        div.appendChild(loadingBarBg);

        for (let i=0; x<imgNames.length; i++) { //pętla po nazwach obrazków...
            images[i] = new Image();
            images[i].addEventListener('load', setLoadingBar.bind(this));    //dla każdego obiektu ustawiamy zdarzenie onload
            images[i].src = imgNames[i];
            if (images[i].complete) {
                images[i].dispatchEvent('load');
            }
        }
    }

    document.addEventListener("DOMContentLoaded", function(event) {
        startLoading();
    });
});

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


Trening czyni mistrza

Poniżej zamieszczam kilka zadań, które w ramach ćwiczenia możesz wykonać:

  1. Utwórz jeden obiekt typu Image. Ustaw mu src do jakiejś grafiki. Następnie stwórz za pomocą JS div o rozmiarach np. 400x300, któremu jako tło ustawisz tą grafikę. Grafika tła powinna być dopasowana do wielkości diva.
    
                    const img = new Image();
                    img.src = "https://placeimg.com/640/480/animals?t=1514540372818";
    
                    const div = document.createElement('div');
                    div.style.setProperty('width', 400);
                    div.style.setProperty('height', 300);
                    div.style.setProperty('background-image', img.src);
                    div.style.setProperty('backgrouns-size', 'cover'); //lub contain jeżeli ma się całe mieścić w divie
    
                    document.querySelector('body').appendChild(div);
                
  2. Po kliknięciu na div, zmień mu grafikę na inną (którą pobierzesz z kolejnego Image)
    
                const img = new Image();
                img.src = "https://placeimg.com/640/480/animals?t=1514540372818";
    
                const img2 = new Image();
                img2.src = "https://placeimg.com/640/480/animals?t=1514540470097";
    
                const div = document.createElement('div');
                div.style.setProperty('width', 400);
                div.style.setProperty('height', 300);
                div.style.setProperty('background-image', img.src);
                div.style.setProperty('backgrouns-size', 'cover');
    
                document.querySelector('body').appendChild(div);
    
                div.addEventListener('click', function() {
                    this.style.setProperty('background-image', img2.src);
                });
            
  3. Dla chętnych. Na środku powyżej stworzonego postępu ładowania (w artykule) dodaj tekst, który będzie pokazywał liczbę wczytywanych obrazków w formacie "Wczytano 5 / 10"
  4. Pobierz sobie stronę zadanie-obrazki.html (prawym klawiszem myszy na link i zapisz jako). W kodzie strony masz kilka obrazków.

    Twoim celem jest:
    - pobrać z nich atrybut src i podstawić go pod atrybut data
    - dodać dla img klasę .loading
    - poczekać aż wczyta się grafika, wtedy usunąć .loading i przywrócić na nowo src z grafiką.

    Aby móc testować to zadanie w debugerze w zakładce Network na górze możesz sobie ustawić testową szybkość połączenia. Dodatkowo odświeżaj stronę poprzez Ctrl + Shift + R (odświeżenie z pominięciem cache).
  5. Dla chętnych. Powyższe zadanie zmodyfikuj tak, by grafiki zaczęły się ładować dopiero, gdy dany obrazek znajdzie się na ekranie. Możesz skorzystać z https://stackoverflow.com/questions/5353934/check-if-element-is-visible-on-screen