ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Categories:

JavaScript: предзагрузка изображений

Решил задачу «Загрузите изображения с колбэком» к подразделу 5.3 «Загрузка ресурсов: onload и onerror» второй части учебника по JavaScript.

Задача несложная, но сразу в нее сложно въехать, нужно сначала разобраться с тестовой HTML-страницей, которая прилагается к задаче (ее код можно посмотреть в песочнице).

От нас требуется написать функцию preloadImages(sources, callback). Первый параметр sources — это массив строк. Каждая строка в этом массиве является адресом изображения. Второй параметр callback — это функция, которую требуется запустить после того, как все изображения, адреса которых указаны в массиве sources, будут загружены браузером.

Для чего может понадобиться такая функция? Предположим, у нас есть много картинок, которые будут вставляться на HTML-страницу с помощью HTML-элемента img по мере прокрутки (скроллирования) HTML-страницы, например, вниз. Каждый HTML-элемент img будет вставляться на HTML-страницу быстро, а вот загрузка изображения не будет происходить мгновенно. Чтобы избавиться от этого «торможения», можно «предзагрузить» картинки, а при вставке HTML-элемента img пользователь не будет ждать загрузки, картинка будет показана мгновенно. Предзагрузка следующих картинок может происходить в то время, пока пользователь разглядывает первые картинки в галерее на HTML-странице.

Первый вопрос, который у меня возник: где хранятся предзагруженные браузером картинки? Оказалось (об этом сказано и в текущем подразделе учебника, и в тексте постановки задачи), они хранятся в кэше браузера. То есть наша функция должна будет «предзагрузить» картинки в кэш браузера.

Второй вопрос: как в скрипте на языке JavaScript загрузить картинку в кэш? В тексте постановки задачи это показано на примере:
let img = document.createElement("img");
img.src = "my.jpg";

Именно (и исключительно) для HTML-элемента img загрузка картинки в кэш браузера начинается после того, как мы помещаем адрес картинки в свойство src. (Об этом сказано и в текущем подразделе учебника, и в тексте постановки задачи.) Для других HTML-элементов загрузка ресурса начинается только после добавления HTML-элемента на HTML-страницу.

Третий вопрос: как работает тест для нашей будущей функции, подготовленный авторами задачи на тестовой HTML-странице?

1) Сначала создается массив sources адресов картинок (в этом тесте картинок всего три, но может быть любое другое количество):
let sources = [
    "https://en.js.cx/images-load/1.jpg",
    "https://en.js.cx/images-load/2.jpg",
    "https://en.js.cx/images-load/3.jpg"
];
Конкретно эти три картинки действительно существуют по указанным адресам, я проверил.

2) Затем каждый из этих адресов модифицируется так, чтобы при каждом запуске теста адрес формально получался новый (с точки зрения кэша), но с точки зрения загрузки изображения браузером оставался тем же самым. Я уже рассматривал этот прием ранее в отдельном посте. Этот прием нужен для предотвращения кэширования файлов картинок при первой загрузке тестовой HTML-страницы браузером (это помешало бы тестированию нашей будущей функции).
for (let i = 0; i < sources.length; i++) {
    sources[i] += "?" + Math.random();
}

3) И, наконец, в качестве функции callback для нашей будущей функции создается функция testLoaded, которая моделирует создание HTML-элементов img на некой HTML-странице, для которых требуется предзагрузка изображений, обеспечиваемая нашей будущей функцией.
function testLoaded() {
    let widthSum = 0;
    for (let i = 0; i < sources.length; i++) {
        let img = document.createElement("img");
        img.src = sources[i];
        widthSum += img.width;
    }
    alert(widthSum);
}

4) Производится запуск нашей будущей функции с созданными выше параметрами:
preloadImages(sources, testLoaded);

Как работает этот тест? Если наша будущая функция выполнит предзагрузку в кэш указанных трех картинок правильно, то при моделировании использования этих картинок в вышеописанном третьем пункте тестовый скрипт сможет получить правильную ненулевую ширину каждой картинки (если картинка существует по указанному адресу). Ширина каждой картинки составляет 100 пикселей. Таким образом, при правильной работе нашей будущей функции тестовый скрипт выведет на экран сообщение (с помощью функции alert) с числом 300 (сумма ширин всех трех картинок).

Если же какая-либо одна, или какие-либо две, или все три картинки из заданных трех не будут предзагружены, тестовый скрипт выведет на экран соответственно либо число 200, либо число 100, либо число 0.

* * *

Приступим к решению задачи. Сначала напишем заготовку для функции preloadImages и просто запустим из нее функцию callback, заданную вторым параметром. Посмотрим, что произойдет. Пишем код:
function preloadImages(sources, callback) {
    callback();
}

Эту заготовку вставляем в начало скрипта тестовой HTML-страницы. При загрузке этой HTML-страницы тест выдаёт число 0. Так и должно быть. Ведь мы пока не выполнили никакой предзагрузки изображений.

Далее я решил попробовать предзагрузить первое из трех заданных изображений. Меняем код:
function preloadImages(sources, callback) {
    let img = document.createElement("img");
    img.src = sources[0];

    callback();
}

