UNPKG

11.9 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 Doka.js. It features cropping, rotating, filtering, annotation, and lots more.
37
38[Learn more about Doka.js 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## FAQ
272
273### What about showing the crop on the client?
274
275I wanted to keep this component focused so I didn't provide this. Normally a cropped image will be rendered and cached by a backend.
276
277However 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:
278
279```js
280/**
281 * @param {HTMLImageElement} image - Image File Object
282 * @param {Object} crop - crop Object
283 * @param {String} fileName - Name of the returned file in Promise
284 */
285function getCroppedImg(image, crop, fileName) {
286 const canvas = document.createElement('canvas');
287 const scaleX = image.naturalWidth / image.width;
288 const scaleY = image.naturalHeight / image.height;
289 canvas.width = crop.width;
290 canvas.height = crop.height;
291 const ctx = canvas.getContext('2d');
292
293 ctx.drawImage(
294 image,
295 crop.x * scaleX,
296 crop.y * scaleY,
297 crop.width * scaleX,
298 crop.height * scaleY,
299 0,
300 0,
301 crop.width,
302 crop.height,
303 );
304
305 // As Base64 string
306 // const base64Image = canvas.toDataURL('image/jpeg');
307
308 // As a blob
309 return new Promise((resolve, reject) => {
310 canvas.toBlob(blob => {
311 blob.name = fileName;
312 resolve(blob);
313 }, 'image/jpeg', 1);
314 });
315}
316
317async test() {
318 const croppedImg = await getCroppedImg(image, crop, fileName);
319}
320```
321
322Some things to note:
323
3241. [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.
325
3262. [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).
327
3283. Another option to make the conversion faster is to scale the image down before converting it.
329
330### How to correct image EXIF orientation/rotation
331
332You 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.
333
334You can use the following library to load images, which will correct the rotation for you: https://github.com/blueimp/JavaScript-Load-Image/
335
336You can read an issue on this subject here: https://github.com/DominicTobias/react-image-crop/issues/181
337
338If you're looking for a complete out of the box image editor which already handles EXIF rotation then consider using [Doka](https://gumroad.com/a/611955827).
339
340<h3>How to filter, rotate and annotate</h3>
341
342This 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 [Doka](https://gumroad.com/a/611955827).
343
344![Doka Demo](https://raw.githubusercontent.com/DominicTobias/react-image-crop/master/doka-demo.gif)
345
346### How can I center the crop?
347
348The easiest way is to use the percentage unit:
349
350```js
351crop: {
352 unit: '%',
353 width: 50,
354 height: 50,
355 x: 25,
356 y: 25
357}
358```
359
360Also 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):
361
362```js
363onCropChange = (crop, percentCrop) => this.setState({ crop: percentCrop });
364```
365
366If you need more control over the crop you can set it in [onImageLoaded](#onimageloadedimage-optional). For example to center a percent crop:
367
368```js
369const onLoad = useCallback(img => {
370 setImgRef(img);
371
372 const aspect = 16 / 9;
373 const width = img.width / aspect < img.height * aspect ? 100 : ((img.height * aspect) / img.width) * 100;
374 const height = img.width / aspect > img.height * aspect ? 100 : (img.width / aspect / img.height) * 100;
375 const y = (100 - height) / 2;
376 const x = (100 - width) / 2;
377
378 setCrop({
379 unit: '%',
380 width,
381 height,
382 x,
383 y,
384 aspect,
385 });
386
387 return false; // Return false if you set crop state in here.
388}, []);
389```
390
391## Contributing / Developing
392
393To develop run `yarn start`, this will recompile your JS and SCSS on changes.
394
395You can test your changes by opening `test/index.html` in a browser (you don't need to be running a server).