Recipes
#
Stencil Tuning#
Change the stencilThere are only two default stencil components now, RectangleStencil
(default) and CircleStencil
, but you can easily create your own stencil himself.
To specify stencil component you should pass it to stencilComponent
prop. For globally registered component just pass their name:
<Cropper stencilComponent={CircleStencil}/>
import React, { useState } from 'react';import { CircleStencil, Cropper } from 'react-advanced-cropper';
export const Example = () => { const [image] = useState( 'https://images.unsplash.com/photo-1599140849279-1014532882fe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1300&q=80', );
return ( <Cropper src={image} stencilComponent={CircleStencil} /> )};
#
Pass props to the stencilTo pass any props to the stencil pass them as object to stencilProps
prop.
For example, that's how you can set aspect ratio:
<Cropper stencilProps={{ aspectRatio: 6/9, movable: false, resizable: false }}/>
The list of available props varies from one stencil component to another. The props of the default stencils are available in the documentation (RectangleStencil, CircleStencil)
#
Set the aspect ratioThis library supports setting either aspect ratio value or aspect ratio range (i.e. minimum and maximums aspect ratio values).
Generally speaking, aspect ratio is the property of the stencil, not the cropper, so the possibility to set aspect ratio
entirely depends of used stencil. For example, CircleStencil
aspect ratio can't be customized, it's always a circle, at the same time
RectangleStencil
has not any restrictions on aspect ratios.
The examples below are written for RectangleStencil
.
#
Fixed aspect ratio<Cropper stencilProps={{ aspectRatio: 1/1, }}/>
#
Aspect ratio range<Cropper stencilProps={{ aspectRatio: { minimum: 16/8, maximum: 4/8 } }}/>
#
Add the gridYou can enable the stencil grid for the default stencils to facilitate the selecting of the cropped area.
<Cropper stencilProps={{ grid: true }}/>
#
Getting the result#
First approachTo get the current stencil coordinates and canvas with cropped image
you can call the cropper methods getCoordinates
and getCanvas
respectively.
Click at the button Crop Image below to see this method in action
import React, { useState, useRef } from 'react';import { Cropper, CropperRef, Coordinates } from 'react-advanced-cropper';
export const Example = () => { const cropperRef = useRef<CropperRef>(null);
const [coordinates, setCoordinates] = useState<Coordinates | null>(null); const [image, setImage] = useState<string>();
const [src, setSrc] = useState( 'https://images.unsplash.com/photo-1599140849279-1014532882fe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1300&q=80', );
const onCrop = () => { if (cropperRef.current) { setCoordinates(cropperRef.current.getCoordinates()); // You are able to do different manipulations at a canvas // but there we just get a cropped image, that can be used // as src for <img/> to preview result setImage(cropperRef.current.getCanvas()?.toDataURL()); } };
return ( <Cropper ref={cropperRef} src={src} />; )};
#
Second approachAlso there is alternative to get the cropper result. You can get the coordinates of stencil and canvas with cropped image by processing one of numerous callbacks.
The example below shows processing the onChange
event.
tip
Though, be careful. Getting the canvas result in onChange
callback can heavily affect the performance, it's better to use this approach to
get the coordinates, transitions and other plain properties. If you still need to get the canvas result in this callback, use debounce.
import { Cropper, CropperRef } from 'react-advanced-cropper';
export const Example = () => { const [coordinates, setCoordinates] = useState<Coordinates | null>(null); const [image, setImage] = useState<string>();
const [src] = useState( 'https://images.unsplash.com/photo-1599140849279-1014532882fe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1300&q=80', );
const onChange = (cropper: CropperRef) => { setCoordinates(cropper.getCoordinates()); setImage(cropper.getCanvas()?.toDataURL()); };
return ( <Cropper src={src} onChange={onChange} />; )};
#
Resize the resultIf you use coordinates only, the result should be scaled on server-side, but if you use canvas you may prefer to resize the result on client-side.
The most simplest way to do it is pass the restrictions for the result size in getCanvas
method options.
const canvas = cropper.getCanvas({ minHeight: 0, minWidth: 0, maxHeight: 2048, maxWidth: 2048,})
If you need to set the specific height and width use height
and width
attributes.
But you should note that canvas will have the same aspect ratio as the stencil, so the result size may
be different than one that you have set.
const canvas = cropper.getCanvas({ height: 256, width: 256,})
It uses default canvas image scaling procedure under the hood. If the result doesn't suit you, try to use the external libraries to resize image (pica, downscale and etc.)
#
Preview the resultTo implement real-time preview of cropping result you can use CropperPreview
component.
There are currently exists two competing approaches to use <CropperPreview/>
. Which one will
remain still not decided, so feel free to use any of them.
- Update
- Stateful
- Two References
info
This approach is available starting from 0.20.0
version
import React, { useRef, useState } from 'react';import { CropperRef, CropperPreviewRef, Cropper } from 'react-advanced-cropper';
export const Example = () => { const previewRef = useRef<CropperPreviewRef>(null); const cropperRef = useRef<CropperRef>(null); const [src, setSrc] = useState( 'https://images.unsplash.com/photo-1623432532623-f8f1347d954c?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=387&q=80', );
const onUpdate = (cropper: CropperRef) => { previewRef.current?.update(cropper); };
return ( <div> <Cropper className="cropper" stencilProps={{ aspectRatio: 1 }} src={src} onUpdate={onUpdate} /> <CropperPreview ref={previewRef} className="preview" /> </div> );};
import React, { useRef, useState } from 'react';import { Cropper, CropperPreview, CropperState, CropperImage, CropperTransitions,} from 'react-advanced-cropper';
interface PreviewState { state: CropperState | null; image: CropperImage | null; transitions: CropperTransitions | null; loading?: false; loaded?: false;}
export const Example = () => { const [previewState, setPreviewState] = useState<PreviewState>({ state: null, image: null, transitions: null });
const [src, setSrc] = useState( 'https://images.unsplash.com/photo-1623432532623-f8f1347d954c?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=387&q=80', );
const onUpdate = () => { setPreviewState({ state: cropper.getState(), image: cropper.getImage(), transitions: cropper.getTransitions(), loaded: cropper.isLoaded(), loading: cropper.isLoading(), }); };
return ( <div> <Cropper ref={cropperRef} className="cropper" stencilProps={{ aspectRatio: 1 }} src={src} onUpdate={onUpdate} /> <CropperPreview ref={previewRef} className="preview" {...previewState} /> </div> );};
warning
This approach is deprecated since 0.20.0
import React, { useRef, useState } from 'react';import { CropperRef, CropperPreviewRef, Cropper } from 'react-advanced-cropper';
export const Example = () => { const previewRef = useRef<CropperPreviewRef>(null); const cropperRef = useRef<CropperRef>(null);
const [src, setSrc] = useState( 'https://images.unsplash.com/photo-1623432532623-f8f1347d954c?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=387&q=80', );
const onUpdate = () => { previewRef.current?.refresh(); };
return ( <div> <Cropper ref={cropperRef} className="cropper" stencilProps={{ aspectRatio: 1 }} src={src} onUpdate={onUpdate} /> <CropperPreview ref={previewRef} cropper={cropperRef} className="preview" /> </div> );};
As an alternative you can add the preview component inside the cropper wrapper, just like the slider in this tutorial. It can even improve the performance slightly.
#
Load image from a discThe image loading doesn't depend at this library and can be completed by a numerous ways. There will be considered only one of them.
That's what you will get:
Warning!
Blob is used in the example below. Ensure that browsers that you support can fully handle it or use the corresponding polyfill.
- Simple Example
- Advanced Example
import React, { ChangeEvent, useState, useRef, useEffect } from 'react';import { CropperRef, Cropper } from 'react-advanced-cropper';
interface Image { type?: string; src: string;}
export const UploadExample = () => { const inputRef = useRef<HTMLInputElement>(null);
const [image, setImage] = useState<Image | null>(null);
const onUpload = () => { if (inputRef.current) { inputRef.current.click(); } };
const onLoadImage = (event: ChangeEvent<HTMLInputElement>) => { // Reference to the DOM input element const { files } = event.target;
// Ensure that you have a file before attempting to read it if (files && files[0]) { // Create the blob link to the file to optimize performance: const blob = URL.createObjectURL(files[0]);
// Get the image type from the extension. It's the simplest way, though be careful it can lead to an incorrect result: setImage({ src: blob, type: files[0].type }) } // Clear the event target value to give the possibility to upload the same image: event.target.value = ''; };
useEffect(() => { // Revoke the object URL, to allow the garbage collector to destroy the uploaded before file return () => { if (image && image.src) { URL.revokeObjectURL(image.src); } }; }, [image]);
return ( <div className="upload-example"> <Cropper className="upload-example__cropper" src={image && image.src} /> <div className="buttons-wrapper"> <button className="button" onClick={onUpload}> <input ref={inputRef} type="file" accept="image/*" onChange={onLoadImage} /> Upload image </button> </div> </div> );};
Notice!
The function getMimeType
is imported from from advanced-cropper
, not from react-advanced-cropper
. It's the dependency of react-advanced-cropper
. And it's
preferred way to import such dependencies if you use a bundler (in the future it will be only possible way).
import React, { ChangeEvent, useState, useRef, useEffect } from 'react';import { CropperRef, Cropper } from 'react-advanced-cropper';import { getMimeType } from 'advanced-cropper/extensions/mimes';
interface Image { type?: string; src: string;}
export const UploadExample = () => { const inputRef = useRef<HTMLInputElement>(null);
const [image, setImage] = useState<Image | null>(null);
const onUpload = () => { if (inputRef.current) { inputRef.current.click(); } };
const onLoadImage = (event: ChangeEvent<HTMLInputElement>) => { // Reference to the DOM input element const { files } = event.target;
// Ensure that you have a file before attempting to read it if (files && files[0]) { // Create the blob link to the file to optimize performance: const blob = URL.createObjectURL(files[0]);
// Remember the fallback type: const typeFallback = files[0].type;
// Create a new FileReader to read this image binary data const reader = new FileReader();
// Define a callback function to run, when FileReader finishes its job reader.onload = (e) => { // Note: arrow function used here, so that "this.image" refers to the image of Vue component setImage({ // Read image as base64 and set it as src: src: blob, // Determine the image type to preserve it during the extracting the image from canvas: type: getMimeType(e.target?.result, typeFallback), }); }; // Start the reader job - read file as a data url (base64 format) and get the real file type reader.readAsArrayBuffer(files[0]); } // Clear the event target value to give the possibility to upload the same image: event.target.value = ''; };
useEffect(() => { // Revoke the object URL, to allow the garbage collector to destroy the uploaded before file return () => { if (image && image.src) { URL.revokeObjectURL(image.src); } }; }, [image]);
return ( <div className="upload-example"> <Cropper className="upload-example__cropper" src={image && image.src} /> <div className="buttons-wrapper"> <button className="button" onClick={onUpload}> <input ref={inputRef} type="file" accept="image/*" onChange={onLoadImage} /> Upload image </button> </div> </div> );};
#
Upload image to a serverThe preferred method to upload image to the server is using a blob (for IE-11 you should use polyfill). The detailed explanation why you shouldn't use the data-url you can read in the great answer on Stackoverflow.
There are different approaches to implement image uploading because it depends on your backend. The one of them is presented below.
tip
Look the network section in your developer tools to examine the sent request.
import React, { useState, useRef } from 'react';import { CropperRef, Cropper } from 'react-advanced-cropper';
export const UploadExample = () => { const cropperRef = useRef<CropperRef>(null);
const [image] = useState( 'https://images.unsplash.com/photo-1604335079441-274c03ad99a1?ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&q=80', );
const onUpload = () => { const canvas = cropperRef.current?.getCanvas(); if (canvas) { const form = new FormData(); canvas.toBlob((blob) => { if (blob) { form.append('file', blob); fetch('http://example.com/upload/', { method: 'POST', body: form, }); } }, 'image/jpeg'); } };
return ( <Cropper ref={cropperRef} src={image} /> );};