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 🙏

© 2025 – Pkg Stats / Ryan Hefner

aria-cropper

v0.2.21

Published

React image uploader & cropper with enforced output sizes.

Downloads

180

Readme

AriaCropper

AriaCropper — یک کامپوننت React/Next.js سبک برای آپلود، برش (Crop) و تبدیل فرمت تصاویر با امکان اجباری‌کردن ابعاد خروجی، قفل نسبت تصویر، تنظیم کیفیت، و تولید خروجی ساختاریافته برای ارسال به سرور.

npm version license


امکانات کلیدی

  • 🔒 ابعاد خروجی اجباری (مثلاً 1200×1200) با قفل نسبت تصویر
  • 🎯 Crop دقیق با پیش‌نمایش زنده (Inline/Modal)
  • 🧰 تبدیل فرمت (webp/jpeg/png) + تنظیم کیفیت
  • 🧾 خروجی استاندارد به شکل ImageChangeSet برای استفاده مستقیم در فرم‌ها
  • 🧪 اعتبارسنجی‌های کاربردی: نوع فایل، حجم، حداقل ابعاد
  • ♿️ توجه به Accessibility (کلیدها، لیبل‌ها، متن جایگزین)
  • ⚡️ الگوهای پیشنهادی برای Next.js (app dir) و API Route

این README طوری نوشته شده که بتوانید آن را مستقیماً جایگزین README.md بسته کنید.


نصب

# بسته اصلی (از npm)
npm install aria-cropper cropperjs

# یا اگر scoped دارید
npm install @your-scope/aria-cropper cropperjs

نکته: cropperjs یک peer dependency است. فراموش نکنید استایل آن را نیز ایمپورت کنید:

// in a client component or global CSS
import 'cropperjs/dist/cropper.css'

راه‌اندازی سریع (Quick Start)

'use client'
import React, { useState } from 'react'
import AriaCropper, { type ImageChangeSet } from 'aria-cropper'
import 'cropperjs/dist/cropper.css'

export default function Demo() {
  const [change, setChange] = useState<ImageChangeSet | null>(null)

  return (
    <div className="space-y-4">
      <AriaCropper
        value={null}
        requiredWidth={1200}
        requiredHeight={1200}
        previewWidth={300}
        outputFormat="webp"
        quality={0.9}
        onChange={setChange}
      />

      <pre className="text-xs bg-gray-50 p-3 rounded border">
        {JSON.stringify(change, null, 2)}
      </pre>
    </div>
  )
}

سناریوی پیش‌فرض پیشنهادی: خروجی 1200×1200 WebP با پیش‌نمایش 300px و اکشن replace.


انواع داده (Types)

ImageInfo

export type ImageInfo = {
  url: string            // Blob URL یا URL سرور
  width: number          // عرض واقعی خروجی
  height: number         // ارتفاع واقعی خروجی
  format: 'webp' | 'jpeg' | 'png'
  size: number           // بایت
  fileName?: string      // اختیاری: نام فایل خروجی پیشنهادی
  meta?: Record<string, any>
}

ImageChangeSet

export type ImageChangeSet =
  | { action: 'keep' }
  | { action: 'remove' }
  | { action: 'replace', file: File, info: ImageInfo }
  | { action: 'format_change', info: ImageInfo }
  • keep: تصویر قبلی بدون تغییر نگه داشته می‌شود.
  • remove: درخواست حذف تصویر موجود.
  • replace: تصویر جدید برش داده شده (فایل + اطلاعات) آماده آپلود.
  • format_change: فقط فرمت/کیفیت/پردازش تغییر کرده (بدون نیاز به فایل جدید؛ برای گردش‌های کاری خاص).

معمول‌ترین حالتی که به سرور ارسال می‌شود replace است (شامل File).


API کامپوننت (Props)

