Оптимизация изображений
На этой странице
Положите полноразмерное изображение в src/ или public/, направьте на него <img> — и sitelo уменьшит его, переведёт в современный формат и перепишет тег с srcset. Ничего импортировать не нужно, никакого компонента изучать не придётся.
Включение
export default {
images: true,
}Кодированием занимается sharp — необязательная peer-зависимость; установите её вместе с sitelo, когда включаете изображения:
npm install -D sharpСайтам без оптимизации изображений устанавливать её не нужно вовсе.
Пишите обычный <img>
export default () => `
<html lang="ru">
<body>
<img src="/images/hero.png" alt="Рассвет над гаванью">
</body>
</html>
`import { html, body, img } from 'javascript-to-html'
export default () =>
html({ lang: 'ru' },
body(
img({ src: '/images/hero.png', alt: 'Рассвет над гаванью' }),
),
)export default function Home() {
return (
<html lang="ru">
<body>
<img src="/images/hero.png" alt="Рассвет над гаванью" />
</body>
</html>
)
}Исходник 3000×2000 выходит вот таким:
<img src="/assets/img/hero.a1b2c3d4-3000.webp"
alt="Рассвет над гаванью"
sizes="100vw"
width="3000" height="2000"
loading="lazy" decoding="async"
srcset="/assets/img/hero.9f8e7d6c-400.webp 400w,
/assets/img/hero.5b4a3c2d-800.webp 800w,
/assets/img/hero.7c6d5e4f-1200.webp 1200w,
/assets/img/hero.a1b2c3d4-3000.webp 3000w">Экран шириной до 400px получает файл 400, до 800 — 800, до 1200 — 1200, а всё, что шире, — полные 3000: браузер берёт первую ступень, покрывающую окно (на экране 2× — вдвое больше).
Что вы получаете
- Масштабирование, которое никогда не увеличивает — каждая настроенная ширина меньше исходной, а сверху — ширина самого источника. Источник 750px с
widths: [400, 800, 1200]даст 400 и 750, и на этом остановится. - Современные форматы — один формат даёт обычный
<img srcset>; два и больше оборачиваются в<picture>с запасным вариантом в исходном формате. - Без скачков вёрстки —
widthиheightзаполняются из настоящего изображения, плюсloading="lazy"иdecoding="async". - Та же разметка в разработке —
sitelo(dev) переписывает страницы и отдаёт варианты по запросу, так что вы видите именно то, что опубликуете. - Общий кэш — варианты хранятся по хэшу содержимого в
node_modules/.sitelo/images, поэтому ни разработка, ни повторные сборки не кодируют одно и то же изображение дважды.
Переписывание идёт по собранному HTML, поэтому охватывает изображения и из src/, и из public/.
После переписывания тега на оригинал уже ничто не ссылается, поэтому по умолчанию он удаляется из сборки. Уходят только действительно неиспользуемые файлы: просматривается каждый HTML, CSS, JS, XML и JSON в сборке, так что оригинал, на который ссылается <a href>, og:image, RSS-вложение, CSS-url() или тег data-no-optimize, остаётся. URL, собираемые в скрипте на лету или отрисованные серверным островом, ему не видны — поставьте prune: false, если такие есть.
Параметры
export default {
images: {
widths: [400, 800, 1200],
formats: ['avif', 'webp'],
quality: { avif: 55, webp: 78, jpeg: 82 },
exclude: ['**/og/**'],
},
}widths— по умолчанию[400, 800, 1200]; ширина самого источника всегда венчает лестницуformats— по умолчанию['webp'];avif,webp,jpeg,pngquality— по умолчанию{ avif: 55, webp: 78, jpeg: 82 }; качество кодировщика по форматам (png использует сжатие, а не качество)sizes— атрибутsizes;sizes, указанный в теге, всегда важнееdimensions— по умолчаниюtrue; добавляетwidth/heightlazy— по умолчаниюtrue; добавляетloadingиdecodingexclude— glob-шаблоны или регулярные выражения URL изображений, которые не трогатьassetsDir— по умолчанию'assets/img'cacheDir— по умолчанию'node_modules/.sitelo/images'remote— по умолчаниюfalse; оптимизирует изображенияhttps://во время сборкиprune— по умолчаниюfalse; удаляет оригиналы, на которые больше ничего в сборке не ссылаетсяdev— по умолчаниюtrue; поставьтеfalse, чтобы в разработке отдавать нетронутые оригиналыconcurrency— параллельные кодирования, по умолчанию CPU − 1 (максимум 8); 1, если включён remote
Два формата дают <picture>
AVIF меньше, но моложе WebP, поэтому указание обоих позволяет браузеру выбрать и сохраняет запасной вариант для старых браузеров:
<picture>
<source type="image/avif" srcset="/assets/img/hero.*-400.avif 400w, ..." sizes="...">
<source type="image/webp" srcset="/assets/img/hero.*-400.webp 400w, ..." sizes="...">
<img src="/assets/img/hero.*-3000.png" alt="..." srcset="..." width="3000" height="2000">
</picture>Один размер, по имени
Миниатюры карусели, аватары, сетка карточек — некоторые изображения показываются только в одном размере, и целая лестница для них лишняя. Имена файлов вариантов содержат хеш содержимого, так что написать их вручную нельзя; вместо этого укажите ширину в URL, и тег выйдет ровно с этим одним файлом:
<img src="/images/hero.png?w=400" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=400&format=jpeg" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200&fit=contain" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200&background=fff" alt="Рассвет над гаванью">
<img src="/images/hero.png?w=200&h=200&position=top" alt="Рассвет над гаванью"><img src="/assets/img/hero.9f8e7d6c-400.webp"
alt="Рассвет над гаванью"
width="400" height="267"
loading="lazy" decoding="async">w— ширина в пикселях. Подойдёт любая ширина, не только изwidths; источник меньшего размера остаётся как есть, а не растягивается.h— высота в пикселях. Сама по себе: ширина следует пропорциям; вместе сwизображение масштабируется до этого бокса и обрезается —?w=200&h=200даёт квадратную миниатюру.fit— как выполняется боксw×h:cover(по умолчанию) заполняет его и обрезает;containвписывает изображение целиком, без обрезки и полей —width/heightпоказывают, что получилось.background— дополняет результатcontainдо точного бокса и подразумеваетfit=contain. Hex без#(fff,1a1a1a,ffffff80), имя цвета CSS илиtransparent— ему нужен формат с альфа-каналом; в jpeg он станет чёрным.position— какую часть оставляет обрезкаcover. По умолчанию центр; край или угол (top,left,right-bottom, …) либо стратегии sharp по содержимому —entropy/attention.format—avif,webp,jpegилиpng. Сам по себе сохраняет полную лестницу, но в этом одном формате.
Если настроено несколько formats, закреплённая ширина по-прежнему даёт <picture> — по одному файлу на <source>. Имена те же, что у vite-imagetools. Внешние URL никогда не разбираются — их строка запроса принадлежит источнику, — а при выключенном images статические хосты игнорируют запрос и отдают оригинал.
Пусть картинка сама выберет обрезку
Край или угол предсказуемы, но в карусели разных фотографий объект редко дважды оказывается в одном месте. entropy и attention просят sharp посмотреть на каждую картинку и сдвинуть окно обрезки туда, где найдётся что-то стоящее:
<img src="/images/hero.png?w=300&h=300&position=entropy" alt="Рассвет над гаванью">
<img src="/images/team.jpg?w=300&h=300&position=attention" alt="Команда">entropyоставляет область с наибольшей детализацией — контуры, текстуру, разнообразие цвета. Ровное небо, пустые стены и однотонный фон уходят первыми. Подходит для пейзажей, товаров и всего, где «насыщенно» значит «интересно».attentionсмещает обрезку к насыщенным цветам, высокочастотной яркости и оттенкам кожи — эвристика того, куда посмотрел бы человек. Подходит для фотографий людей: обычно находит лица и объекты.
Обе — простые эвристики, без модели и обучения, так что это разумное значение по умолчанию для многих изображений, а не гарантия для одного. Для важного главного изображения назовите край. Выбор зависит только от картинки, поэтому он детерминирован и кэшируется как любой другой вариант.
Как исключить изображение
Теги, у которых уже есть srcset, которые находятся внутри <picture> или указывают на SVG, анимированный GIF либо внешний URL, остаются нетронутыми. Для всего остального скажите об этом явно:
<img src="/images/exact.png" alt="Пиксель-арт" data-no-optimize>Изображения для соцсетей и фавиконки живут в <meta> и <link>, которые здесь никогда не переписываются — их URL остаётся неизменным.
Внешние изображения
Контент, импортированный из CMS, часто указывает на чужой сервер. Включите remote — и такие изображения будут скачаны, оптимизированы и отданы с вашего домена:
export default {
images: {
remote: true,
},
}Неудачная загрузка оставляет тег ровно таким, каким он был, с предупреждением — нестабильный источник никогда не обрушит вашу сборку. Затем prune убирает локальные оригиналы, на которые больше ничего не ссылается.