@feugene/granularity-media

Media components for @feugene/granularity: image cropping, camera capture, code scanning and video playback.
2,517 1
Install
npm install @feugene/granularity-media
Latest Version:3.0.0
Vue:3
License:LicenseRef-Granularity-EUC-1.1
Last Updated:Oct 22, 2026
Links: GitHub  ·  npm
Maintainer: efureev

@feugene/granularity-media

Работа с изображением, камерой и видео для @feugene/granularity. Четыре компонента, и они стоят рядом по сценарию: GrCameraCapture снимает кадр камерой, GrImageCrop выбирает кадр из уже существующей картинки, GrCodeScanner читает камерой QR и штрихкоды, GrVideoPlayer проигрывает готовое видео своими элементами управления.

yarn add @feugene/granularity-media

Своих зависимостей у пакета нет: кадрирование держится на Canvas и браузерных API, а не на библиотеке. Наружу объявлены только peer — ядро, granum и Vue.

Быстрый старт

<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { GrImageCrop } from '@feugene/granularity-media/components/GrImageCrop'

const file = ref<File | null>(null)
const cropper = useTemplateRef('cropper')

async function save() {
  const blob = await cropper.value?.crop()
  if (blob)
    await fetch('/api/avatar', { method: 'POST', body: blob })
}
</script>

<template>
  <GrImageCrop ref="cropper" :src="file" shape="circle" :output="{ width: 256, height: 256 }" />
  <button @click="save">
Сохранить
</button>
</template>

Рамка неподвижна, двигается картинка

Обратная модель — рамка ездит по изображению — требует двух жестов вместо одного, а её угловые ручки на телефоне меньше пальца. Здесь жест один: тянуть картинку, а увеличение живёт отдельным контролом (по умолчанию GrSlider ядра, заменяется слотом #controls).

Соотношение сторон кадра задаёт приложение (aspectRatio), а не пользователь: место, куда картинка потом встанет, известно заранее.

Кадр считается в пикселях исходника

Без output результат получает размер захваченной области исходного файла, а не окна на экране: окно почти всегда меньше картинки, и вывод по нему молча ополовинил бы разрешение. Нужен точный размер — он задаётся явно.

Что внутри

Жест перетаскивание на useDragGesture ядра: оборванный жест откатывается, а не коммитит
Клавиатура стрелки двигают кадр, +/- меняют увеличение, Home сбрасывает
Экспорт crop() отдаёт Blob; тип и качество — output.type, output.quality
Формы кадра прямоугольник и круг (shape="circle" — маска показа, результат остаётся прямоугольным)
Серверный рендер браузерного API в setup нет, оборачивать в <ClientOnly> не нужно

Вес гранулярного импорта

Что берут gzip от бареля
весь пакет из корня 14.4 kB 100 %
самый лёгкий компонент — GrVideoPlayer 3.6 kB 25 %
медианный компонент — GrCodeScanner 4.1 kB 28 %
4 самых тяжёлых вместе 14.7 kB 102 %

Числа не складываются: общий код посчитан в каждой строке заново, а платится один раз — поэтому набор компонентов и показан объединением, а не суммой. Это верхняя граница: gzip всего, что подпуть тянет из dist, а бандлер приложения трясёт дальше и минифицирует повторно.

Вес каждого компонента — docs/entry-sizes.md.

Подключение к сборке

Пакет — рядом с ядром, именем: granum читает манифест из его dist, а не сканирует node_modules. Авто-импорт — GranularityResolver() из @feugene/unplugin-granularity >=1.1.0: пакет он найдёт в package.json приложения сам.

// granum.config.ts
import { defineGranumConfig } from '@feugene/granum/vite'
import { windEngine } from '@feugene/granum-engine-wind'

export default defineGranumConfig({
  engine: windEngine(),
  providers: ['@feugene/granularity', '@feugene/granularity-media'],
  components: ['@feugene/granularity-media:GrImageCrop'],
})

Классы компонентов написаны против словаря unocss/preset-wind3+granum@66. Движок другого диалекта заставит granum сообщить provider-dialect-mismatch и перечислить отброшенные классы, а не отрисовать компоненты наполовину.

Локали подключаются тем же createFintI18n, что и словари ядра — блок GR_MEDIA_I18N_BLOCK.

Снимок камерой

<GrCameraCapture :aspect-ratio="1" @capture="blob => upload(blob)" />

Камера включается только по кнопке: запрос разрешения, всплывший сам по себе, отклоняют не глядя — а второй раз браузер уже не спросит, потому что решение запоминается для сайта целиком.

Отказ разведён на четыре состояния (denied, missing, busy, insecure), потому что действия пользователя в них разные: «разрешите доступ» при отсутствующей камере отправляет искать настройку, которой нет, а на http:// объекта navigator.mediaDevices не существует вовсе.

Превью фронтальной камеры зеркальное, снимок — нет: иначе текст на визитке или в документе уехал бы в зазеркалье.

Чтение кодов

<GrCodeScanner :formats="['qr_code']" @detect="codes => open(codes[0].value)" />

Декодера пакет не несёт. Нативный BarcodeDetector есть в Chrome и Edge, а Safari и Firefox закрываются детектором, который передаёт приложение (:detector): встроить библиотеку значило бы навязать самую тяжёлую зависимость и тем, кто взял один кроп. Готовый рецепт на @zxing/browser — на странице компонента.

Один код в кадре даёт одно событие: камера отдаёт десятки кадров в секунду, и без фильтра приложение оформило бы двадцать заказов вместо одного. Режим приёмки, где сканируют одинаковые упаковки подряд, включается continuous.

Видео со своими элементами управления

<GrVideoPlayer :src="clip" poster="/preview.jpg" muted />

Нативные controls в каждом браузере выглядят по-своему и не знают ни про темы, ни про размеры дизайн-системы. Полный экран запрашивается у рамки, а не у <video>: иначе браузер подменил бы эти элементы управления своими — вместе с клавиатурой и подписями.

Документация

Related Packages

vue-capture

A Vuejs library to record and save audio, video or pictures from your camera or...

1,871
@feugene/granularity-editor

Rich-text editing for @feugene/granularity: a TipTap-backed GrRichText field wit...

2,793 1
@feugene/granularity-devtools

Vue DevTools panel for @feugene/granularity — where a prop value came from, the...

1,273 1
vue-cam-barcode

Vue 3 camera barcode scanner for phones and tablets. Native BarcodeDetector when...

514 0
@feugene/unplugin-granularity

unplugin-vue-components resolver for @feugene/granularity — granular auto-import...

2,700 1

Version History

Version Released Vue License
3.0.0 ^3.5.0 LicenseRef-Granularity-EUC-1.1
2.0.2 ^3.5.0 LicenseRef-Granularity-EUC-1.1
2.0.1 ^3.5.0 LicenseRef-Granularity-EUC-1.1
2.0.0 ^3.5.0 LicenseRef-Granularity-EUC-1.1
1.1.0 ^3.5.0 LicenseRef-Granularity-EUC-1.1