export type AriaCropperProps = {
  /** مقدار اولیه (تصویر موجود یا null) */
  value: ImageInfo | null

  /** هندلر تغییرات – خروجی استاندارد */
  onChange: (change: ImageChangeSet) => void

  /**
   * ابعاد خروجی اجباری. هر دو را بدهید تا نسبت قفل شود.
   * اگر فقط یکی را بدهید، نسبت براساس تصویر/کِرُپر حفظ می‌شود.
   */
  requiredWidth?: number
  requiredHeight?: number

  /** حداقل ابعاد ورودی قبل از Crop (اختیاری) */
  minWidth?: number
  minHeight?: number

  /** محدودیت بزرگ‌نمایی بالاتر از ابعاد ورودی */
  allowUpscale?: boolean    // پیش‌فرض: false

  /** قفل نسبت تصویر (اگر required* داده شده باشد معمولاً true می‌شود) */
  lockAspectRatio?: boolean // پیش‌فرض: true وقتی required* هر دو مقدار داشته باشند

  /** اندازه پیش‌نمایش (پهنای باکس Preview) */
  previewWidth?: number     // پیش‌فرض: 200

  /** انتخاب فرمت خروجی */
  outputFormat?: 'webp' | 'jpeg' | 'png' // پیش‌فرض: 'webp'

  /** کیفیت خروجی (۰..۱) */
  quality?: number          // پیش‌فرض: 0.9

  /** رنگ پس‌زمینه برای فرمت‌های بدون شفافیت (مثل JPEG) */
  background?: 'transparent' | string // پیش‌فرض: 'transparent'

  /** پذیرش نوع فایل ورودی */
  accept?: string           // پیش‌فرض: 'image/*'

  /** سقف حجم فایل ورودی (MB) */
  maxFileSizeMB?: number    // پیش‌فرض: 10

  /** غیرفعال کردن کنترل‌ها */
  disabled?: boolean

  /** نمایش رابط برش به صورت درجا یا مودال */
  uiMode?: 'inline' | 'modal' // پیش‌فرض: 'inline'

  /** برچسب‌ها و متن‌های رابط کاربری (i18n) */
  i18n?: Partial<typeof defaultI18n>

  /** کال‌بک‌های کمکی */
  onOpen?: () => void
  onClose?: () => void
  onError?: (error: AriaCropperError) => void

  /** ارسال مستقیم تنظیمات به cropperjs (اختیاری) */
  cropperOptions?: Partial<Cropper.Options>
}

خطاها (AriaCropperError)

export type AriaCropperError = {
  code:
    | 'INVALID_TYPE'
    | 'FILE_TOO_LARGE'
    | 'DIMENSION_TOO_SMALL'
    | 'DECODE_FAILED'
    | 'CROP_FAILED'
    | 'UNSUPPORTED_FORMAT'
  message: string
  detail?: any
}

الگوهای رایج استفاده

1) سناریوی پروفایل کاربر (Next.js – app dir)

'use client'
import { useState } from 'react'
import AriaCropper, { type ImageChangeSet } from 'aria-cropper'
import 'cropperjs/dist/cropper.css'

export default function ProfilePhotoField() {
  const [photoChange, setPhotoChange] = useState<ImageChangeSet | null>(null)

  const handleSubmit = async () => {
    if (!photoChange) return
    const fd = changeSetToFormData(photoChange, { field: 'photo' })
    const res = await fetch('/api/profile/photo', { method: 'POST', body: fd })
    // پاسخ را بررسی کنید…
  }

  return (
    <div className="space-y-4">
      <AriaCropper
        value={null}
        requiredWidth={1200}
        requiredHeight={1200}
        previewWidth={300}
        outputFormat="webp"
        onChange={setPhotoChange}
      />

      <button onClick={handleSubmit} className="px-3 py-2 rounded bg-blue-600 text-white">
        ذخیره
      </button>
    </div>
  )
}

2) فقط تبدیل فرمت بدون تعویض فایل (format_change)

<AriaCropper
  value={{ url: existingUrl, width: 800, height: 800, format: 'jpeg', size: 120000 }}
  outputFormat="webp"
  onChange={(c) => {
    if (c.action === 'format_change') {
      // می‌توانید فقط meta را به سرور بفرستید یا در کلاینت ذخیره کنید
    }
  }}
/>

3) حذف تصویر

<AriaCropper
  value={currentImage}
  onChange={(c) => {
    if (c.action === 'remove') {
      // آلارم/تأیید و سپس ارسال به سرور
    }
  }}
/>

کمکی‌ها (Helpers)

changeSetToFormData

