title | sidebar_position |
---|---|
Recipes |
1 |
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
import { ChangingStencilExample } from '../../src/components/examples/ChangingStencilExample'; import { StencilGridExample } from '../../src/components/examples/StencilGridExample'; import { GettingResultExample } from '../../src/components/examples/GettingResultExample'; import { GettingResultManualExample } from '../../src/components/examples/GettingResultManualExample'; import { ResizeResultExample } from '../../src/components/examples/ResizeResultExample'; import { PreviewResultExample } from '../../src/components/examples/PreviewResultExample'; import { LoadImageExample } from '../../src/components/examples/LoadImageExample'; import { UploadExample } from '../../src/components/examples/UploadExample';
There 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}
/>
)
};
To 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)
This 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
.
<Cropper
stencilProps={{
aspectRatio: 1/1,
}}
/>
<Cropper
stencilProps={{
aspectRatio: {
minimum: 16/8,
maximum: 4/8
}
}}
/>
You can enable the stencil grid for the default stencils to facilitate the selecting of the cropped area.
<Cropper
stencilProps={{
grid: true
}}
/>
To 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}
/>;
)
};
Also 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}
/>;
)
};
If 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.)
To 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.
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>
);
};
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.
The 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 Warning! Blob is used in the example below. Ensure that browsers that you support can fully handle it or use the corresponding polyfill. :::
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>
);
};
:::note 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>
);
};
The 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}
/>
);
};