Skip to content

jayantbh/react-multi-cropper

Repository files navigation

React Multi Cropper

A component for allowing multiple cropping regions on a single image, powered by fabric.

build version size downloads

WIP: Certain aspects are in development, and may be incomplete or buggy.
It should be fine for most purposes.

screenshot

Installation

yarn add react-multi-cropper fabric

Development

yarn install
yarn start # in one terminal
yarn serve # in another

Then open http://localhost:3000 in your browser.
If 3000 is occupied, the terminal output will show you what URL the serve command is serving on.

Usage

See the examples/index.tsx file.

Functionality

Draw rectangular regions on an image to obtain the selected area as a base64 encoded data URL.
Multiple regions can be obtained by drawing multiple boxes.

The component is responsive, so the image dimensions can use relative units (like %), and the cropping regions/rectangles should stay in place w.r.t. the image.

The cropping logic is aware of the device pixel ratio, so you won't get blurry crops on a MacBook or phone.

Documentation

Default Usage

const Cropper = ({ imageUrl }: { imageUrl: string ) => {
  const [boxes, setBoxes] = useState<CropperBox[]>([]);
  const [imageMap, setImageMap] = useState<CropperBoxDataMap>({});

  const updateBoxes = (_, __, _boxes) => setBoxes(_boxes);

  return (
    <div>
      <MultiCrops
        src={imageUrl}
        zoom={1}
        boxes={boxes}
        onChange={updateBoxes}
        onCrop={(e, map) => setImageMap(map)}
        onLoad={(map) => setImageMap(map)}
      />
      {boxes.map((box, i) =>
        !!imageMap[box.id] && <img src={imageMap[box.id]} key={i} />
      )}
    </div>
  );
};

How to reset zoom, rotation, and pan?

In examples/index.tsx, you'll see an implementation of reset.

const reset = () => {
  setRotation(0);
  setZoom(1);
  resetCenter();
};

setRotation, and setZoom are simple state setting functions obtained from a useState.
resetCenter needs a few more lines.
Note: Due to the current implementation, when resetting the component you must reset rotation before zoom to avoid bugs. It would be changed in the future to use a different update mechanism.

The second argument of onLoad provides the reset handler. To call this from anywhere, you may want to assign this to a ref.

// Initialize a ref to store the function
const resetCenterRef = useRef(() => {});
const resetCenter = resetCenterRef.current;

// Call function anywhere
resetCenter();

// Obtain the function from onLoad
onLoad={(map, reset) => {
  setImageMap(map);
  resetCenterRef.current = reset;
}}

Props for the MultiCrops component

type CropperProps = {
  cropperRef?: MutableRefObject<fabric.Canvas | null>;
  src: string;
  zoom?: number;
  rotation?: number; // degrees
  cropScale?: number; // the scale of the resultant cropped images
  boxes: CropperBox[];
  onChange?: UpdateFunction;
  onDelete?: UpdateFunction;
  onBoxMouseEnter?: UpdateFunction;
  onBoxMouseLeave?: UpdateFunction;
  onBoxClick?: UpdateFunction;
  onLoad?: ImgOnLoadWithImageData;
  onCrop?: CropTriggerFunctionWithImageData;
  onZoomGesture?: (newZoom: number) => any;
  containerClassName?: string;
  containerStyles?: CSSProperties;
  imageStyles?: CSSProperties;
  cursorMode?: CropperCursorMode;
  disableKeyboard?: boolean;
  disableMouse?: {
    all?: boolean;
    zoom?: boolean;
    pan?: boolean;
    draw?: boolean;
  };
  boxInView?: { id?: string; rotate?: boolean; panInView?: boolean };
};

All the above types have been exported from the module.

Notes:

  • The onLoad prop is optional, but useful for a few things.
    1. To determine that the image has indeed loaded, same as an img tag.
    2. Get access to the internal fabric object.
    3. To get a resetCenter handler to reset the panned position of the image.
  • You need to pass a function the onCrop prop if you want the default functionality to work out of the box. It will be called when a drawing operation was completed. This will be needed if you want to receive the image payload after a cropping action was done.
    • It is however optional, in case you want the box drawing to be controlled externally.
  • The function supplied to onCrop will be called when a drawing operation was completed. This will be needed if you want to receive the image payload after a cropping action was done.
    • The first argument is a CropperEvent event that tells you all you need to know about the event that was triggered to cause this function to fire.
    • The second argument is a dictionary of box.id's and their respective base64 encoded image contents.
  • Removed the ability to change a box after drawing it. The existing functionality was not stable enough to leave it, and leaving it in caused more issues than were manageable.
  • Mouse/Touchpad wheel to pan/zoom is supported. Example for wheel zoom is present in the examples/index.tsx.
  • Arrow Keys based pan/zoom is supported.
  • It would be advisable to memoize the functions passed to the component.
  • If you want to pass a box for reasons other than getting the imageData out of it, add the noImage: true key-val to it.