export function changeSetToFormData(
  change: ImageChangeSet,
  opts?: { field?: string; meta?: Record<string, any> }
) {
  const fd = new FormData()
  const field = opts?.field ?? 'file'
  if (opts?.meta) fd.append('meta', JSON.stringify(opts.meta))

  switch (change.action) {
    case 'replace':
      fd.append(field, change.file, change.info.fileName ?? inferFileName(change.info))
      fd.append('info', JSON.stringify(change.info))
      fd.append('action', 'replace')
      break
    case 'remove':
      fd.append('action', 'remove')
      break
    case 'format_change':
      fd.append('action', 'format_change')
      fd.append('info', JSON.stringify(change.info))
      break
    case 'keep':
      fd.append('action', 'keep')
      break
  }
  return fd
}

function inferFileName(info: ImageInfo) {
  const base = info.fileName?.replace(/\.[^.]+$/, '') || 'image'
  const ext = info.format === 'jpeg' ? 'jpg' : info.format
  return `${base}.${ext}`
}

شِمای پاسخ سرور (پیشنهادی)

// Next.js Route Handler (app/api/profile/photo/route.ts)
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const form = await req.formData()
  const action = form.get('action') as string

  if (action === 'replace') {
    const file = form.get('file') as File
    // ✅ اینجا MIME/اندازه/ابعاد را دوباره سمت سرور اعتبارسنجی کنید
    // سپس در S3/Filesystem ذخیره و URL نهایی را برگردانید
    return NextResponse.json({ ok: true, url: '/uploads/...' })
  }

  if (action === 'remove') {
    // حذف فایل قبلی و بروزرسانی DB
    return NextResponse.json({ ok: true })
  }

  if (action === 'format_change') {
    // معمولاً تغییر کلاینتی است؛ ولی می‌توانید در سرور هم پردازش کنید
    return NextResponse.json({ ok: true })
  }

  return NextResponse.json({ ok: true })
}

نکات SSR و Next.js

  • این کامپوننت Client Component است. در فایل استفاده‌شونده، حتماً "use client" بالای فایل باشد.
  • اگر با خطای window is not defined روبه‌رو شدید، از Dynamic Import بدون SSR استفاده کنید:
import dynamic from 'next/dynamic'
const AriaCropper = dynamic(() => import('aria-cropper'), { ssr: false })
  • اگر بسته را به‌صورت local و TypeScript منتشر کرده‌اید و Next نتوانست آن را transpile کند، در next.config.js از transpilePackages کمک بگیرید:
// next.config.js
module.exports = {
  transpilePackages: ['aria-cropper'],
}
  • برای استایل CropperJS، import 'cropperjs/dist/cropper.css' را در یک Client Component یا در globals.css انجام دهید.

