Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 1x 2x 2x 2x 2x 2x 2x 1x 8x 8x 8x 16x 14x 2x 12x 8x 1x 8x 8x 8x 8x 8x 6x 5x 5x 6x 5x 8x 8x 7x 8x | import React, {useEffect, useRef} from 'react';
import Cropper from 'cropperjs';
interface ReactCropperElement extends HTMLImageElement {
cropper: Cropper;
}
type ReactCropperRef =
| ((instance: HTMLImageElement | ReactCropperElement | null) => void)
| React.MutableRefObject<HTMLImageElement | ReactCropperElement | null>
| null;
interface ReactCropperDefaultOptions {
scaleX?: number;
scaleY?: number;
enable?: boolean;
zoomTo?: number;
rotateTo?: number;
}
interface ReactCropperProps
extends ReactCropperDefaultOptions,
Cropper.Options<HTMLImageElement>,
Omit<React.HTMLProps<HTMLImageElement>, 'data' | 'ref' | 'crossOrigin'> {
crossOrigin?: '' | 'anonymous' | 'use-credentials' | undefined;
on?: (eventName: string, callback: () => void | Promise<void>) => void | Promise<void>;
onInitialized?: (instance: Cropper) => void | Promise<void>;
}
const applyDefaultOptions = (cropper: Cropper, options: ReactCropperDefaultOptions = {}): void => {
const {enable = true, scaleX = 1, scaleY = 1, zoomTo = 0, rotateTo = 0} = options;
enable ? cropper.enable() : cropper.disable();
cropper.scaleX(scaleX);
cropper.scaleY(scaleY);
cropper.rotateTo(rotateTo);
zoomTo > 0 && cropper.zoomTo(zoomTo);
};
/**
* sourced from: https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd
*/
const useCombinedRefs = (...refs: ReactCropperRef[]): React.RefObject<ReactCropperElement> => {
const targetRef = useRef<ReactCropperElement>(null);
React.useEffect(() => {
refs.forEach((ref) => {
if (!ref) return;
if (typeof ref === 'function') {
ref(targetRef.current);
} else {
ref.current = targetRef.current;
}
});
}, [refs]);
return targetRef;
};
const ReactCropper = React.forwardRef<ReactCropperElement | HTMLImageElement, ReactCropperProps>(({...props}, ref) => {
const {
dragMode = 'crop',
src,
style,
className,
crossOrigin,
scaleX,
scaleY,
enable,
zoomTo,
rotateTo,
alt = 'picture',
ready,
onInitialized,
...rest
} = props;
const defaultOptions: ReactCropperDefaultOptions = {scaleY, scaleX, enable, zoomTo, rotateTo};
const innerRef = useRef<HTMLImageElement>(null);
const combinedRef = useCombinedRefs(ref, innerRef);
useEffect(() => {
if (combinedRef.current !== null) {
const cropper = new Cropper(combinedRef.current, {
dragMode,
...rest,
ready: (e) => {
if (e.currentTarget !== null) {
applyDefaultOptions(e.currentTarget.cropper, defaultOptions);
}
ready && ready(e);
},
});
onInitialized && onInitialized(cropper);
}
/**
* destroy cropper on un-mount
*/
return () => {
combinedRef.current?.cropper?.destroy();
};
}, [combinedRef]);
/**
* re-render when src changes
*/
useEffect(() => {
if (combinedRef.current?.cropper && typeof src !== 'undefined') {
combinedRef.current.cropper.reset().clear().replace(src);
}
}, [src]);
return (
<div style={style} className={className}>
<img
crossOrigin={crossOrigin}
src={src}
alt={alt}
style={{opacity: 0, maxWidth: '100%'}}
ref={combinedRef}
/>
</div>
);
});
export {ReactCropper, ReactCropperProps, ReactCropperElement, applyDefaultOptions};
|