背景
- 使用框架:基于umi 和 antDesign UI 的开发框架
- 使用插件:react-easy-crop
- 实现功能:任意尺寸的图片裁剪功能(可放大缩小 移动图片位置)
- 简单实现场景:点击图片上传logo 在本地选择一张图片 选择后弹出一个model弹窗,里面为待裁剪的图片,对图片进行拖拽放大缩小等操作后 点击确定按钮 将图片进行保存并实现上传功能
实现
项目安装react-easy-crop插件
npm install react-easy-crop --save
项目文件中引入该插件
import Cropper from 'react-easy-crop'
Upload上传组件和Modal弹窗组件
<div className="upload-wrapper">
<Upload
accept=".png,.jpg"
listType="picture-card"
showUploadList={false}
beforeUpload={this.beforeUpload}
disabled={opType === 'detail' || disable === '1'}
>
{
imgUrl
? opType === 'detail' || disable === '1'
? <ImageComp src={imgUrl} alt="avatar" style={{maxHeight: 100}}/>
: <img src={imgUrl} alt="avatar" style={{ maxWidth: '100%', maxHeight: '100%' }} />
: uploadButton
}
</Upload>
<Modal
onOk={this.saveImg}
bodyStyle={bodyStyle}
visible={modalVisible}
width={cropperModalWidth}
onCancel={this.onImgClose}
>
<Cropper
crop={crop} // 定位
zoom={zoom} // 变焦
ref="cropper"
image={srcCropper} // 目标图片信息
rotation={rotation} // 旋转
aspect={cropperAccept} // 横竖比例
onCropChange={this.onCropChange} // 位置修改函数
onZoomChange={this.onZoomChange} // 变焦修改函数
onCropComplete={this.onCropComplete} // 修改完毕后触发的函数
cropSize={{ width: width, height: height }} // 图片需要裁剪的长度和宽度 长款可以自己随意写
/>
</Modal>
</div>
点击上传 执行beforeUpload方法 实现图片的缓存的功能 为后面model弹窗提供可裁剪的图片信息
/**
* 在弹窗进行弹出之前 操作电脑中的文件或图片 并进行缓存
* @param file
* @returns {boolean}
*/
beforeUpload = (file) => {
this.setState({
uploading: true
}, () => {
// 使用fileReader 操作目标文件/图片
let reader = new FileReader()
// 创建img
const image = new Image()
let height
let width
//因为读取文件需要时间,所以要在回调函数中使用读取的结果
//开始读取文件
reader.readAsDataURL(file)
reader.onload = (e) => {
// 实现图片预加载功能
// 为图片设置属性
image.src = reader.result
image.onload = () => {
height = image.naturalHeight
width = image.naturalWidth
// 保存图片信息数据 为modal图片剪切展示时候用
this.setState({
srcCropper: e.target.result,
modalVisible: true
})
}
}
})
return false
}
crop:图片的position定位
zoom:图片的变焦
通过onCropChange方法和onZoomChange方法修改图片位置和大小
onCropChange = (crop) => this.setState({ crop })
onZoomChange = (zoom) => this.setState({ zoom })
onCropComplete方法 实现图片裁剪完成后 处理图片信息的功能(需要用到一个相关处理方法getCroppedImg 后面代码贴出)获取处理后的图片数据信息
import getCroppedImg from '@/components/ImgComp/getCroppedImg'
onCropComplete = async (croppedArea, croppedAreaPixels) => {
try {
croppedImage = await getCroppedImg(
this.state.srcCropper,
croppedAreaPixels
)
} catch (e) {
console.error(e)
}
}
最后点击确定 将裁剪好的图片以formData的形式传递给后台
saveImg = async () => {
const {
urlType,
photoType,
imgSubType,
handleAdditionUpload,
} = this.props
const formData = new FormData
const postData = { ...getDispatchParam(null, this.token), resType: 'document' }
_.each(postData, (value, key) => formData.append(key, value))
formData.append('file', croppedImage)
const res = await request.post(upImgURl, { data: formData })
if (res && res.code === 0) {
this.setState({
modalVisible: false,
uploading: false
}, () => {
message.success('图片上传成功')
// 如果有在上传后有其他的事情 调用此方法
handleAdditionUpload(photoType, imgSubType, urlType, res.oss)
})
}
}
这样将图片拖拽裁剪上传功能实现了
完整代码
ImgUpload文件
import React, { PureComponent } from 'react'
import {
Modal,
Upload,
message
} from 'antd'
import {Image as ImageComp} from 'antd'
import {
PlusOutlined,
LoadingOutlined
} from '@ant-design/icons'
import './index.less'
import _ from 'lodash'
import Cropper from 'react-easy-crop'
import request from '@/utils/request'
import { upImgURl } from '@/config/upload'
import { getDispatchParam } from '@/utils'
import getCroppedImg from '@/components/ImgComp/getCroppedImg'
let croppedImage
class Index extends PureComponent {
constructor(props) {
super(props)
this.state = {
logoFList: '',
modalVisible: false,
srcCropper: '',
crop: { x: 0, y: 0 },
zoom: 1,
aspect: 4 / 3,
imgUrl: '',
uploading: false
}
}
token = localStorage.getItem('token')
onCropChange = (crop) => this.setState({ crop })
onZoomChange = (zoom) => this.setState({ zoom })
/**
* 在弹窗进行弹出之前 操作电脑中的文件或图片 并进行缓存
* @param file
* @returns {boolean}
*/
beforeUpload = (file) => {
this.setState({
uploading: true
}, () => {
// 使用fileReader 操作目标文件/图片
let reader = new FileReader()
// 创建img
const image = new Image()
let height
let width
//因为读取文件需要时间,所以要在回调函数中使用读取的结果
//开始读取文件
reader.readAsDataURL(file)
reader.onload = (e) => {
// 实现图片预加载功能
// 为图片设置属性
image.src = reader.result
image.onload = () => {
height = image.naturalHeight
width = image.naturalWidth
// 保存图片信息数据 为modal图片剪切展示时候用
this.setState({
srcCropper: e.target.result,
modalVisible: true
})
}
}
})
return false
}
onCropComplete = async (croppedArea, croppedAreaPixels) => {
try {
croppedImage = await getCroppedImg(
this.state.srcCropper,
croppedAreaPixels
)
} catch (e) {
console.error(e)
}
}
onImgClose = () => {
this.setState({
modalVisible: false,
uploading: false
})
}
saveImg = async () => {
const {
urlType,
photoType,
imgSubType,
handleAdditionUpload,
} = this.props
const formData = new FormData
const postData = { ...getDispatchParam(null, this.token), resType: 'document' }
_.each(postData, (value, key) => formData.append(key, value))
formData.append('file', croppedImage)
const res = await request.post(upImgURl, { data: formData })
if (res && res.code === 0) {
this.setState({
modalVisible: false,
uploading: false
}, () => {
message.success('图片上传成功')
// 如果有在上传后有其他的事情 调用此方法
handleAdditionUpload(photoType, imgSubType, urlType, res.oss)
})
}
}
render() {
const {
crop,
zoom,
rotation,
uploading,
srcCropper,
modalVisible
} = this.state
const {
width,
height,
imgUrl,
opType,
accept,
disable,
uploadName,
modalWidth,
} = this.props
let cropperAccept = accept ?? 4/3
let cropperModalWidth = modalWidth ?? 800
let cropperUploadName = uploadName ?? '上传'
const uploadButton = (
<div>
{ uploading
? <LoadingOutlined />
: <><PlusOutlined /><div style={{ marginTop: 8 }}>{cropperUploadName}</div></>
}
</div>
)
const bodyStyle = { width: '100%', height: 600 }
return (
<div className="upload-wrapper">
<Upload
accept=".png,.jpg"
listType="picture-card"
showUploadList={false}
beforeUpload={this.beforeUpload}
disabled={opType === 'detail' || disable === '1'}
>
{
imgUrl
? opType === 'detail' || disable === '1'
? <ImageComp src={imgUrl} alt="avatar" style={{maxHeight: 100}}/>
: <img src={imgUrl} alt="avatar" style={{ maxWidth: '100%', maxHeight: '100%' }} />
: uploadButton
}
</Upload>
<Modal
onOk={this.saveImg}
bodyStyle={bodyStyle}
visible={modalVisible}
width={cropperModalWidth}
onCancel={this.onImgClose}
>
<Cropper
crop={crop}
zoom={zoom}
ref="cropper"
image={srcCropper}
rotation={rotation}
aspect={cropperAccept}
onCropChange={this.onCropChange}
onZoomChange={this.onZoomChange}
onCropComplete={this.onCropComplete}
cropSize={{ width: width, height: height }}
/>
</Modal>
</div>
)
}
}
export default Index
getCroppedImg文件
const createImage = url =>
new Promise((resolve, reject) => {
const image = new Image()
image.addEventListener('load', () => resolve(image))
image.addEventListener('error', error => reject(error))
image.setAttribute('crossOrigin', 'anonymous') // needed to avoid cross-origin issues on CodeSandbox
image.src = url
})
function getRadianAngle(degreeValue) {
return (degreeValue * Math.PI) / 180
}
/**
* This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
* @param {File} image - Image File url
* @param {Object} pixelCrop - pixelCrop Object provided by react-easy-crop
* @param {number} rotation - optional rotation parameter
*/
export default async function getCroppedImg(imageSrc, pixelCrop, rotation = 0) {
const image = await createImage(imageSrc)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const maxSize = Math.max(image.width, image.height)
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2))
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea
canvas.height = safeArea
// translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2)
ctx.rotate(getRadianAngle(rotation))
ctx.translate(-safeArea / 2, -safeArea / 2)
// draw rotated image and store data.
ctx.drawImage(
image,
safeArea / 2 - image.width * 0.5,
safeArea / 2 - image.height * 0.5
)
const data = ctx.getImageData(0, 0, safeArea, safeArea)
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width
canvas.height = pixelCrop.height
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
)
// As Base64 string
// return canvas.toDataURL('image/jpeg');
// As a blob
return new Promise(resolve => {
canvas.toBlob(file => {
resolve(file)
}, 'image/jpeg')
})
}
在此需要上传一个长宽为72px的图片
弹窗打开对图片进行裁剪处理
点击确定后上传图片