Skip to content

Latest commit

 

History

History
566 lines (442 loc) · 15.1 KB

recipes.mdx

File metadata and controls

566 lines (442 loc) · 15.1 KB
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';

Recipes

Stencil Tuning

Change the stencil

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}
		/>
	)
};

Pass props to the stencil

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)

Set the aspect ratio

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.

Fixed aspect ratio

<Cropper
	stencilProps={{
		aspectRatio: 1/1,
	}}
/>

Aspect ratio range

<Cropper
	stencilProps={{
		aspectRatio: {
			minimum: 16/8,
			maximum: 4/8
		}
	}}
/>

Add the grid

You 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 approach

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}
		/>;
	)
};

Second approach

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}
		/>;
	)
};

Resize the result

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.)

Preview the result

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.

Load image from a disc

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>
	);
};

Upload image to a server

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}
		/>
	);
};