react-cropper文档:https://www.npmjs.com/package/react-cropperjs
官方cropper文档:https://fengyuanchen.github.io/cropper/
一个作者总结的相关插件的中文版文档:https://blog.csdn.net/weixin_38023551/article/details/78792400
三.主要的实现的功能
使用antdesign的upload组件进行上传图片,并调用react-cropper组件实现固定长和宽的截图,将截图的结果以FILE格式传给后台。实现效果如下。

可以上传多张图片。

四.主要的思路
首先构建一个CropperUpload组件,该组件接收截图框的长,宽,Upload样式模式,初始图片列表四个参数。该组件主要实现上传可截图的图片的功能,在Upload组件的beforeUpload中用FileReader读取要上传的文件的base64码,并返回false阻止Upload组件自动上传,将文件码赋值给cropper组件,用cropper进行截图,截图之后,将cropper返回的base64码转换成File格式传向后台。
五.具体函数的实现
1.首先需要一个ant的Upload组件,在这里主要用到这个组件的三个重要参数,beforeUpload上传图片之前触发的回调,onRemove删除图片的回调,fileList已上传图片的列表。
<Upload
name="files"
listType={this.state.pattern === 1 ? " " : this.state.pattern === 2 ? "picture-card" : "picture"}
className={this.state.pattern === 3 ? styles.uploadinline : ""}
beforeUpload={this.beforeUpload.bind(this)}
onRemove={this.handleRemove.bind(this)}
fileList={this.state.fileList}
{botton}
</Upload>
其他的listType主要是支持Upload不同的样式模式。如图所示的三种模式。



