Skip to content

Repository files navigation

react-avatar-editor

npm version Downloads

Avatar / profile picture cropping component for React. Resize, crop and rotate your uploaded image using a simple and clean user interface.

Features

  • Fully typed, written in TypeScript
  • Works with React 17, 18, and 19
  • Resize, crop, and rotate
  • Rounded or square crop area
  • Built-in loading indicator
  • useAvatarEditor hook for easy access to the editor API
  • Zero runtime dependencies

Install

npm i react-avatar-editor

Usage

Basic

import AvatarEditor from 'react-avatar-editor'

function MyEditor() {
  return (
    <AvatarEditor
      image="https://example.com/photo.jpg"
      width={250}
      height={250}
      border={50}
      color={[255, 255, 255, 0.6]}
      scale={1.2}
      rotate={0}
    />
  )
}

With useAvatarEditor hook

The useAvatarEditor hook provides a clean API to access the editor's methods without managing refs manually. All methods return null if the editor isn't ready or no image is loaded.

import AvatarEditor, { useAvatarEditor } from 'react-avatar-editor'

function MyEditor() {
  const editor = useAvatarEditor()

  const handleSave = () => {
    const canvas = editor.getImageScaledToCanvas()
    if (canvas) {
      const dataUrl = canvas.toDataURL()
      // upload dataUrl to your server
    }
  }

  return (
    <div>
      <AvatarEditor
        ref={editor.ref}
        image="https://example.com/photo.jpg"
        width={250}
        height={250}
        border={50}
        scale={1.2}
      />
      <button onClick={handleSave}>Save</button>
    </div>
  )
}

Hook methods

Method Returns Description
ref RefObject Pass this to the ref prop of AvatarEditor.
getImage() HTMLCanvasElement | null The cropped image at the original resolution.
getImageScaledToCanvas() HTMLCanvasElement | null The cropped image scaled to the editor dimensions.
getCroppingRect() object | null The crop area as { x, y, width, height } (0–1 range).

With ref (alternative)

If you prefer using refs directly:

import { useRef } from 'react'
import AvatarEditor, { type AvatarEditorRef } from 'react-avatar-editor'

function MyEditor() {
  const editor = useRef<AvatarEditorRef>(null)

  return (
    <div>
      <AvatarEditor
        ref={editor}
        image="https://example.com/photo.jpg"
        width={250}
        height={250}
      />
      <button
        onClick={() => {
          const canvas = editor.current?.getImageScaledToCanvas()
        }}
      >
        Save
      </button>
    </div>
  )
}

Saving as JPEG (smaller files)

canvas.toDataURL() and canvas.toBlob() produce a PNG by default. PNG is lossless, so a cropped photo can end up several times larger than the JPEG you uploaded. Pass a type and quality to get a JPEG instead:

const canvas = editor.getImageScaledToCanvas()
canvas?.toBlob(
  (blob) => {
    if (!blob) return
    const formData = new FormData()
    formData.append('avatar', blob, 'avatar.jpg')
    fetch('/api/avatar', { method: 'POST', body: formData })
  },
  'image/jpeg',
  0.9, // quality, 0–1
)

Also pick the right method for your use case:

  • getImageScaledToCanvas() returns the crop at the editor's width × height. This is usually what you want for avatars.
  • getImage() returns the crop at the original image resolution. With a large camera photo that can be thousands of pixels wide, so the file will be large even as a JPEG.

JPEG has no transparency, so transparent areas (for example in an uploaded PNG) turn black. Set backgroundColor to fill them with a color, or keep PNG.

Responsive size

width and height also accept a percentage of the parent element. The editor measures its parent and follows it when it resizes. A percentage sets the size of the whole canvas, and the crop area is that minus border on each side.

<div style={{ width: '100%', maxWidth: 600 }}>
  <AvatarEditor image={image} width="100%" height={300} border={20} />
</div>

The canvas becomes display: block in this mode. A percentage height needs a parent with a fixed height, as in CSS. Resizing keeps the image, position and zoom. The crop area just changes size.

With drag and drop

Using react-dropzone:

import AvatarEditor from 'react-avatar-editor'
import Dropzone from 'react-dropzone'

function MyEditor() {
  const [image, setImage] = useState('https://example.com/photo.jpg')

  return (
    <Dropzone onDrop={([file]) => setImage(file)} noClick noKeyboard>
      {({ getRootProps, getInputProps }) => (
        <div {...getRootProps()}>
          <AvatarEditor width={250} height={250} image={image} />
          <input {...getInputProps()} />
        </div>
      )}
    </Dropzone>
  )
}

Animated rotation

The rotate prop can be animated using any animation library. Here's an example with motion:

