Учитывая уже развернутый сервис
imgproxy, я решил делать Image компонент поверх этого сервиса - это закрыло вопросы лишней нагрузки на SSR приложения, оптимизации под разные форматы, отдачи нужного формата в рантайме без тега
picture (что позволяет сделать очень простую разметку).
Еще не решенная проблема этого выбора - невозможность использовать сервис при разработке, для изображений на localhost.
.avif формат у нас был отключен из-за высокой нагрузки при генерации изображений в этом формате, было интересно разобраться в проблеме, даже помогли немножко улучшить сервис -
https://github.com/imgproxy/imgproxy/issues/938.
Оказалось, что для достижения сравнимого качества изображения, для AVIF надо указывать значительно более низкий уровень качества при оптимизации, хорошее сравнение форматов можно найти тут -
https://www.industrialempathy.com/posts/avif-webp-quality-settings/В итоге, после точечной настройки, мы подключили AVIF, и в среднем, с примерно одинаковым качеством, это снизило размер итоговых изображений на 30-50% по сравнению с webP.
Следующий кейс - автоматическое сохранение пропорций, вычисление aspect-ratio.
На примере некста, логику получения размера картинки можно встроить в сборку -
https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack/loaders/next-image-loader.js.
В
tramvai я добавил простой лоадер, который переиспользует логику
file-loader (компромисс, т.к. по хорошему надо использовать Asset Modules), но при именованном импорте отдает объект с данными об изображении, которые необходимы для сохранения пропорций:
jsx
import { image } from './image.png';
const { src, width, height } = image;
<img src={src} width={width} height={height} />
По аналогии с next/image, добавил в компонент несколько вариантов верстки (и без необходимости поддерживать старые браузеры и наличии aspect-ratio, верстка невероятно простая):
- адаптивная, но не больше размера изображения (intrinsic)
- резиновая под размер контейнера (responsive)
- фиксированный размер (fixed)
- заполняющая доступное пространство (fill)
Если для intrinsic и fixed изображений достаточно учесть только плотность пикселей на экране, и передать в
srcset 1x и
2x изображения, то для оптимальной responsive и fill верстки
srcset должен содержать изображения под совершенно разные размеры экранов.
По этой причине, отдельной задачей было исследовать аналитику посещения наший приложений на
tinkoff.ru для создания минимального но в то же время полного списка экранов, которые мы будем учитывать в
srcset при генерации изображений.
Пример размеров из этого списка:
- 720px - популярная ширина мобилок 360px плюс 2x плотность пикселей
- 1366px - популярная ширина на десктопе
- 3840px - еще одна популярная ширина на десктопе, 1920px плюс 2x плотность пикселей
Таким образом, пользователи большинства устройств получат изображение минимально необходимого размера и оптимального качества.
Еще из интересного, простые и удобные абстракции:
- Свойство
quality - позволяет при необходимости сконфигурировать оптимизацию через
imgproxy более высокого качества, или наоборот более низкого
- Свойство
priority - по умолчанию делаем все изображения c
loading="lazy", но можем повысить приоритет и убрать этот атрибут, а с самым высоким приоритетом автоматически добавляем
preload link на страницу для этой картинки (тривиальная задача для SSR фреймворка), что идеально подходит для Largest Contentful Paint изображения
Это был очень интересный опыт, и интересно как дальше будет развиваться новый Image компонент.
Например, в будущем я планирую отвязать его от
imgproxy, и перенести в список публичных пакетов, которые публикуются в open-source.
Также, надо будет выбрать хороший способ создания blur плейсхолдера для изображений.