vite-smart-img
v1.1.16
Published
Smart lazy-loading image component for React + Vite with automatic thumbnail generation
Downloads
108
Readme
vite-smart-img
Умный компонент ленивой загрузки изображений для React + Vite: автоматические превью (thumbnails), конвертация в WebP, обновление src при изменении размера элемента.
Содержание
- Возможности
- Установка
- Интеграция
- API компонента
- Кастомизация CSS
- Атрибут data-max-size-load
- Как это работает — подробно
- ⚠️ Исходные файлы WebP — не поддерживаются
- Отладочные атрибуты
Возможности
- Компонент SmartImg — ленивый React-компонент: изображение запрашивается, когда оно попадает в область просмотра (видимость ≥ 5%).
- JPG, PNG, SVG и внешние URL — для каждого формата своя логика.
- SVG и внешние картинки — после появления в зоне видимости загружаются целиком; сразу выставляется
data-max-size-load. - Адаптивные миниатюры (только prod) — выбирается наименьшая миниатюра, которая покрывает CSS-ширину элемента × DPR; после первого события resize при необходимости подгружается более крупный размер.
- Плавное появление — опционально через проп
opacity. - Кэш сессии — загруженные URL запоминаются на время жизни страницы; при повторном монтировании восстановление без повторного fade.
- IntersectionObserver — сетевой запрос не уходит, пока браузер не сообщит о видимости ≥ 5%.
- Плагин сборки Vite — хеши имён, очистка метаданных, миниатюры и варианты WebP для растра, оптимизация SVG.
- Авторазмеры — в dev и build плагины вытаскивают
originalW/originalHиз ассетов и встраивают в модуль, чтобы контейнер с aspect-ratio был корректным.
Дополнительно:
- Определение WebP через
canvas.toDataURL('image/webp')— один раз при первом использовании, результат кэшируется. - Debounced resize —
window.resizeс задержкой 200 ms. - Ошибка загрузки — для битой картинки показывается SVG-плейсхолдер;
data-max-size-loadравен"svg-error"или"external-error". - CSS-переменные —
--smi-bg(фон плейсхолдера),--smi-fade-duration(длительность перехода). - Проп
className— дополнительный класс на обёртке<div>. - Один IO и один слушатель resize на страницу для всех экземпляров SmartImg.
- Progressive JPEG — миниатюры JPEG кодируются как progressive.
decoding="async"на<img>, чтобы декод не блокировал основной поток.
Установка
npm install vite-smart-imgPeer dependencies — в проекте должны быть
react >= 18иreact-dom >= 18.Для сборки — плагинам Vite нужен
sharpв devDependencies проекта:npm install --save-dev sharp
Интеграция
1. Плагины Vite
Подключите все четыре плагина в vite.config.ts. У каждого своя роль:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import {
imageThumbnails,
smartImgSvgAssetBuild,
importedAssetsAlwaysFiles,
smartImgLocalAssetImport,
} from 'vite-smart-img/plugin'
export default defineConfig({
plugins: [
// Должны идти до react() и прочих плагинов
importedAssetsAlwaysFiles(), // отключает inline ассетов Vite (assetsInlineLimit: 0)
smartImgLocalAssetImport(), // только dev: размеры JPG/PNG/SVG и { src, originalW, originalH }
imageThumbnails(), // только build: миниатюры, WebP, без EXIF, хеши имён файлов
smartImgSvgAssetBuild(), // только build: SVGO и хеш для SVG
react(),
],
})Что делает каждый плагин:
| Плагин | Когда | Назначение |
|--------|--------|------------|
| importedAssetsAlwaysFiles | всегда | build.assetsInlineLimit: 0 — мелкие файлы не уходят в base64 внутри JS, иначе ломается сборка URL миниатюр |
| smartImgLocalAssetImport | только dev | Перехватывает импорты *.jpg / *.png / *.svg из папок assets/; sharp (растр) или парсер SVG; экспорт { src, originalW, originalH } |
| imageThumbnails | только build | Хеш содержимого (SHA-256, 8 символов), снятие EXIF/метаданных, миниатюры по ширинам [400, 600, 800 … 8000], не шире оригинала; пары .jpg/.png + .webp; если миниатюра получилась больше очищенного оригинала — используется оригинал |
| smartImgSvgAssetBuild | только build | Оптимизация SVG через SVGO (preset-default, multipass), хеш, вывод как отдельный файл |
2. Типы для TypeScript
Добавьте в tsconfig.json, чтобы TypeScript понимал импорты ассетов:
{
"compilerOptions": {
"types": ["vite-smart-img/vite-asset-types"]
}
}Или в *.d.ts проекта:
/// <reference types="vite-smart-img/vite-asset-types" />После этого импорты из */assets/*.jpg, *.png, *.svg имеют тип { src: string; originalW: number; originalH: number }, а не просто string.
3. Использование SmartImg в компонентах
Локальный ассет (JPG / PNG / SVG)
Импортируйте файл из папки assets/. Плагины сами добавят метаданные размеров:
import { SmartImg } from 'vite-smart-img'
import photo from './assets/photo.jpg'
import logo from './assets/logo.svg'
export const MyComponent = () => (
<div>
{/* Локальный растр — в prod миниатюры, в dev оригинал */}
<SmartImg asset={photo} alt="Фотография" opacity />
{/* Локальный SVG — всегда полный оригинал */}
<SmartImg asset={logo} alt="Логотип" opacity />
</div>
)Внешний URL
Размеры задаются вручную (плагин на этапе сборки не видит чужие файлы):
import { SmartImg } from 'vite-smart-img'
export const Banner = () => (
<SmartImg
dataSrc="https://example.com/images/banner.jpg"
originalW={1600}
originalH={900}
alt="Баннер"
opacity
/>
)Без плавного появления
Не передавайте opacity — картинка покажется сразу после загрузки:
<SmartImg asset={photo} alt="Без fade" />Дополнительный класс обёртки
<SmartImg asset={photo} alt="Стили" className="my-image-block" opacity />className вешается на внешний <div>, не на <img>. Удобно для размеров и позиционирования:
.my-image-block {
width: 400px;
border-radius: 12px;
overflow: hidden;
}Соотношение сторон
Компонент создаёт контейнер с padding-top по формуле originalH / originalW, чтобы до загрузки зарезервировать место и снизить CLS. Отдельная высота не обязательна.
Плейсхолдер и длительность fade
Переопределяйте CSS-переменные глобально или локально:
/* глобально */
:root {
--smi-bg: #1a1a2e; /* фон до загрузки */
--smi-fade-duration: 600ms; /* длительность fade, по умолчанию 400ms */
}
/* область компонента */
.hero-image {
--smi-bg: transparent;
--smi-fade-duration: 200ms;
}API компонента
Пропсы — локальный ассет
| Проп | Тип | Обязательно | Описание |
|------|-----|-------------|----------|
| asset | SmartImgImportedAsset | ✓ | Импорт из */assets/*.jpg \| *.png \| *.svg |
| alt | string | ✓ | Текст для атрибута alt |
| opacity | boolean | — | Включить плавное появление |
| className | string | — | Доп. класс на обёртке <div> |
Пропсы — внешний URL
| Проп | Тип | Обязательно | Описание |
|------|-----|-------------|----------|
| dataSrc | string | ✓ | Полный URL изображения |
| originalW | number | ✓ | Ширина оригинала в пикселях |
| originalH | number | ✓ | Высота оригинала в пикселях |
| alt | string | ✓ | Текст для alt |
| opacity | boolean | — | Плавное появление |
| className | string | — | Класс на обёртке <div> |
Кастомизация CSS
Стили один раз вставляются в <head> при первом рендере. Доступны две переменные:
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| --smi-bg | #f0f0f0 | Фон плейсхолдера до загрузки |
| --smi-fade-duration | 400ms | Длительность fade (нужен проп opacity) |
Атрибут data-max-size-load
После подтверждения оптимального размера загрузки компонент выставляет на <img> атрибут data-max-size-load. Удобно для CSS «полностью загружено»:
/* зелёная точка, когда картинка на финальном размере */
.smi-wrapper:has(img[data-max-size-load]) .my-indicator {
background: green;
}Возможные значения:
| Значение | Когда |
|----------|--------|
| "dev" | Режим разработки — загружен оригинал |
| "svg" | SVG загружен полностью |
| "external" | Внешний URL загружен |
| "800" / "1200" / … | Prod — подтверждённая ширина миниатюры в px после первого resize |
| "Infinity" | Prod — загружен оригинал (подходящей миниатюры не нашлось) |
| "svg-error" | Ошибка загрузки SVG |
| "external-error" | Ошибка внешнего URL |
Важно: в production data-max-size-load выставляется только после первого события resize, следующего за загрузкой. Так учитываются смена ориентации и размера viewport. Повторный монтирование компонента само по себе этот атрибут не восстанавливает — нужен новый resize.
Как это работает — подробно
Схема загрузки
Монтирование компонента
│
├─ Попадание в кэш? → мгновенно восстановить src (без fade, без IO)
│
└─ Нет → регистрация в IntersectionObserver (порог 5%)
│
└─ Видимость ≥ 5%
│
├─ SVG / внешний URL → loadDirectFinal → data-max-size-load="svg|external"
│
├─ Dev (JPG/PNG) → оригинал → data-max-size-load="dev"
│
└─ Prod (JPG/PNG) → pickSize(cssWidth × DPR, originalW)
→ миниатюра
│
└─ При resize (debounce 200ms)
→ повторное наблюдение видимых
→ при необходимости апгрейд миниатюры
→ data-max-size-load="<size>"Выбор миниатюры (pickSize)
target = ceil(cssWidth × devicePixelRatio)
первый размер из [400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000,
2500, 3000, 3500, 4000, 5000, 6000, 7000, 8000],
где size >= target И size <= originalW
→ null: грузить оригинальный файлСтруктура выхода сборки
Для photo.jpg (оригинальная ширина 2400px) плагин кладёт в dist:
dist/assets/img/
a1b2c3d4.jpg ← очищенный оригинал
a1b2c3d4-400.jpg ← миниатюра 400px, progressive JPEG
a1b2c3d4-400.webp
a1b2c3d4-600.jpg
a1b2c3d4-600.webp
…
a1b2c3d4-2000.jpg
a1b2c3d4-2000.webp
(размеры шире 2400px не генерируются)Модуль импорта:
{ src: '/assets/img/a1b2c3d4.jpg', originalW: 2400, originalH: 1600 }Кэш сессии
loadedCache — Map<dataSrc, { url, size }> на время сессии страницы. При повторном монтировании SmartImg:
useLayoutEffectсинхронно читает кэш до первого paint.imgSrcсразу из кэша — без вспышки пустоты и без fade.- Второй экземпляр того же изображения, ещё не загрузившийся, при появлении в viewport может получить fade.
⚠️ Исходные файлы WebP — не поддерживаются
Не импортируйте .webp как ассеты. Обрабатываются только .jpg, .jpeg, .png, .svg.
Если импортировать .webp из assets/:
- dev: плагин
smartImgLocalAssetImportне перехватит импорт → Vite отдаст обычную строку URL, не объект{ src, originalW, originalH }— компонент сломается. - build:
imageThumbnailsне трогает.webp; файл пойдёт в стандартный пайплайн Vite (возможен inline base64 и отсутствие метаданных размеров).
Используйте JPG или PNG как исходники. WebP для браузеров пакет соберёт сам.
Отладочные атрибуты
Каждый <img> от SmartImg в DevTools имеет:
| Атрибут | Смысл |
|---------|--------|
| data-src | Исходный dataSrc (файл-оригинал, не URL миниатюры) |
| data-original-w | Ширина оригинала |
| data-original-h | Высота оригинала |
Только метаданные для отладки; логика компонента на них не опирается.