import { useState } from 'react'
import { useMotionValue, useSpring, useMotionValueEvent } from 'motion/react'
import AvatarEditor from 'react-avatar-editor'

function MyEditor() {
  const [rotate, setRotate] = useState(0)
  const [animatedRotate, setAnimatedRotate] = useState(0)

  const rotateMotion = useMotionValue(0)
  const rotateSpring = useSpring(rotateMotion, { stiffness: 200, damping: 25 })

  useMotionValueEvent(rotateSpring, 'change', (v) => setAnimatedRotate(v))

  if (rotateMotion.get() !== rotate) {
    rotateMotion.set(rotate)
  }

  return (
    <>
      <AvatarEditor
        image="https://example.com/photo.jpg"
        rotate={animatedRotate}
      />
      <button onClick={() => setRotate((r) => r - 90)}>↺</button>
      <button onClick={() => setRotate((r) => r + 90)}>↻</button>
    </>
  )
}

Props

Prop Type Default Description
image string | File The URL or File object of the image to edit.
width number | string 200 Width of the crop area in pixels, or a percentage like "100%" (see Responsive size).
height number | string 200 Height of the crop area in pixels, or a percentage like "50%".
border number | number[] 25 Border size around the crop area. Use an array [horizontal, vertical] for different values.
borderRadius number 0 Border radius of the crop area. Set to width / 2 for a circle.
color number[] [0, 0, 0, 0.5] RGBA color of the crop mask overlay.
borderColor number[] RGBA color of the 1px border around the crop area. No border if omitted.
backgroundColor string Background color for transparent images (CSS color string).
scale number 1 Zoom level. 1 = fit, > 1 = zoom in, < 1 = zoom out (requires disableBoundaryChecks).
rotate number 0 Rotation in degrees.
position { x, y } Center of the crop area (0–1 range). Set this + onPositionChange for controlled panning.
style CSSProperties Additional CSS styles for the canvas element.
crossOrigin string crossOrigin attribute for the image. Use "anonymous" for CORS images.
showGrid boolean false Show a rule-of-thirds grid overlay.
gridColor string "#666" Color of the grid lines.
disableBoundaryChecks boolean false Allow the image to be moved outside the crop boundary.
disableHiDPIScaling boolean false Disable devicePixelRatio scaling. Can improve performance on mobile.
disableCanvasRotation boolean true When false, the canvas resizes to fit the rotated image.
onLoadStart () => void Called when image loading begins.
onLoadSuccess (image) => void Called when the image loads successfully.
onLoadFailure () => void Called when the image fails to load.
onImageReady () => void Called when the image is first painted on the canvas.
onImageChange () => void Called on every visual change (drag, scale, rotate, etc.).
onMouseUp () => void Called when the user releases the mouse after dragging.
onMouseMove (event) => void Called on every mouse/touch move while dragging.
onPositionChange (position) => void Called when the crop position changes. Receives { x, y }.
onRequestScaleChange (scale) => void Called when the user zooms via +/- keys, pinch, or wheel. Receives the requested new scale.
enableWheelZoom boolean false Zoom with the mouse wheel / trackpad. Requires onRequestScaleChange.
keyboardStep number 1 Pixels to move per arrow key press. Shift multiplies by 10.

Contributing

pnpm install          # install dependencies
pnpm build            # build the library
pnpm lint             # run oxlint
pnpm fmt              # format with oxfmt
pnpm demo:dev         # run demo at localhost:3000

Kudos

Thanks to all contributors:

dan-lee, mtlewis, jakerichan, hu9o, ggwzrd, nmn, kukagg, benwiley4000, ruipserra, rdw, sktt, RKJuve, tibotiber, aumayr, yamafaktory, codedmart, chengyin, MahdiHadrich, jyash97, fivenp, pvcresin, shakaman, oyeanuj, dev-nima, kpbp, DedaDev, vitbokisch, pekq, MateusZitelli, kimon89, xaviergonz, jbrumwell, luisrudge, bytor99999, Mitelak, sahanDissanayake, sle-c, YacheLee, yogthesharma, taro-shono, tanguyantoine, lulzsun, mloeks, metacortex, exiify, thinhvoxuan, tvankith, yarikgenza, zhipenglin, TheMcMurder, notjosh, xulww, jimniels, jeffkole, deadlyicon, velezjose, lixiaoyan, brigand, florapdx, kuhelbeher, chris-rudmin, bluej100, dehbmarques, kimorq

About

Small avatar & profile picture component. Resize and crop uploaded images using a intuitive user interface.

Topics

Resources

Stars

2.5k stars

Watchers

31 watching

Forks

Releases

Packages

Used by

Contributors

Languages