Изменение размеров изображений в режиме реального времени

Моментальное изменение размеров изображений в режиме реального времени

В качестве веб-архитектора одной из многих проблем является управление активами. И наиболее значительной проблемой с активами являются изображения. Наивным подходом будет установить изображение и позволить браузеру изменять размер изображения с помощью CSS:

Однако это означает, что вы загружаете оригинальное изображение. Это влечет две проблемы: размер оригинального изображения и субоптимальное изменение размера в браузере.

В этой статье будут рассмотрены два альтернативных решения: традиционные и совершенно новые.

Масштабирование наперед

Традиционным решением для одного источника изображения являлось масштабирование наперед. Перед выпуском дизайнеры тратят время на создание нескольких версий изображения разного разрешения. В этом блоге я использую эту технику. Я предоставляю три разрешения для отображения основного изображения записи в разных контекстах в качестве фоновых изображений:

  • Большое для публикации на ее странице
  • VoAGI для публикации на главной странице
  • Маленькое для связанных записей на странице записи

Я также удаляю метаданные JPEG для еще большего сокращения размера.

Тем не менее, традиционным подходом является использование тега HTML picture:

HTML-элемент <picture> содержит ноль или более элементов <source> и один элемент <img> для предложения альтернативных версий изображения для разных сценариев отображения/устройств.

Браузер рассмотрит каждый дочерний элемент <source> и выберет наилучшее соответствие среди них. Если совпадений не найдено или браузер не поддерживает элемент <picture>, выбирается URL атрибута src элемента <img>. Выбранное изображение затем отображается в месте, занимаемом элементом <img>.

Элемент Picture на веб-сайте документации MDN

В свою очередь, можно использовать его следующим образом:

Этот способ работал веками, но у него есть две проблемы. Во-первых, использование нескольких разрешений для каждого изображения занимает много времени. Можно автоматизировать процесс и получить хорошие результаты с помощью искусственного интеллекта.

Однако объем необходимого хранилища может быть в два или три раза больше, чем у оригинального изображения, в зависимости от количества созданных дополнительных разрешений. В среде, богатой активами, например, в электронной коммерции, это значительно увеличивает затраты.

Масштабирование «на лету»

Я недавно наткнулся на imgproxy, компонент для изменения размеров изображений на лету:

imgproxy делает веб-сайты и приложения быстрыми и экономит место и затраты на SaaS

Веб-сайт imgproxy

Он предлагает точку входа, куда можно отправить закодированный URL, определяющий:

  • Изображение для изменения размера и его расположение, например, локальное, HTTP-URL, корзина S3 и т. д.
  • Различные параметры масштабирования, например, размеры, соответствие или заполнение и т. д.
  • Формат. imgproxy поддерживает стандартные форматы, такие как JPEG и PNG, а также более современные, такие как WebP и AVIF. Он также может выбрать лучший формат в зависимости от заголовка «Прием».
  • Множество (множество!) других параметров, таких как водяные знаки, фильтрация, поворот и т. д.

imgproxy предлагает как свободную версию с открытым исходным кодом, так и платную версию; все, что включено в эту статью, является частью первого.

Одним из решений было бы веб-разработчику кодировать каждый URL imgproxy в HTML:

Это приводит к утечке связанных с топологией деталей на веб-странице. Это не поддерживаемое решение. Мы можем решить эту проблему с помощью обратного прокси или шлюза API. Я буду использовать Apache APISIX по очевидным причинам.

С таким подходом вышеуказанный HTML становится намного проще:

Apache APISIX перехватывает запросы, начинающиеся с /resize, изменяет URL для imgproxy и пересылает измененный URL в imgproxy. Вот общий ход:

Соответствующая конфигурация Apache APISIX выглядит следующим образом:

  1. Сопоставляйте запросы с префиксом /resize
  2. Переписывайте URL
  3. Отлавливайте ширину и изображение с помощью регулярного выражения
  4. Форматируйте URL для imgproxy. http://server:3000 – это сервер, на котором находится исходное изображение; @webp указывает предпочтение формату WebP (если браузер его поддерживает)

С помощью указанного выше кода, /resize/200/ai-generated.jpg на Apache APISIX переписывается как /rs:fill/w:200/plain/http://server:3000/ai-generated.jpg@webp в imgproxy.

Тестирование

Мы можем создать небольшой тестовый пример с помощью Docker Compose:

  1. Простой веб-сервер, на котором размещены HTML и основное изображение

Теперь мы можем протестировать указанную выше настройку с помощью инструментов разработчика браузера, эмулируя устройства с маленьким экраном, такие как iPhone SE. Результат следующий:

  • Из-за разрешения экрана запрашивается изображение шириной 400 пикселей, а не оригинальное. Вы можете увидеть это в URL запроса
  • Возвращаемое изображение имеет формат WebP, его размер составляет 14.4 КБ
  • Оригинальное изображение в формате JPEG весит 154 КБ, более чем в десять раз больше. Это значительная экономия пропускной способности сети!

Обсуждение

Сокращение затрат на хранение в десять раз, конечно, является большим преимуществом. Однако, не все так радужно. Создание измененного изображения требует вычислительных ресурсов, и каждый запрос требует затрат процессорного времени. Более того, несмотря на эффективность imgproxy, создание изображения занимает некоторое время. Мы поменяли затраты на хранение на затраты на процессорное время и теперь сталкиваемся с небольшим снижением производительности.

Чтобы исправить это, нам нужен прокси-кэш спереди, либо настраиваемый, либо, скорее всего, CDN. Вы можете возразить, что мы снова будем хранить ресурсы, и затраты на хранение снова вырастут. Однако существенное отличие состоит в том, что кэш работает только для используемых изображений, в то время как в предыдущем решении мы платили за хранение всех изображений. Также можно применять известные методы кэширования, такие как “предварительная загрузка” (pre-warming), когда известно, что группа изображений будет активно использоваться, например, перед событием.

Заключение

В этой статье мы описали, как использовать Apache APISIX с imgproxy, чтобы снизить затраты на хранение изображений разных разрешений. С использованием кэширования, мы добавляем еще несколько компонентов в общую архитектуру, однако сокращаем затраты на хранение.

Эта статья была вдохновлена докладом Андреаса Лера на конференции StackConf.

Полный исходный код для этой статьи можно найти на GitHub.

Подробнее