Реализация Image Editor на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Image Editor на сайте
Сложная
~1-2 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Image Editor на сайте

Встроенный редактор изображений нужен там, где пользователь должен обработать фото прямо в браузере: аватарки с кадрированием, генераторы сертификатов, редакторы маркетинговых материалов, инструменты аннотирования скриншотов. Отправлять пользователя в Photoshop — неприемлемо для современного UX.

Стек по сценарию

Кадрирование и ресайзreact-image-crop или cropperjs. Самый частый сценарий — загрузка аватарки.

Полноценный редактор с фильтрами, текстом, слоямиfabric.js поверх Canvas. Весит ~300 КБ, но даёт полный контроль.

Профессиональный уровень (аннотирование, лассо, кисти)tui-image-editor (Toast UI) или konva.js.

Кадрирование аватарки (react-image-crop)

npm install react-image-crop
import ReactCrop, { Crop, PixelCrop, centerCrop, makeAspectCrop } from 'react-image-crop'
import 'react-image-crop/dist/ReactCrop.css'

function AvatarCropper({ onComplete }: { onComplete: (blob: Blob) => void }) {
  const [imgSrc, setImgSrc] = useState('')
  const [crop, setCrop] = useState<Crop>()
  const [completedCrop, setCompletedCrop] = useState<PixelCrop>()
  const imgRef = useRef<HTMLImageElement>(null)

  function onFileChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0]
    if (!file) return

    const reader = new FileReader()
    reader.onload = () => setImgSrc(reader.result as string)
    reader.readAsDataURL(file)
  }

  function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) {
    const { naturalWidth: width, naturalHeight: height } = e.currentTarget
    // Центрируем кроп 1:1 при загрузке
    const initialCrop = centerCrop(
      makeAspectCrop({ unit: '%', width: 80 }, 1, width, height),
      width,
      height
    )
    setCrop(initialCrop)
  }

  async function getCroppedImg(): Promise<Blob> {
    const image = imgRef.current!
    const canvas = document.createElement('canvas')
    const scaleX = image.naturalWidth / image.width
    const scaleY = image.naturalHeight / image.height

    canvas.width = completedCrop!.width
    canvas.height = completedCrop!.height

    const ctx = canvas.getContext('2d')!
    ctx.drawImage(
      image,
      completedCrop!.x * scaleX,
      completedCrop!.y * scaleY,
      completedCrop!.width * scaleX,
      completedCrop!.height * scaleY,
      0, 0,
      completedCrop!.width,
      completedCrop!.height
    )

    return new Promise((resolve) => {
      canvas.toBlob((blob) => resolve(blob!), 'image/jpeg', 0.92)
    })
  }

  return (
    <div>
      <input type="file" accept="image/*" onChange={onFileChange} />
      {imgSrc && (
        <>
          <ReactCrop
            crop={crop}
            onChange={setCrop}
            onComplete={setCompletedCrop}
            aspect={1}
            circularCrop
          >
            <img ref={imgRef} src={imgSrc} onLoad={onImageLoad} />
          </ReactCrop>
          <button
            onClick={async () => {
              const blob = await getCroppedImg()
              onComplete(blob)
            }}
          >
            Применить
          </button>
        </>
      )}
    </div>
  )
}

Fabric.js: редактор с наложением текста и фигур

npm install fabric
npm install -D @types/fabric
import { fabric } from 'fabric'
import { useEffect, useRef } from 'react'

function ImageEditor({ imageUrl }: { imageUrl: string }) {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const fabricRef = useRef<fabric.Canvas | null>(null)

  useEffect(() => {
    const canvas = new fabric.Canvas(canvasRef.current!, {
      width: 800,
      height: 600,
      backgroundColor: '#fff',
    })
    fabricRef.current = canvas

    // Загружаем фоновое изображение
    fabric.Image.fromURL(imageUrl, (img) => {
      img.scaleToWidth(800)
      canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas))
    }, { crossOrigin: 'anonymous' })

    return () => canvas.dispose()
  }, [imageUrl])

  function addText() {
    const text = new fabric.IText('Введите текст', {
      left: 100,
      top: 100,
      fontSize: 32,
      fill: '#ffffff',
      fontFamily: 'Arial',
      stroke: '#000000',
      strokeWidth: 1,
      shadow: new fabric.Shadow({ blur: 4, color: 'rgba(0,0,0,0.5)', offsetX: 2, offsetY: 2 }),
    })
    fabricRef.current!.add(text)
    fabricRef.current!.setActiveObject(text)
  }

  function addRect() {
    const rect = new fabric.Rect({
      left: 150,
      top: 150,
      width: 200,
      height: 100,
      fill: 'rgba(37,99,235,0.4)',
      stroke: '#2563eb',
      strokeWidth: 2,
      rx: 8,
      ry: 8,
    })
    fabricRef.current!.add(rect)
  }

  function applyFilter(type: 'grayscale' | 'sepia' | 'blur') {
    const bgImage = fabricRef.current!.backgroundImage as fabric.Image
    if (!bgImage) return

    const filterMap = {
      grayscale: new fabric.Image.filters.Grayscale(),
      sepia: new fabric.Image.filters.Sepia(),
      blur: new fabric.Image.filters.Blur({ blur: 0.05 }),
    }

    bgImage.filters = [filterMap[type]]
    bgImage.applyFilters()
    fabricRef.current!.renderAll()
  }

  function exportImage(): string {
    return fabricRef.current!.toDataURL({
      format: 'jpeg',
      quality: 0.92,
      multiplier: 2, // 2x для ретины
    })
  }

  return (
    <div>
      <div className="flex gap-2 mb-4">
        <button onClick={addText}>Добавить текст</button>
        <button onClick={addRect}>Прямоугольник</button>
        <button onClick={() => applyFilter('grayscale')}>Ч/Б</button>
        <button onClick={() => applyFilter('sepia')}>Сепия</button>
        <button onClick={() => {
          const dataUrl = exportImage()
          const a = document.createElement('a')
          a.href = dataUrl
          a.download = 'edited.jpg'
          a.click()
        }}>
          Скачать
        </button>
      </div>
      <canvas ref={canvasRef} />
    </div>
  )
}

Обрезка изображения до нужного соотношения на сервере

Для автоматической обрезки без участия пользователя — Sharp на Node.js:

// На бэкенде (Next.js API route / Express)
import sharp from 'sharp'

export async function resizeAvatar(buffer: Buffer): Promise<Buffer> {
  return sharp(buffer)
    .resize(400, 400, {
      fit: 'cover',
      position: 'attention', // Smart crop — фокус на лица
    })
    .webp({ quality: 85 })
    .toBuffer()
}

attention в Sharp использует saliency detection — умный кроп, который сохраняет лица и важные объекты в кадре.

Что делаем

Уточняем сценарий: просто кроп аватарки — это одна задача, редактор маркетинговых баннеров с текстом и фильтрами — совсем другая. Под задачу выбираем библиотеку, реализуем UI инструментов, экспорт в нужный формат (PNG/JPEG/WebP), интеграцию с формой загрузки и API.

Срок: кроппер аватарки — 1 день. Полноценный редактор на Fabric.js — 4–6 дней.