اعتبارسنجی و بهینه‌سازی کیفیت

  • حداقل ابعاد: با minWidth/minHeight ورودی‌هایی که خیلی کوچک‌اند را رد کنید.
  • جلوگیری از Upscale: با allowUpscale={false} از افت کیفیت جلوگیری کنید.
  • کیفیت تطبیقی: برای JPEG/WebP بین 0.75 .. 0.95 را تست کنید؛ بالاتر معمولاً بهبود محسوسی ندارد.
  • پس‌زمینه: برای JPEG اگر تصویر شفاف است، با background یک رنگ پس‌زمینه تعریف کنید (مثلاً #fff).

دسترس‌پذیری (A11y)

  • همه دکمه‌ها باید label مناسب داشته باشند (i18n را تنظیم کنید).
  • با کیبورد بتوان به دکمه‌های «انتخاب فایل»، «تأیید برش»، «حذف» و… دسترسی داشت.
  • برای تصویر نهایی alt مناسب در مصرف‌کننده‌ی کامپوننت تنظیم کنید.

بین‌المللی‌سازی (i18n)

export const defaultI18n = {
  upload: 'انتخاب تصویر',
  replace: 'تغییر تصویر',
  remove: 'حذف',
  cancel: 'انصراف',
  confirm: 'تأیید',
  cropTitle: 'برش تصویر',
  errors: {
    INVALID_TYPE: 'نوع فایل نامعتبر است.',
    FILE_TOO_LARGE: 'حجم فایل بیش از حد مجاز است.',
    DIMENSION_TOO_SMALL: 'ابعاد تصویر برای این برش کافی نیست.',
    DECODE_FAILED: 'خواندن تصویر ناموفق بود.',
    CROP_FAILED: 'برش تصویر ناموفق بود.',
    UNSUPPORTED_FORMAT: 'فرمت پشتیبانی نمی‌شود.',
  },
}

سفارشی‌سازی UI و تم

  • کلاس‌ها را با Tailwind یا CSS سفارشی کنید.
  • حالت نمایش Modal یا Inline را با uiMode تغییر دهید.
  • از cropperOptions برای تغییر رفتار CropperJS (مثل viewMode, dragMode, guides, zoomable، و …) استفاده کنید.

مثال:

<AriaCropper
  requiredWidth={1600}
  requiredHeight={900}
  outputFormat="jpeg"
  background="#fff"
  cropperOptions={{
    viewMode: 1,
    dragMode: 'move',
    autoCropArea: 1,
    guides: true,
    zoomOnWheel: true,
  }}
/>

سناریوهای ادغام پیشرفته

ادغام با فرم‌های چندمرحله‌ای

  • مقدار ImageChangeSet را در استیت فرم نگه دارید و در مرحله‌ی نهایی با FormData ارسال کنید.
  • اگر کاربر به مرحله قبل برگشت، مقدار keep/remove را همین‌جا اعمال کنید.

ادغام با ImageUploaderV3 (نمونه پیشنهادی)

<ImageUploaderV3
  value={form.profilePhoto || null}
  onChange={(change) => setPhotoChange(change)}
  requiredWidth={1200}
  requiredHeight={1200}
  previewWidth={300}
  outputFormat="webp"
/>
  • ImageUploaderV3 می‌تواند درون خود از AriaCropper (inline یا modal) استفاده کند و خروجی را به شکل ImageChangeSet بالا بدهد.

رفع اشکال (Troubleshooting)

«Module not found: Can't resolve 'aria-cropper'»

  • نصب را بررسی کنید: npm i aria-cropper cropperjs

  • اگر پکیج لوکال است، package.json باید مسیرهای درست داشته باشد:

    • "main": "dist/index.js"
    • "module": "dist/index.mjs"
    • "types": "dist/index.d.ts"
  • در Next.js ممکن است به transpilePackages نیاز باشد.

«window is not defined» در SSR

  • از Dynamic Import با ssr:false استفاده کنید (بخش SSR بالا).

تصویر خروجی تار است / وارونه شده

  • Upscale را غیرفعال کنید یا کیفیت را افزایش دهید.
  • برای Orientation، مرورگرهای مدرن EXIF را تا حدی لحاظ می‌کنند؛ اگر ورودی موبایل مشکل دارد، قبل از crop از createImageBitmap یا کتابخانه‌های EXIF جهت تصحیح استفاده کنید.

WebP نمایش نمی‌شود

  • مرورگرهای امروزی WebP را پشتیبانی می‌کنند؛ اگر مجبور به سازگاری هستید، یک fallback به JPEG در سمت سرور در نظر بگیرید.

امنیت و اعتبارسنجی سمت سرور

  • MIME-Type را در سرور دوباره بررسی کنید (صرفاً به ورودی کلاینت اعتماد نکنید).
  • حداکثر اندازه فایل، حداکثر ابعاد (مگاپیکسل) و پسوند را کنترل کنید.
  • نام فایل را sanitize کنید؛ یا نام‌دهی را در سرور انجام دهید.
  • اگر در فضای عمومی آپلود می‌کنید، URLهای امضا شده/موقتی یا مسیرهای امن را مد نظر داشته باشید.

Roadmap

  • ناحیه برش دایره‌ای/بیضوی
  • چندتصویری (گالری)
  • پیش‌پردازش EXIF درون‌ساخت
  • پشتیبانی از Worker/OffscreenCanvas برای Decode سنگین

مشارکت (Contributing)

  • PRها و Issueها پذیرفته می‌شوند.
  • قبل از PR: pnpm build / npm run build و اجرای lint/test.

لایسنس

MIT © AriaCropper Authors