Однако, тест снова выдал число 0, то есть все три изображения не были предзагружены. Почему не получилось предзагрузить первое из трех изображений? Дело в том, что тестирование (запуск функции callback) в этом коде начинается сразу после запуска предзагрузки первого изображения. Таким образом, первое изображение просто не успевает предзагрузиться.

Очевидно, что именно в этом месте следует начать использовать знания, полученные в текущем подразделе обсуждаемого учебника: нам нужно запустить функцию callback после полной предзагрузки изображения. Меняем код:
function preloadImages(sources, callback) {
    let img = document.createElement("img");
    img.src = sources[0];

    img.addEventListener("load", callback);
}

Для этой редакции функции preloadImages тест уже выдаёт число 100. Это значит, что одно из трех заданных изображений было предзагружено удачно. Теперь следует масштабировать код на все заданные изображения, то есть в данном случае нужен цикл. Меняем код:
function preloadImages(sources, callback) {
    let imgs = [],   // массив HTML-элементов img для предзагрузки картинок
        loaded = []; // массив HTML-элементов img с загруженной картинкой
    
    // цикл выполнения предзагрузки заданных картинок
    for (let i = 0; i < sources.length; i++) {

        // запуск предзагрузки очередной картинки
        let img = document.createElement("img");
        img.src = sources[i];

        // после окончания предзагрузки картинки поместить ее в массив загруженных,
        // и проверить, если это была последняя из заданных картинок, то запустить
        // функцию callback
        img.addEventListener("load", function () {
            loaded.push(this);
            if (loaded.length == sources.length) callback();
        });

        imgs.push(img);
    }
}

При такой редакции кода тест выдаёт требуемое число 300. То есть все три заданные изображения были удачно предзагружены.

Однако, по условиям задачи требуется учесть еще случай, когда одну (или несколько) из заданных картинок не удастся предзагрузить из-за какой-либо ошибки при попытке предзагрузки. Например, если какая-либо картинка из заданных будет отсутствовать по указанному адресу.

Я решил дополнить тестовые данные и добавил в массив sources четвертый адрес картинки. Я специально взял адрес, по которому указанная картинка отсутствует:
let sources = [
    "https://en.js.cx/images-load/1.jpg",
    "https://en.js.cx/images-load/2.jpg",
    "https://en.js.cx/images-load/3.jpg",
    "https://en.js.cx/images-load/4.jpg"  // этой картинки не существует
];

После запуска нашего кода с этими тестовыми данными тестовая HTML-страница не выдала вообще никакого числа. Почему это произошло? Наш код попытался предзагрузить четвертое изображение и получил ошибку, которую можно рассмотреть в консоли разработчика в браузере (ошибка 404). После получения этой ошибки браузер прервал работу нашего скрипта, поэтому в итоге тест не выдал вообще никакого числа. Но наша задача — заставить скрипт работать даже при таких ошибках. Чтобы это сделать, используем событие error. Меняем код:
function preloadImages(sources, callback) {
    let imgs = [],   // массив HTML-элементов img для предзагрузки картинок
        loaded = []; // массив HTML-элементов img с загруженной картинкой
    
    // цикл выполнения предзагрузки заданных картинок
    for (let i = 0; i < sources.length; i++) {

        // запуск предзагрузки очередной картинки
        let img = document.createElement("img");
        img.src = sources[i];

        // после окончания предзагрузки картинки поместить ее в массив загруженных,
        // и проверить, если это была последняя из заданных картинок, то запустить
        // функцию callback
        img.addEventListener("load", onLoad);
        img.addEventListener("error", onLoad);
        function onLoad() {
            loaded.push(this);
            if (loaded.length == sources.length) callback();
        }

        imgs.push(img);
    }
}

При такой редакции кода и вышеуказанных тестовых данных (заданы четыре картинки, одна из которых не существует) тест выдаёт число 300. Так и должно быть: три картинки, которые существуют, были предзагружены, а четвертая картинка, которая не существует, естественно, не была предзагружена, так как и не могла быть предзагружена изначально, при всём желании. Зато тест сработал, несмотря на ошибку, случившуюся при попытке предзагрузить одну из картинок. Теперь наш код не «сломался», хоть и произошла ошибка.

Сообщение об ошибке, кстати, как и в предыдущий раз, можно посмотреть в консоли разработчика в браузере. Этих ошибок теперь будет две одинаковых: одна при попытке предзагрузки несуществующего изображения, а вторая — при попытке загрузки несуществующего изображения (загрузка изображений, ведь, моделируется в тесте).

Этот код является решением задачи, но он меня не устраивает по нескольким причинам, которые я опишу в следующем посте.
Tags: Образование, Программирование
Subscribe

  • Юмор на ютубе, появление шоу TALK

    У меня в принципе нет в квартире телевизора. Я, в общем-то, не считаю телезрителей быдлом, а телевизор — устройством для промывки мозгов. Просто в…

  • Любимый женский кавер песни «My Way»

    Голландский «Голос» вообще один из моих самых любимых. Удивительно, но почему-то именно эта маленькая страна дала этому шоу очень много понравившихся…

  • «Oh! Darling» против «Imagine»

    Не понимаю, что люди находят в песне «Imagine» Леннона. Ну да, мелодичная. Но по мне слишком спокойная и чересчур сладенькая. Вот «Oh! Darling»…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments