Как ускорить загрузку изображений, используя встроенный предварительный просмотр

Чаще всего ленивая загрузка используется для вывода версий картинки для предварительного просмотра. При этом сначала создается некачественное изображение для предварительного просмотра, которое далее будет заменено исходным изображением. Но можно ли предоставить изображение для предварительного просмотра, не загружая дополнительные данные?

Формат progressive JPEG может сохранять информацию таким образом, чтобы сначала отображалось нечеткое, а далее более качественное картинки.

Описанный в данной статье метод загружает размытое изображение быстро, которое постепенно становится все более резким(прогрессивный метод).

Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Базовый метод


Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Прогрессивный метод

Но даже progressive JPEG-файлы загружаются один за иным из-за того, что современные браузеры допускают только шесть одновременных подключений к домену.

Рассматриваемый метод может загружать с сервера столько байтов progressive JPEG, сколько необходимо для быстрого выведения содержимого. Далее(когда все картинки для предварительного просмотра загружены), остальная часть картинки должна быть загружена без запроса той части, которая была получена ранее.

Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Загрузка progressive JPEG с двумя запросами

Но нельзя указать тегу img, какая часть картинки должно быть загружено в определенное время. Для этого необходимо использовать Ajax. Но только на серверах, на которых хранятся картинки, поддерживающих запросы HTTP Range. С их помощью покупатель может сообщить серверу в заголовке HTTP, какие байты запрашиваемого файла должны содержаться в ответе.

Данная технология поддерживается всеми популярными серверами(Apache, IIS, nginx). Она используется для загрузки видео при воспроизведении. При помощи HTTP Range сервер запрашивает только ту часть видео, которую запросил посетитель.

Но даже использование HTTP Range не решает следующие проблемы:

  1. Создание прогрессивного JPEG.
  2. Определение размера, до которого первый запрос HTTP Range должен загрузить изображение для предварительного просмотра.
  3. Реализация кода JavaScript.

1. Создание progressive JPEG

Progressive JPEG состоит из нескольких сегментов, каждый из которых включает часть конечного картинки. Первая загрузка показывает изображение нечетко. Но следующие добавляют все больше информации к загруженным данным и в конце формируют окончательное(полновесное) изображение.

Как выглядят отдельные сканы картинки, определяется программой, которая генерирует файлы JPEG. В программах CMD, таких как cjpeg из проекта mozjpeg, можно легко определить, какие данные содержат данные сканирования.

В файле wizard.txt проекта mozjpeg описаны точные настройки, которые надо передать программе в скрипте сканирования. Это удачный компромисс между быстрой прогрессивной структурой и размером файла.

Чтобы преобразовать исходный JPEG в progressive JPEG, воспользуемся jpegtran из проекта mozjpeg. Это инструмент для внесения изменений в существующий JPEG без каких-либо потерь. Предварительно скомпилированные сборки для Windows и Linux доступны здесь.

Создадим progressive JPEG из командной строки:

$ jpegtran input.jpg > progressive.jpg

При этом данные картинки не будут модифицированы. Изменяется только расположение данных картинки в файле.

Метаданные, не относящиеся к внешнему виду картинки(такие как данные Exif, IPTC или XMP), должны быть удалены из JPEG. При помощи утилиты exiftool, работающей из командной строки, можно легко удалить данные метаданные:

$ exiftool -all= progressive.jpg

Также можно без проблем воспользоваться онлайн-сервисом сжатия compress-or-die.com, чтобы создать progressive JPEG без метаданных.

2. Определение размера, до которого первый запрос HTTP Range должен загрузить изображение

Файл JPEG разделен на различные сегменты. Они содержат различные компоненты(данные картинки, метаданные, встроенные цветовые профили и т. д.). Каждый из данных сегментов начинается с маркера, заданного шестнадцатеричным байтом FF. Далее следует байт, указывающий тип сегмента. Например, D8 завершает маркер до маркера SOI FF D8(Start Of Image), с которого начинается файл JPEG.

Каждый запуск сканирования отмечен маркером SOS(Start Of Scan, шестнадцатеричный FF DA). Данные, следующие за маркером SOS, шифруются при помощи кодирования Хаффмана.

Но существует ещё один сегмент с таблицами Хаффмана(DHT, шестнадцатеричный FF C4), необходимый для декодирования перед сегментом SOS. Так что интересующая нас область progressive JPEG состоит из чередующихся таблиц Хаффмана(сегментов данных сканирования).

Чтобы отобразить первый скан картинки, надо запросить с сервера все байты до второго вхождения сегмента DHT(шестнадцатеричный FF C4).

Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Структура файла JPEG

В PHP для этого можно использовать приведенный ниже код:

<?php$img = "progressive.jpg";$jpgdata = file_get_contents($img);$positions = [];$offset = 0;while($pos = strpos($jpgdata, "xFFxC4", $offset)) {$positions[] = $pos+2;$offset = $pos+2;}

Необходимо добавить два к найденной позиции. Так как браузер выводит только последнюю строку картинки предварительного просмотра, когда встречает новый маркер(который состоит из двух байтов).

Нас интересует первый скан картинки предварительного просмотра. Для этого мы находим правильную позицию в $position[1], до которой необходимо запросить файл через HTTP Range.

Чтобы запросить изображение лучшего качества, используйте более дальнюю позицию в массиве. К примеру, $positions[3].

3. Написание кода JavaScript

Сначала определяем тег img, которому мы передаем заданную позицию байта:

<img data-src="progressive.jpg" data-bytes="<?= $positions[1]?>">

Мы не определяем атрибут src прямо, чтобы при парсинге HTML-кода браузер не сразу же начал запрашивать изображение с сервера.

Далее загружаем изображение предварительного просмотра, используя приведенный ниже JavaScript-код:

var $img = document.querySelector("img[data-src]");var URL = window.URL || window.webkitURL;var xhr = new XMLHttpRequest();xhr.onload = function(){ if(this.status === 206){ $img.src_part = this.response; $img.src = URL.createObjectURL(this.response); }}xhr.open('GET', $img.getAttribute('data-src'));xhr.setRequestHeader("Range", "bytes=0-" + $img.getAttribute('data-bytes'));xhr.responseType = 'blob';xhr.send();

Этот код создает запрос Ajax, который сообщает серверу в HTTP-заголовке range вернуть маркер файла в позицию, указанную в data-bytes. После чего сервер вернет двоичный программный код картинки в ответе HTTP-206(частичное содержимое) в виде объекта blob. При помощи createObjectURL из него можно сгенерировать внутренний URL-адрес для браузера. Мы используем этот URL в виде значения для src тега img . Мы дополнительно сохраняем данные blob в объекте в свойстве src_part .

На вкладке Network в инструментах для разработчика, встроенных в браузер, видно, что мы загрузили не все изображение, а лишь его небольшую часть. Кроме этого загрузка URL-адреса в объекте blob-объекта должна выводиться размером в 0 байт.

Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Вкладка Network в инструментах разработчика.

Мы загрузили заголовок JPEG исходного файла, так что изображение для предварительного просмотра имеет в себя правильный размер. Благодаря данному можно не задавать высоту и ширину в теге img .

Альтернатива: загрузка картинки для предварительного просмотра через HTML

Для повышения производительности изображение можно без труда передать в виде URI-данных непосредственно в HTML-код. При этом картинки для предварительного просмотра доступны сразу же, и посетитель не замечает задержек при загрузке веб-страницы.

Сначала мы сгенерируем URI ресурса, который используем в теге img (как значение src) :

<?php…$fp = fopen($img, 'r');$data_uri = 'data:image/jpeg;base64,'. base64_encode(fread($fp, $positions[1]));fclose($fp);

URI созданных данных сейчас добавляется в тег img как src .

<img src="<?= $data_uri ?>" data-src="progressive.jpg" alt="">

Адаптируем JavaScript:

<script>var $img = document.querySelector("img[data-src]");var binary = atob($img.src.slice(23));var n = binary.length;var view = new Uint8Array(n);while(n--) { view[n] = binary.charCodeAt(n); }$img.src_part = new Blob([view], { type: 'image/jpeg' });$img.setAttribute('data-bytes', $img.src_part.size - 1);</script>

После этого создадим объект blob из URI данных. Мы получаем их из той части URI, которая не включает данных картинки: data:image/jpeg;base64 .

Далее расшифруем их при помощи команды atob . Чтобы создать большой blob-объект из двоичных строковых данных, нам необходимо перенести их в массив Uint8. Это гарантирует, что данные не будут обрабатываться как текст в кодировке UTF-8.

К коду предыдущей версии реализации добавляем атрибут data-bytes к тегу img . В нем указано смещение в байтах, из которого должна быть загружена вторая часть картинки.

На вкладке network в инструментах разработчика видно, что загрузка картинки для предварительного просмотра не создает дополнительного запроса. Но при этом размер файла HTML-страницы увеличился.

Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Вкладка network при загрузке предварительного картинки в виде данных URI.

Загружаем окончательное изображение

Сейчас загрузим оставшуюся часть файла картинки через две секунды.

setTimeout(function(){ var xhr = new XMLHttpRequest(); xhr.onload = function(){       if (this.status === 206){            var blob = new Blob([$img.src_part, this.response], { type: 'image/jpeg'} );            $img.src = URL.createObjectURL(blob);        }    }    xhr.open('GET', $img.getAttribute('data-src'));    xhr.setRequestHeader("Range", "bytes="+ (parseInt($img.getAttribute('data-bytes'), 10)+1) +'-');    xhr.responseType = 'blob';    xhr.send();}, 2000);

В этот раз в HTTP-заголовке Range мы указываем, что хотим запросить данные с конечной позиции картинки до конца файла. Ответ на первый запрос хранится в свойстве src_part объекта DOM.

Мы используем ответы от обоих запросов, чтобы создать новый blob-объект для каждого new Blob() , который включает данные всего картинки. URL-адрес blob-объекта используется как src объекта DOM. Сейчас изображение загружено в полном объеме.

Как ускорить загрузку картинок, используя встроенный предварительный просмотр

Вкладка network во время загрузки картинки целиком (31,7 КБ)

Прототип

Прототип примера реализации с разными настройками доступен здесь . Репозиторий GitHub доступен по данной ссылке .

Заключение

Используя представленный в этом руководстве способ, можно без проблем загружать различные картинки для предварительного просмотра из progressive JPEG при помощи Ajax и HTTP-запросов Range. При этом данные файла не удаляются, а используются повторно, чтобы отобразить изображение в полном объеме.

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

Для посетителей с медленным интернет-соединением можно загружать не весь JPEG, а пропустить последние два сканирования. В результате этого мы получим намного меньший размер данных.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *