- recent project with react, react and learn to use cropper assembly crop the picture.
(Here is not unified development components useful for tsx (TypeScript + xml / html) writing components useful jsx (javascript + xml / html) write components
Preface: cropper component into the project in hand along a direct look at the official documentation; GitHub: https://github.com/fengyuanchen/cropperjs#methods online demo url: https://fengyuanchen.github.io/cropper/
1.cropper simple assembly and packaging of various operations.
react-cropper.js file
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Cropper from 'cropperjs'; const optionProps = [ 'dragMode', 'aspectRatio', 'data', 'crop', // unchangeable props start from here 'viewMode', 'preview', 'responsive', 'restore', 'checkCrossOrigin', 'checkOrientation', 'modal', 'guides', 'center', 'highlight', 'background', 'autoCrop', 'autoCropArea', 'movable', 'rotatable', 'scalable', 'zoomable', 'zoomOnTouch', 'zoomOnWheel', 'wheelZoomRatio', 'cropBoxMovable', 'cropBoxResizable', 'toggleDragModeOnDblclick', 'minContainerWidth', 'minContainerHeight', 'minCanvasWidth', 'minCropBoxWidth', 'minCanvasHeight', 'minCropBoxHeight', 'ready', 'cropstart', 'cropmove', 'cropend', 'zoom', ]; const unchangeableProps = optionProps; class ReactCropper extends Component { componentDidMount() { const options = Object.keys(this.props) .filter(propKey => optionProps.indexOf(propKey) !== -1) .reduce((prevOptions, propKey) => Object.assign({}, prevOptions, { [propKey]: this.props[propKey] }), {}); this.cropper = new Cropper(this.img, options); } UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.src !== this.props.src) { this.cropper.reset().clear().replace(nextProps.src); } if (nextProps.aspectRatio !== this.props.aspectRatio) { this.setAspectRatio(nextProps.aspectRatio); } if (nextProps.data !== this.props.data) { this.setData(nextProps.data); } if (nextProps.dragMode !== this.props.dragMode) { this.setDragMode(nextProps.dragMode); } if (nextProps.cropBoxData !== this.props.cropBoxData) { this.setCropBoxData(nextProps.cropBoxData); } if (nextProps.canvasData !== this.props.canvasData) { this.setCanvasData(nextProps.canvasData); } if (nextProps.moveTo !== this.props.moveTo) { if (nextProps.moveTo.length > 1) { this.moveTo(nextProps.moveTo[0], nextProps.moveTo[1]); } else { this.moveTo(nextProps.moveTo[0]); } } if (nextProps.zoomTo !== this.props.zoomTo) { this.zoomTo(nextProps.zoomTo); } if (nextProps.rotateTo !== this.props.rotateTo) { this.rotateTo(nextProps.rotateTo); } if (nextProps.scaleX !== this.props.scaleX) { this.scaleX(nextProps.scaleX); } if (nextProps.scaleY !== this.props.scaleY) { this.scaleY(nextProps.scaleY); } if (nextProps.enable !== this.props.enable) { if (nextProps.enable) { this.enable(); } else { this.disable(); } } Object.keys(nextProps).forEach((propKey) => { let isDifferentVal = nextProps[propKey] !== this.props[propKey]; const isUnchangeableProps = unchangeableProps.indexOf(propKey) !== -1; if (typeof nextProps[propKey] === 'function' && typeof this.props[propKey] === 'function') { isDifferentVal = nextProps[propKey].toString() !== this.props[propKey].toString(); } if (isDifferentVal && isUnchangeableProps) { throw new Error(`prop: ${propKey} can't be change after componentDidMount`); } }); } componentWillUnmount() { if (this.img) { // Destroy the cropper, this makes sure events such as resize are cleaned up and do not leak this.cropper.destroy(); delete this.img; delete this.cropper; } } setDragMode(mode) { return this.cropper.setDragMode(mode); } setAspectRatio(aspectRatio) { return this.cropper.setAspectRatio(aspectRatio); } getCroppedCanvas(options) { return this.cropper.getCroppedCanvas(options); } setCropBoxData(data) { return this.cropper.setCropBoxData(data); } getCropBoxData() { return this.cropper.getCropBoxData(); } setCanvasData(data) { return this.cropper.setCanvasData(data); } getCanvasData() { return this.cropper.getCanvasData(); } getImageData() { return this.cropper.getImageData(); } getContainerData() { return this.cropper.getContainerData(); } setData(data) { return this.cropper.setData(data); } getData(rounded) { return this.cropper.getData(rounded); } crop() { return this.cropper.crop(); } move(offsetX, offsetY) { return this.cropper.move(offsetX, offsetY); } moveTo(x, y) { return this.cropper.moveTo(x, y); } zoom(ratio) { return this.cropper.zoom(ratio); } zoomTo(ratio) { return this.cropper.zoomTo(ratio); } rotate(degree) { return this.cropper.rotate(degree); } rotateTo(degree) { return this.cropper.rotateTo(degree); } enable() { return this.cropper.enable(); } disable() { return this.cropper.disable(); } reset() { return this.cropper.reset(); } clear() { return this.cropper.clear(); } replace(url, onlyColorChanged) { return this.cropper.replace(url, onlyColorChanged); } scale(scaleX, scaleY) { return this.cropper.scale(scaleX, scaleY); } scaleX(scaleX) { return this.cropper.scaleX(scaleX); } scaleY(scaleY) { return this.cropper.scaleY(scaleY); } render() { const { src, alt, crossOrigin, style, className, } = this.props; return ( <div style={style} className={className} > <img crossOrigin={crossOrigin} ref={(img) => { this.img = img; }} src={src} alt={alt === undefined ? 'picture' : alt} style={{ opacity: 0 }} /> </div> ); } } ReactCropper.propTypes = { style: PropTypes.object, // eslint-disable-line react/forbid-prop-types className: PropTypes.string, // react cropper options crossOrigin: PropTypes.string, src: PropTypes.string, alt: PropTypes.string, // props of option can be changed after componentDidmount aspectRatio: PropTypes.number, dragMode: PropTypes.oneOf(['crop', 'move', 'none']), data: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number, width: PropTypes.number, height: PropTypes.number, rotate: PropTypes.number, scaleX: PropTypes.number, scaleY: PropTypes.number, }), scaleX: PropTypes.number, scaleY: PropTypes.number, enable: PropTypes.bool, cropBoxData: PropTypes.shape({ left: PropTypes.number, top: PropTypes.number, width: PropTypes.number, height: PropTypes.number, }), canvasData: PropTypes.shape({ left: PropTypes.number, top: PropTypes.number, width: PropTypes.number, height: PropTypes.number, }), zoomTo: PropTypes.number, moveTo: PropTypes.arrayOf(PropTypes.number), rotateTo: PropTypes.number, // cropperjs options // https://github.com/fengyuanchen/cropperjs#options AspectRatio //, dragMode, data viewMode: PropTypes.oneOf([0, 1, 2, 3]), preview: PropTypes.string, Responsive: PropTypes.bool, restore: PropTypes.bool, checkCrossOrigin: PropTypes.bool, checkOrientation: PropTypes.bool, modal: PropTypes.bool, guides: PropTypes.bool, center : PropTypes.bool, highlight: PropTypes.bool, background: PropTypes.bool, autoCrop: PropTypes.bool, autoCropArea: PropTypes.number, movable: PropTypes.bool, rotatable: PropTypes.bool, scalable: PropTypes.bool, zoomable: PropTypes .bool, zoomOnTouch: PropTypes.bool, zoomOnWheel: PropTypes.bool, wheelZoomRatio: PropTypes.number, cropBoxMovable: PropTypes.bool, cropBoxResizable: PropTypes.bool, toggleDragModeOnDblclick: PropTypes.bool, minContainerWidth: PropTypes.number, minContainerHeight: PropTypes.number, minCanvasWidth: PropTypes.number, minCanvasHeight: PropTypes.number, minCropBoxWidth: PropTypes.number, minCropBoxHeight: PropTypes.number, ready: PropTypes.func, cropstart: PropTypes.func, cropmove: PropTypes.func, cropend: PropTypes.func, crop: PropTypes.func, zoom: PropTypes.func, }; ReactCropper.defaultProps = { src: null, dragMode: 'crop', data: null, scaleX: 1, scaleY: 1, enable: true, zoomTo: 1, rotateTo: 0, }; export default ReactCropper;
Simple calling component package 2.cropper
CropperView.jsx file
import React, { Component, useEffect } from 'react'; import $ from "jquery"; import Cropper from './cropper/react-cropper' import 'cropperjs/dist/cropper.css' /* global FileReader */ var showCropArea = true; export default class CropperView extends Component { constructor(props) { super(props); this.state = { cropResult: null, }; // this.cropper = this; this.onChange = this.onChange.bind(this); this.src = props.src; } // componentDidMount(){ // useEffect(() => { // alert("cropZone" + this.cropper); // // if (typeof this.cropper.getCroppedCanvas() === 'undefined') { // // return; // // } // alert("left:" + this.cropper.getCropBoxData().left // + "top:" + this.cropper.getCropBoxData().top // + "width:" + this.cropper.getCropBoxData().width // + "height:" + this.cropper.getCropBoxData().height); // }, [this.props.save]); // } onChange(e) { e.preventDefault(); let files; if (e.dataTransfer) { files = e.dataTransfer.files; } else if (e.target) { files = e.target.files; } const reader = new FileReader(); reader.onload = () => { this.setState({ src: reader.result }); }; reader.readAsDataURL(files[0]); } cropZone() { if (typeof this.cropper.getCroppedCanvas() === 'undefined') { return; } alert("left:" + this.cropper.getCropBoxData().left + "top:" + this.cropper.getCropBoxData().top + "width:" + this.cropper.getCropBoxData().width + "height:" + this.cropper.getCropBoxData().height); $(".cropper-crop-box").hide(); $(".cropper-drag-box").hide(); $(".cropper-wrap-box").append( "<div style=\"width:" + this.cropper.getCropBoxData().width + ";" + "height:" + this.cropper.getCropBoxData().height + ";" + "background:#0000FF; opacity:0.3" + "position:absolute; left:" + this.cropper.getCropBoxData().left +";" + "top:" + this.cropper.getCropBoxData().top + ";\">"); } showCropZone() { if (showCropArea) { $(".cropper-crop-box").css("display", "block"); $(".cropper-drag-box").css("display", "block"); } else { $(".cropper-crop-box").hide(); $(".cropper-drag-box").hide(); } } creatCrop(){ this.cropper.crop(); } clearCrop(){ this.cropper.clear(); } resetCrop(){ this.cropper.reset(); } moveLeft(){ this.cropper.move(-5, 0); } moveRight(){ this.cropper.move(5, 0); } moveUp(){ console.log("====moveUp==="); try { this.cropper.move(0, -5); }catch (err){ console.log(err); } } moveDown(){ this.cropper.move(0, 5); } enlarge(){ try { // 放大 // this.cropper.zoom(0.1); var allCanvasDate = this.cropper.getCanvasData(); var newCanvasDate = {left:allCanvasDate.left, top: allCanvasDate.top, width: allCanvasDate.width*2, height: allCanvasDate.height*2} this.cropper.setCanvasData(newCanvasDate); }catch (err){ console.log(err); } } shrink(){ try { // 缩小 // this.cropper.zoom(-0.1) var allCanvasDate = this.cropper.getCanvasData(); var newCanvasDate = {left:allCanvasDate.left, top: allCanvasDate.top, width: allCanvasDate.width*0.5, height: allCanvasDate.height*0.5} this.cropper.setCanvasData(newCanvasDate); }catch (err){ console.log(err); } } test(){ try { var allDate = this.cropper.getData(true); alert(allDate.toString()); var par = {x:allDate.x, y:allDate.y, width:allDate.width*2, height:allDate.height*2, rotate:allDate.rotate, scaleX:allDate.scaleX, scaleY:allDate.scaleY} this.cropper.setData(par); }catch (err){ console.log(err); } } moveCrop(){ //this.cropper.movecrop(); console.log("===move===crop==="); } reduceCrop(){ var par = {left:this.cropper.getCropBoxData().left, top:this.cropper.getCropBoxData().top, width:this.cropper.getCropBoxData().width*0.8, height:this.cropper.getCropBoxData().height*0.8} this.cropper.setCropBoxData(par); } raiseCrop(){ var par = {left:this.cropper.getCropBoxData().left, top:this.cropper.getCropBoxData().top, width:this.cropper.getCropBoxData().width*1.2, height:this.cropper.getCropBoxData().height*1.2} this.cropper.setCropBoxData(par); } CropLeft(){ var par = {left:this.cropper.getCropBoxData().left - 10, top:this.cropper.getCropBoxData().top, width:this.cropper.getCropBoxData().width, height:this.cropper.getCropBoxData().height} this.cropper.setCropBoxData(par); } CropRight(){ var par = {left:this.cropper.getCropBoxData().left + 10, top:this.cropper.getCropBoxData().top, width:this.cropper.getCropBoxData().width, height:this.cropper.getCropBoxData().height} this.cropper.setCropBoxData(par); } CropUp(){ var par = {left:this.cropper.getCropBoxData().left, top:this.cropper.getCropBoxData().top - 10, width:this.cropper.getCropBoxData().width, height:this.cropper.getCropBoxData().height} this.cropper.setCropBoxData(par); } CropDown(){ var par = {left:this.cropper.getCropBoxData().left, top:this.cropper.getCropBoxData().top + 10, width:this.cropper.getCropBoxData().width, height:this.cropper.getCropBoxData().height} this.cropper.setCropBoxData(par); } render() { return ( <div className="r-view" style={{ position:'absolute', left:'185px', top:"83px" }}> <Cropper style={{ height:'100%', width:'auto' }} aspectRatio={16 / 9} preview=".img-preview" guides={false} src={this.props.src} viewMode={2} minContainerWidth={585} minContainerHeight={430} ref={cropper => { this.cropper = cropper; }} zoomable={true} zoomOnTouch={true} /> </div> ); } }
3.cropper components and bind a variety of button operation (reason: the device does not support the operation and to enlarge the picture of the finger in the selection area reduction operation), page integration component.
CropperScreen.tsx file
import * as React from 'react'; import styled from "styled-components"; import useTranslate from "../../hooks/useTranslate"; import CropView from "./CropperView"; import 'cropperjs/dist/cropper.css' import FormView from "./FormView" const AppStyle = styled.div` background: #CCC; `; var showCropArea = false; export default function CropperScreen() { const t = useTranslate(); const handleBackClicked = () => { $("#viewable").show(); $("#scan_settings_id").css("display","none"); }; const doCropClicked = () => { // showCropArea = !showCropArea; // if (showCropArea) { // alert("disabled false"); // $("#btn_save_crop").removeAttr("disabled"); // } else { // alert("disabled true"); // $("#btn_save_crop").attr("disabled", "true"); // } // cropUser.showCropZone(showCropArea); cropUser.clearCrop(); }; const doSaveCropClicked = () => { cropUser.cropZone(); }; const handReset = () => { cropUser.resetCrop(); cropUser.creatCrop(); } const handMoveLeft = () => { cropUser.moveLeft(); } const handMoveRight = () => { cropUser.moveRight(); } const handMoveUp = () => { console.log("====handMoveUp==="); try { cropUser.moveUp(); }catch (err){ console.log(err); } } const handMoveDown = () => { cropUser.moveDown(); } const handMoveCrop = () => { alert("unknow"); cropUser.moveCrop(); } const handReduceCrop = () => { cropUser.reduceCrop(); } const handRaiseCrop = () => { cropUser.raiseCrop() } const handEnlarge = () => { cropUser.enlarge(); } const handShrink = () => { cropUser.shrink(); } const handCropLeft = () => { cropUser.CropLeft() } const handCropRight = () => { cropUser.CropRight() } const handCropUp = () => { cropUser.CropUp() } const handCropDown = () => { cropUser.CropDown() } const handTest = () => { cropUser.test() } const handleSubmit = () => { alert("====handleSubmit===="); console.log(formUser); console.log(formUser.state); } let maxItem = 2; let cropUser let formUser var initValue = {name:'tom',job: '12'} return ( <AppStyle id="scan_settings_id" className="r-view" style={{display:"none", height:"470px"}}> <header className="r-titlebar"> <a href="#" className="r-titlebar__back" onClick={handleBackClicked} /> <h1 className="r-titlebar__title">Preview</h1> </header> <div style={{position:"absolute", left:35}}> </div> <CropView ref={cropView => {cropUser = cropView}} src={require('../smartsdk-css/img/test.png')}/> <div className="r-floating-island"> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={doCropClicked}>Crop</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={doSaveCropClicked}>Save</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={handReset}>reset</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={handMoveCrop}>mcrop</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={handMoveLeft}>left</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={handMoveRight}>right</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={handMoveUp}>up</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={handMoveDown}>down</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handEnlarge}>+</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handShrink}>-</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handReduceCrop}>reduce</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handRaiseCrop}>raise</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handCropLeft}>cropr</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handCropRight}>cropl</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handCropUp}>cropu</button> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handCropDown}>cropd</button> </div> <div> <button style={{width:'70px', height:'35px', marginLeft:'5px'}} onClick={ handTest}>test</button> </div> {/* <FormView handleSubmit={handleSubmit} ref={formView => {formUser = formView}} props = {initValue}/> */} {/* <button className="r-start-button"> {t("dapi:cba.common.start")} </button> */} </div> </AppStyle > ); }
4. Finally call the whole assembly, page display
page