npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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 при изменении размера элемента.


Содержание


Возможности

  1. Компонент SmartImg — ленивый React-компонент: изображение запрашивается, когда оно попадает в область просмотра (видимость ≥ 5%).
  2. JPG, PNG, SVG и внешние URL — для каждого формата своя логика.
  3. SVG и внешние картинки — после появления в зоне видимости загружаются целиком; сразу выставляется data-max-size-load.
  4. Адаптивные миниатюры (только prod) — выбирается наименьшая миниатюра, которая покрывает CSS-ширину элемента × DPR; после первого события resize при необходимости подгружается более крупный размер.
  5. Плавное появление — опционально через проп opacity.
  6. Кэш сессии — загруженные URL запоминаются на время жизни страницы; при повторном монтировании восстановление без повторного fade.
  7. IntersectionObserver — сетевой запрос не уходит, пока браузер не сообщит о видимости ≥ 5%.
  8. Плагин сборки Vite — хеши имён, очистка метаданных, миниатюры и варианты WebP для растра, оптимизация SVG.
  9. Авторазмеры — в dev и build плагины вытаскивают originalW / originalH из ассетов и встраивают в модуль, чтобы контейнер с aspect-ratio был корректным.

Дополнительно:

  • Определение WebP через canvas.toDataURL('image/webp') — один раз при первом использовании, результат кэшируется.
  • Debounced resizewindow.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-img

Peer 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 }

Кэш сессии

loadedCacheMap<dataSrc, { url, size }> на время сессии страницы. При повторном монтировании SmartImg:

  1. useLayoutEffect синхронно читает кэш до первого paint.
  2. imgSrc сразу из кэша — без вспышки пустоты и без fade.
  3. Второй экземпляр того же изображения, ещё не загрузившийся, при появлении в 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 | Высота оригинала |

Только метаданные для отладки; логика компонента на них не опирается.