点击上传图片按钮会触发beforeUpload函数,如果想获取通过input选择的文件的数据,我们就需要使用到js封装好的FileReader对象。并通过input.files[0]来获取点击上传的文件对象,在这里因为用了Upload组件,该组件已经封装好了,此时会通过beforeUpload的file参数传递进来,文件对象里面包括文件大小(size),文件的名字(name)和文件类型(type),可以通过该文件类型来判断上传的文件是否是图片,判断的正则如下
/image\/\w+/.test(file.type)
通过调用FileReader来读取文件,文件读取结束之后会调用onload回调,在onload回调中,在通过image.onload回调(该回调是异步的)来获取图片的长宽,图片的长宽必须大于裁剪框的长宽,最后通过e.target.result来获取图片的base64码,将该码通过setSate设置给reactCropper的src中,src为裁剪插件的图片的源,在将包裹裁剪框的Modal框弹出,通过将Modal的visible设置为true,Model的visible在这里用state中的editImageModalVisible来控制,此时将弹出裁剪图片的裁剪框。
beforeUpload(file, fileList) {
if (this.refs.cropper) {
this.refs.cropper.reset();
this.refs.cropper.setData({
width: this.state.width,
height: this.state.height,
});
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
MsgBox.error({ content: '文件大小不能超过10M' });
return false;
var reader = new FileReader();
const image = new Image();
var height;
var width;
reader.readAsDataURL(file);
reader.onload = (e) => {
image.src = reader.result;
image.onload = () => {
height = image.naturalHeight;
width = image.naturalWidth;
if (height < this.state.height || width < this.state.width) {
message.error('图片尺寸不对 宽应大于:'+this.state.width+ '高应大于:' +this.state.height );
this.setState({
editImageModalVisible: false,
else{
this.setState({
srcCropper: e.target.result,
selectImgName: file.name,
selectImgSize: (file.size / 1024 / 1024),
selectImgSuffix: file.type.split("/")[1],
editImageModalVisible: true,
if (this.refs.cropper) {
this.refs.cropper.replace(e.target.result);
return false;
裁剪框的设置如下,src是裁剪框图片的路径,ref为该组件真实实例的引用,viewMode在这里选择了模式1,zoomable是否允许放大图像,movable是否允许移动图像,在这里设置了ready函数,当一个cropper实例完全构建时,这个事件就会发生,用于在每一次裁剪之前都重新设置一下裁剪框大小,否则他会自动的恢复默认原图像0.8比例。
<Cropper
src={this.state.srcCropper}
ref="cropper"
viewMode={1}
zoomable={true}
movable={true}
guides={true}
background={false}
rotatable={false}
style={{ height: '100%', width: '100%' }}
cropBoxResizable={false}
cropBoxMovable={true
}
dragMode="move"
center={true}
ready={this._ready.bind(this)}
裁剪框父组件设置如下,可见与不可见通过页面的stated的editImageModalVisible来控制,当上传图片时此状态设置为true显示裁剪框,当上传图片成功,或者关闭裁剪框的时候将此状态设置为false关闭裁剪框,Modal组件的高度是自适应组件内的图片高度的,宽度并不自适应图片的宽度,若不设置将会有默认的宽度,因此在这里设置为100%,当裁剪完成点击保存触发saveImg函数回调,当点击取消的时候触发handleCancle回调。
<Modal
key="cropper_img_icon_key"
visible={this.state.editImageModalVisible}
width="600"
footer={[
<Button type="primary" onClick={this.saveImg.bind(this)} >保存</Button>,
<Button onClick={this.handleCancel.bind(this)} >取消</Button>
</Modal>
点击保存按钮之后,触发saveImg函数,要使用FormData向后台传数据,并且传输的是格式是FILE格式的,因此现将截图的结果取出,取出的时候使用this.refs.cropper.getCroppedCanvas().toDataURL(),最终取出的是截图后图片的base64码,通过dataURLtoFile函数将base64转换成FILE,封装到formdata中,调用后台的一个接口postimage。后台将返回一个图片的路径给前台。前台将路径取出,并按照Upload组件图片列表默认显示的格式来构建,最终push到fileList中,显示在Upload组件上。并将state中editImageModalVisible的状态置为false,关闭Modal裁剪框。
saveImg() {
const { dispatch } = this.props;
var formdata = new FormData();
formdata.append("files", this.dataURLtoFile(this.refs.cropper.getCroppedCanvas().toDataURL(), this.state.selectImgName));
dispatch({
type: 'getData/postimage',
payload: formdata,
callback: () => {
const { success, msg, obj } = this.props.imagePictures;
if (success) {
let imageArry = this.state.pattern == 3 ? this.state.fileList.slice() : [];
imageArry.push({
uid: Math.random() * 100000,
name: this.state.selectImgName,
status: 'done',
url: obj[0].sourcePath,
thumbUrl: this.refs.cropper.getCroppedCanvas().toDataURL(),
thumbnailPath: obj[0].thumbnailPath,
largePath: obj[0].largePath,
mediumPath: obj[0].mediumPath,
upload: true,
this.setState({
fileList: imageArry,
editImageModalVisible: false,
srcCropper: this.state.srcCropper,
this.props.onChange(imageArry);
else {
message.error(msg);
});
在modal中调用getData的postimage向后台发送数据,现在附上请求头的设置部分,因为传递的数据已经用formdata封装上了,因此直接就传递给后台就可以。
export default function request(url, options) {
const defaultOptions = {
credentials: 'include',
const newOptions = { ...defaultOptions, ...options };
if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
newOptions.method = 'POST'
return fetch(url, newOptions)
.then(checkStatus)
.then((response) => {
if (newOptions.method === 'DELETE' || response.status === 204) {
return response.text();
return response.json();
.catch((e) => {
const { dispatch } = store;
const status = e.name;
if (status === 401) {
dispatch(routerRedux.push('/user/login'));
return;
});
向后台发送图片的请求头:

后传传输回数据:图片存储的路径,分为不同种的格式,大图,小图,中图,原图。

六遇到的问题
上面流程看似简单,可是在实际开发中遇到了无数的困难!下面给大家分享我的经历。
1.beforeUpload组件问题
当Upload组件调用beforeUpload函数的时候,如果函数的返回值是false的时候,就会清空fileList,所以每次参数fileList之中都只有当前的图片,没有之前上传过的,这是因为当beforeUpload函数返回时false的时候,会自动触发onChange函数,触发时fileList中只有当前的上传的图片,因此若再用onChange函数中调用setState来维护fileList就会将之前的图片都清除。因此在这种完全阻止自动上传的情景下就应该将onChange函回调函数删除。
<Upload
name="files"
action="/hyapi/resource/image/multisize/upload"
beforeUpload={this.beforeUpload.bind(this)}
onRemove={this.handleRemove.bind(this
)}
fileList={this.state.fileList}
{botton}
</Upload>
2.对同一张图片进行截图的问题
当对一张图片进行截图,调整了图片的位置和缩放,下次再打开同一张图片,还会保留有上一次的记录。
第一次打开的位置:

改变图片大小和位置:

再次打开时候还是上图所示位置,cropper对同一张图片没有重新的渲染。cropper组件有一个ready函数,每一次截图准备完毕之前都会调用,在ready里面打印,发现同一张图片连续上传,只有第一次会调用ready。
_ready() {
this.refs.cropper.setData({
width: this.state.width,
height: this.state.height,
});
这说明对同一张图片,cropper不会重新渲染,所以就要手动在每次弹出截图框的时候进行reset一下,因此在beforeUpload函数中进行恢复初始化操作。调用this.refs.cropper.reset(),并重新设置裁剪框的宽和高,否则会恢复默认原图的0.8比例。
beforeUpload(file, fileList) {
if (this.refs.cropper) {
this.refs.cropper.reset();
this.refs.cropper.setData({
width: this.state.width,
height: this.state.height,
});
3.多次截图后面图片会变小,容器变成默认的200*100
第一次上传图片截图时正常,图片大小为原始图片大小

后来在截图发现图片会压缩成很小,查看样式发现为cropper-container恢复默认值200*100

发现单纯的通过setState改变cropper的src有时并不能使图片将cropper容器撑开,达到重新渲染组件的目的,后来查看了官方文档,发现有一个参数replace(url),作用替换图像的src并重新构建cropper,尝试了一下发现可以重新构建cropper,达到图片撑开容器而不是显示容器默认值得效果。并且加上这个参数之后。连续上传同一张图片也不会显示上一次默认的值了,同一张图片也会当做一张新的图片重新渲染,所以每次给cropper赋值的时候,调用replace函数十分重要,而不是单纯的靠改变cropper的src来实现重新渲染裁剪插件。
this.setState({
srcCropper: e.target.result,
selectImgName: file.name,
selectImgSize: (file.size / 1024 / 1024),
selectImgSuffix: file.type.split("/")[1],
editImageModalVisible: true,
if (this.refs.cropper) {
this.refs.cropper.replace(e.target.result);
4.用fetch向后台传输File格式文件,头文件设置问题
将File绑定在formdata里面之后,向后台传递时候,起初手动设置了头文件Content-Type=multipart/form-data,向后台发送数据失败,头文件显示如下,显示的结果没有正常情况下的boundary

看了原生ajax传递数据的代码如下,将contentType设置为false,不设置头文件。因此尝试在fetch时候,不设置contentType的内容。
$.ajax({
url: "upload.ashx",
type: "POST",
data: formData,
*必须false才会自动加上正确的Content-Type
contentType: false,
* 必须false才会避开jQuery对 formdata 的默认处理
* XMLHttpRequest会对 formdata 进行正确的处理
processData: false,
success: function (data) {
if (data.status == "true") {
alert("上传成功!");
if (data.status == "error") {
alert(data.msg);
$("#imgWait").hide();
error: function () {
alert("上传失败!");
$("#imgWait").hide();
之后再向后台传递的时候,头文件就显示为正常,自动设置了boundary

以上是所有开发中遇到的问题,欢迎大家一起来讨论
七.完整代码部分
自己封装的cropper-upload组件完整代码
import React, { PureComponent } from 'react';
import moment from 'moment';
import { routerRedux, Route, Switch, Link } from 'dva/router';
import { Upload, Button, Modal, Icon, message } from 'antd';
import "cropperjs/dist/cropper.css"
import Cropper from 'react-cropper'
import { connect } from 'dva';
import styles from './Upload.less';
@connect(state => ({
imagePictures: state.getData.imagePictures,
}))
class CropperUpload extends PureComponent {
constructor(props) {
super(props);
console.log(props);
console.log("props");
this.state = {
width: props.width,
height: props.height,
pattern: props.pattern,
fileList: props.fileList ? props.fileList : [],
editImageModalVisible: false,
srcCropper: '',
selectImgName: '',
componentWillReceiveProps(nextProps) {
if ('fileList' in nextProps) {
this.setState({
fileList: nextProps.fileList ? nextProps.fileList : [],
});
handleCancel = () => {
this.setState
({
editImageModalVisible: false,
});
beforeUpload(file, fileList) {
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
MsgBox.error({ content: '文件大小不能超过10M' });
return false;
var reader = new FileReader();
const image = new Image();
var height;
var width;
reader.readAsDataURL(file);
reader.onload = (e) => {
image.src = reader.result;
image.onload = () => {
height = image.naturalHeight;
width = image.naturalWidth;
if (height < this.state.height || width < this.state.width) {
message.error('图片尺寸不对 宽应大于:'+this.state.width+ '高应大于:' +this.state.height );
this.setState({
editImageModalVisible: false,
else{
this.setState({
srcCropper: e.target.result,
selectImgName: file.name,
selectImgSize: (file.size / 1024 / 1024),
selectImgSuffix: file.type.split("/")[1],
editImageModalVisible: true,
if (this.refs.cropper) {
this.refs.cropper.replace(e.target.result);
return false;
handleRemove(file) {
this.setState((state) => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
this.props.onChange(newFileList);
return {
fileList: newFileList,
});
convertBase64UrlToBlob(base64Data) {
var byteString;
if (base64Data.split(',')[0].indexOf('base64') >= 0) {
byteString = atob(base64Data.split(',')[1]);
} else {
byteString = unescape(base64Data.split(',')[1]);
var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
return new Blob([ia], { type: this.state.selectImgSuffix });
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
return new File([u8arr], filename, { type: mime });
_ready() {
this.refs.cropper.setData({
width: this.state.width,
height: this.state.height,
});
saveImg() {
const { dispatch } = this.props;
var formdata = new FormData();
formdata.append("files", this.dataURLtoFile(this.refs.cropper.getCroppedCanvas().toDataURL(), this.state.selectImgName));
dispatch({
type: 'getData/postimage',
payload: formdata,
callback: () => {
const { success, msg, obj } = this.props.imagePictures;
if (success) {
let imageArry = this.state.pattern == 3 ? this.state.fileList.slice() : [];
imageArry.push({
uid: Math.random() * 100000,
name: this.state.selectImgName,
status: 'done',
url: obj[0].sourcePath,
thumbUrl: this.refs.cropper.getCroppedCanvas().toDataURL(),
thumbnailPath: obj[0].thumbnailPath,
largePath: obj[0].largePath,
mediumPath: obj[0].mediumPath,
upload: true,
this.setState({
fileList: imageArry,
editImageModalVisible: false,
this.props.onChange(imageArry);
else {
message.error(msg);
});
render() {
const botton = this.state.pattern == 2 ?
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div> :
<Button>
<Icon type="upload" />选择上传</Button>
return (
<Upload
name="files"
action="/hyapi/resource/image/multisize/upload"
listType={this.state.pattern === 1 ? " " : this.state.pattern === 2 ? "picture-card" : "picture"}
className={this.state.pattern === 3 ? styles.uploadinline : ""}
beforeUpload={this.beforeUpload.bind(this)}
onRemove={this.handleRemove.bind(this)}
fileList={this.state.fileList}
{botton}
</Upload>
<Modal
key="cropper_img_icon_key"
visible={this.state.editImageModalVisible}
width="100%"
footer={[
<Button type="primary" onClick={this.saveImg.bind(this)} >保存</Button>,
<Button onClick={this.handleCancel.bind(this)} >取消</Button>
<Cropper
src={this.state.srcCropper}
ref="cropper"
preview=".uploadCrop"
viewMode={1}
zoomable={true}
movable={true}
guides={true}
background={false}
rotatable={false}
style={{ height: '100%', width: '100%' }}
cropBoxResizable={false}
cropBoxMovable={true}
dragMode="move"
center={true}
ready={this._ready.bind(this)}
</Modal>
</div>
export default CropperUpload;
页面具体调用范例:
<CropperUpload
pattern={1}
width={375}
height={120}
onChange={this.ChangeIconImage.bind(this)}
fileList={this.state.iconUrl}
react-cropper + antdesign +dva 实现裁剪图片并上传的功能一.首先安装react-cropper插件npm install --save react-cropper执行该命令以后,下载react-cropper依赖信息自动更新到package.json中在使用该插件的代码中需要进行引入import "cropperjs/dist/cropper.css"im...
React + React-Router 4 + Redux + Ant Design + Webpack 4带有热重载和redux-devtools-extension入门
:warning_selector: Ant Design仍在升级到ReactJS 16.3新生命周期,因此,应用目前无法正常运行
完整的Ant Design入门
您喜欢Bootstrap吗? 等待并查看 。 您现在明白我的意思了吗?
ReactJS(16.4.x + - )
蚂蚁设计(3.8+ )
具有内置主题自定义(更改var以进行自定义)
图标字体在本地加载
Redux(随着应用程序的增长,管理状态将是一个严
npm install --save react-cropper
您的项目中需要cropper.css ,来自 。 由于此项目依赖于 ,因此,对于更高版本的npm 3.0.0 ,它位于/node_modules/react-cropper/node_modules/cropperjs/dist/cropper.css或node_modules/cropperjs/dist/cropper.css中
重大更改(版本> = 2.0.0)
对ref支持已被删除。 使用onInitialized方法来获取cropper实例。 在2.
在交互式监视模式下启动测试运行程序。 有关更多信息,请参见关于的部分。
npm run build或yarn build
构建生产到应用程序build文件夹。 它在生产模式下正确捆绑了React,并优化了构建以获得最佳性能。
最小化构建,文件名包含哈希。 您的应用已准备好进行部署!
有关更多信息,请参见有关的部分。
npm run eject或yarn eject
注意:这是单向操作。 eject ,您将无法返回!
如果您对构建工具和配置选择不满意,则可以随时eject 。 此命令将从项目中删除单个构建依赖项。
而是将所有配置
react-cropper
react-crepper的github:https://github.com/react-cropper/react-cropper
不想看官方文档的,就直接看如何使用吧。
npm install --save react-cropper //或者
yarn add react-cropper
** 组件内使用**
import React, { useRef } from 'react';
/** 引入react-cropper */
import Cropper
1. 安装 react-cropper
npm install --save react-cropper //或者
yarn add react-cropper
2. 组件内引入
import Cropper from "react-cropper"; // 引入Cropper
import "cropperjs/dist/cropper.css"; // 引入Cropper对应的css
3. 函数式组件
1. jsx文件
import { useState } from 'react';
:gem_stone:优雅美观:基于Ant Design架构精心设计
:rocket:最新技术栈:使用React / dva / antd等前端前沿技术开发
:input_numbers: 模拟数据:实用的本地数据调试方案
roadhog版本升级2.0
$ git clone https://github.com/sosout/react-antd-dva.git
$ cd react-antd-dva
$ yarn install
$ yarn start
react+antd+react-cropper+lrz 实现图片剪裁后压缩上传
需要安装的依赖
npm install react-cropper 图片裁剪
npm install lrz 图片压缩
关于react-cropper图片裁剪器
参考官方文档https://github.com/roadmanfong/react-cropper,另外本文也会对其一些常用的功能进行注释
关于l...
最近公司项目在做一个求职工具类的小程序,小程序端有企业Logo的显示,为了保证图片在小程序端的展示效果,所以要求在后台管理系统进行企业Logo上传时需要限定上传的图片尺寸和比例。
为了减少管理人员的录入成本,需要在Logo上传前进行裁剪操作,保证上传进来的图片都是规则的正方。,此处用到了react-cropper组件,本文主要介绍了react-cropper组件在Ant Design Pro项目中...
2. 在webpack配置中启用sass
在项目根目录下找到config文件夹,找到webpack.config.dev.js和webpack.config.prod.js文件,分别进行以下操作:
- 在module.rules中添加对sass文件的处理:
```javascript
test: /\.scss$/,
use: [
loader: "sass-loader",
options: {
implementation: require("node-sass")
3. 全局变量
在src文件夹中创建一个variables.scss文件,并在其中定义要使用的全局变量,例如:
```scss
$primary-color: #007bff;
body {
background-color: $primary-color;
4. 在组件中使用sass文件
在需要使用sass文件的组件中,使用import语句导入变量和样式:
```jsx
import './MyComponent.scss';
import variables from './variables.scss';
然后就可以在组件中正常使用定义好的全局变量和样式了:
```jsx
const MyComponent = () => {
return (
<div className="my-component">
<h1 style={{ color: variables.primaryColor }}>Hello World</h1>
这样就可以在create-react-app中配置sass并使用全局变量了。
m0_37119354:
BraftEditor使用总结
发哥随手记:
create-react-app创建的项目配置多入口MPA模式。报Cannot read property ‘filter’ of undefined
再次重逢的时间:
create-react-app创建的项目配置多入口MPA模式。报Cannot read property ‘filter’ of undefined
一只小鸟er
create-react-app创建的项目配置多入口MPA模式。报Cannot read property ‘filter’ of undefined
昏黄烛火轻摇晃: