UNPKG

13.2 kBMarkdownView Raw
1# React Image Crop
2
3An image cropping tool for React with no dependencies.
4
5[![React Image Crop on NPM](https://img.shields.io/npm/v/react-image-crop.svg)](https://www.npmjs.com/package/react-image-crop)
6
7[Demo using class](https://codesandbox.io/s/72py4jlll6) |
8[Demo using hook](https://codesandbox.io/s/react-image-crop-demo-with-react-hooks-y831o)
9
10![ReactCrop Demo](https://raw.githubusercontent.com/DominicTobias/react-image-crop/master/crop-demo.gif)
11
12## Table of Contents
13
141. [Features](#features)
152. [Installation](#installation)
163. [Usage](#usage)
174. [Example](#example)
185. [CDN](#cdn)
196. [Props](#props)
207. [FAQ](#faq)
21 1. [What about showing the crop on the client?](#what-about-showing-the-crop-on-the-client)
22 2. [How to correct image EXIF orientation/rotation](#how-to-correct-image-exif-orientationrotation)
23 3. [How to filter, rotate and annotate](#how-to-filter-rotate-and-annotate)
24 4. [How can I center the crop?](#how-can-i-center-the-crop)
258. [Contributing / Developing](#contributing--developing)
26
27## Features
28
29- Responsive (you can use pixels or percentages).
30- Touch enabled.
31- Free-form or fixed aspect crops.
32- Keyboard support for nudging selection.
33- No dependencies/small footprint (5KB gzip).
34- Min/max crop size.
35
36If React Image Crop doesn't cover your requirements then I advise to take a look at [Pintura](https://gumroad.com/a/611955827). It features cropping, rotating, filtering, annotation, and lots more.
37
38[Learn more about Pintura here](https://gumroad.com/a/611955827)
39
40## Installation
41
42```
43npm i react-image-crop --save
44```
45
46## Usage
47
48Include the main js module:
49
50```js
51import ReactCrop from 'react-image-crop';
52```
53
54Include either `dist/ReactCrop.css` or `ReactCrop.scss`.
55
56```js
57import 'react-image-crop/dist/ReactCrop.css';
58// or scss:
59import 'react-image-crop/lib/ReactCrop.scss';
60```
61
62## Example
63
64```js
65function CropDemo({ src }) {
66 const [crop, setCrop] = useState({ aspect: 16 / 9 });
67 return <ReactCrop src={src} crop={crop} onChange={newCrop => setCrop(newCrop)} />;
68}
69```
70
71See the [sandbox demo](https://codesandbox.io/s/72py4jlll6) for a more complete example.
72
73## CDN
74
75If you prefer to include ReactCrop globally by marking `react-image-crop` as external in your application, then include `react-image-crop` from the following CDN:
76
77- [**unpkg**](https://unpkg.com/react-image-crop/)
78
79```html
80<script src="https://unpkg.com/react-image-crop/dist/ReactCrop.min.js"></script>
81```
82
83Note when importing the script globally using a `<script>` tag access the component with `ReactCrop.Component`.
84
85## Props
86
87#### src (required)
88
89```jsx
90<ReactCrop src="path/to/image.jpg" />
91```
92
93You can of course pass a blob url (using `URL.createObjectURL()` and `URL.revokeObjectURL()`) or base64 data.
94
95#### onChange(crop, percentCrop) (required)
96
97A callback which happens for every change of the crop (i.e. many times as you are dragging/resizing). Passes the current crop state object.
98
99Note you _must_ implement this callback and update your crop state, otherwise nothing will change!
100
101```js
102onChange = crop => {
103 this.setState({ crop });
104};
105```
106
107You can use either `crop` or `percentCrop`, the library can handle either interchangeably. Percent crops will be drawn using percentages, not converted to pixels.
108
109#### crop (required\*)
110
111All crop params are initially optional.
112
113\* _While you can initially omit the crop object, any subsequent change will need to be saved to state in the `onChange` and passed into the component._
114
115```js
116crop: {
117 unit: 'px', // default, can be 'px' or '%'
118 x: 130,
119 y: 50,
120 width: 200,
121 height: 200
122}
123
124<ReactCrop src="path/to/image.jpg" crop={this.state.crop} />
125```
126
127If you want a fixed aspect you can either omit `width` and `height`:
128
129```js
130crop: {
131 aspect: 16 / 9;
132}
133```
134
135Or specify one or both of the dimensions:
136
137```js
138crop: {
139 aspect: 16/9,
140 width: 50,
141}
142```
143
144If you specify just one of the dimensions, the other will be calculated for you.
145
146```js
147crop: {
148 unit: '%',
149 width: 50,
150 height: 50,
151}
152```
153
154`unit` is optional and defaults to pixels `px`. It can also be percent `%`. In the above example we make a crop that is 50% of the rendered image size. Since the values are a percentage of the image, it will only be a square if the image is also a square.
155
156#### minWidth (optional)
157
158A minimum crop width, in pixels.
159
160#### minHeight (optional)
161
162A minimum crop height, in pixels.
163
164#### maxWidth (optional)
165
166A maximum crop width, in pixels.
167
168#### maxHeight (optional)
169
170A maximum crop height, in pixels.
171
172#### keepSelection (optional)
173
174If true is passed then selection can't be disabled if the user clicks outside the selection area.
175
176#### disabled (optional)
177
178If true then the user cannot resize or draw a new crop. A class of `ReactCrop--disabled` is also added to the container for user styling.
179
180#### locked (optional)
181
182If true then the user cannot create or resize a crop, but can still drag the existing crop around. A class of `ReactCrop--locked` is also added to the container for user styling.
183
184#### className (optional)
185
186A string of classes to add to the main `ReactCrop` element.
187
188#### style (optional)
189
190Inline styles object to be passed to the image wrapper element.
191
192#### imageStyle (optional)
193
194Inline styles object to be passed to the image element.
195
196#### imageAlt (optional)
197
198Add an alt attribute to the image element.
199
200#### onComplete(crop, percentCrop) (optional)
201
202A callback which happens after a resize, drag, or nudge. Passes the current crop state object.
203
204`percentCrop` is the crop as a percentage. A typical use case for it would be to save it so that the user's crop can be restored regardless of the size of the image (for example saving it on desktop, and then using it on a mobile where the image is smaller).
205
206#### onImageLoaded(image) (optional)
207
208A callback which happens when the image is loaded. Passes the image DOM element.
209
210Useful if you want to set a crop based on the image dimensions when using pixels:
211
212```js
213onImageLoaded = image => {
214 this.setState({ crop: { width: image.width, height: image.height } });
215 return false; // Return false when setting crop state in here.
216};
217```
218
219Note that you must **return false** in this callback if you are changing the crop object.
220
221#### onImageError(event) (optional)
222
223This event is called if the image had an error loading.
224
225#### onDragStart(event) (optional)
226
227A callback which happens when a user starts dragging or resizing. It is convenient to manipulate elements outside this component.
228
229#### onDragEnd(event) (optional)
230
231A callback which happens when a user releases the cursor or touch after dragging or resizing.
232
233#### crossorigin (optional)
234
235Allows setting the crossorigin attribute on the image.
236
237#### renderSelectionAddon(state) (optional)
238
239Render a custom element in crop selection.
240
241#### renderComponent (optional)
242
243Render a custom HTML element in place of an image. Useful if you want to support videos:
244
245```js
246const videoComponent = (
247 <video
248 autoPlay
249 loop
250 style={{ display: 'block', maxWidth: '100%' }}
251 onLoadStart={e => {
252 // You must inform ReactCrop when your media has loaded.
253 e.target.dispatchEvent(new Event('medialoaded', { bubbles: true }));
254 }}
255 >
256 <source src="sample.mp4" type="video/mp4" />
257 </video>
258);
259
260<ReactCrop onChange={this.onCropChange} renderComponent={videoComponent} />;
261```
262
263#### ruleOfThirds (optional)
264
265Show [rule of thirds](https://en.wikipedia.org/wiki/Rule_of_thirds) lines in the cropped area. Defaults to `false`.
266
267#### circularCrop (optional)
268
269Show the crop area as a circle. If your aspect is not 1 (a square) then the circle will be warped into an oval shape. Defaults to `false`.
270
271#### rotate (optional)
272
273Rotates the image, you should pass a value between `-180` and `180`. Defaults to `0`.
274
275#### scale (optional)
276
277The image source (can be base64 or a blob just like a normal image).
278
279#### zoom (optional)
280
281A non-visual prop to keep pointer coords accurate when a parent element is scaled. Not to be confused with the `scale` prop which scales the image itself. Defaults to `1`.
282
283#### spin (optional)
284
285A non-visual prop to keep pointer coords accurate when a parent element is rotated. Not to be confused with the `rotate` prop which rotates the image itself. Defaults to `0`, range is from `-180` to `180`.
286
287## FAQ
288
289### What about showing the crop on the client?
290
291I wanted to keep this component focused so I didn't provide this. Normally a cropped image will be rendered and cached by a backend.
292
293However here's a ready to use function that returns a file blob for the cropped part after providing some parameters you already have when using this package:
294
295```js
296/**
297 * @param {HTMLImageElement} image - Image File Object
298 * @param {Object} crop - crop Object
299 * @param {String} fileName - Name of the returned file in Promise
300 */
301export function getCroppedImg(image, crop, fileName) {
302 const canvas = document.createElement("canvas");
303 const scaleX = image.naturalWidth / image.width;
304 const scaleY = image.naturalHeight / image.height;
305 canvas.width = crop.width;
306 canvas.height = crop.height;
307 const ctx = canvas.getContext("2d");
308
309 // New lines to be added
310 const pixelRatio = window.devicePixelRatio;
311 canvas.width = crop.width * pixelRatio;
312 canvas.height = crop.height * pixelRatio;
313 ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
314 ctx.imageSmoothingQuality = "high";
315
316 ctx.drawImage(
317 image,
318 crop.x * scaleX,
319 crop.y * scaleY,
320 crop.width * scaleX,
321 crop.height * scaleY,
322 0,
323 0,
324 crop.width,
325 crop.height
326 );
327
328 // As Base64 string
329 // const base64Image = canvas.toDataURL("image/jpeg");
330 // return base64Image;
331
332 // As a blob
333 return new Promise((resolve, reject) => {
334 canvas.toBlob(
335 (blob) => {
336 blob.name = fileName;
337 resolve(blob);
338 },
339 "image/jpeg",
340 1
341 );
342 });
343}
344
345async test() {
346 const croppedImg = await getCroppedImg(image, crop, fileName);
347}
348```
349
350There are other considerations to take into account such as cropping from an off-screen image if your image is sized down. Or taking into account the pixel ratio of the device and setting `image/png` and `imageSmoothingQuality`. For a more advanced example check out the [react hook demo](https://codesandbox.io/s/react-image-crop-demo-with-react-hooks-y831o).
351
352Some things to note:
353
3541. [toDataURL](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL) is synchronous and will block the main thread, for large images this could be for as long as a couple of seconds. We are using `toDataURL('image/jpeg')` otherwise it will default to `image/png` and the conversion will be significantly slower.
355
3562. [toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) is both faster and asynchronous, but not supported on old browsers (this is quickly becoming irrelevant).
357
3583. Another option to make the conversion faster is to scale the image down before converting it.
359
360### How to correct image EXIF orientation/rotation
361
362You might find that some images are rotated incorrectly. Unfortunately this is a browser wide issue not related to this library. You need to fix your image before passing it in.
363
364You can use the following library to load images, which will correct the rotation for you: https://github.com/blueimp/JavaScript-Load-Image/
365
366You can read an issue on this subject here: https://github.com/DominicTobias/react-image-crop/issues/181
367
368If you're looking for a complete out of the box image editor which already handles EXIF rotation then consider using [Pintura](https://gumroad.com/a/611955827).
369
370<h3>How to filter, rotate and annotate</h3>
371
372This library is deliberately lightweight and minimal for you to build features on top of. If you wish to perform more advanced image editing out of the box then consider using [Pintura](https://gumroad.com/a/611955827).
373
374![Pintura Demo](https://raw.githubusercontent.com/DominicTobias/react-image-crop/master/doka-demo.gif)
375
376### How can I center the crop?
377
378The easiest way is to use the percentage unit:
379
380```js
381crop: {
382 unit: '%',
383 width: 50,
384 height: 50,
385 x: 25,
386 y: 25
387}
388```
389
390Also remember to set your crop using the percentCrop on changes or the crop will go off-center if the container size changes (e.g. browser resize):
391
392```js
393onCropChange = (crop, percentCrop) => this.setState({ crop: percentCrop });
394```
395
396If you need more control over the crop you can set it in [onImageLoaded](#onimageloadedimage-optional). For example to center a percent crop:
397
398```js
399const onLoad = useCallback(img => {
400 imgRef.current = img;
401
402 const aspect = 16 / 9;
403 const width = img.width / aspect < img.height * aspect ? 100 : ((img.height * aspect) / img.width) * 100;
404 const height = img.width / aspect > img.height * aspect ? 100 : (img.width / aspect / img.height) * 100;
405 const y = (100 - height) / 2;
406 const x = (100 - width) / 2;
407
408 setCrop({
409 unit: '%',
410 width,
411 height,
412 x,
413 y,
414 aspect,
415 });
416
417 return false; // Return false if you set crop state in here.
418}, []);
419```
420
421## Contributing / Developing
422
423To develop run `yarn start`, this will recompile your JS and SCSS on changes.
424
425You can test your changes by opening `test/index.html` in a browser (you don't need to be running a server).