react-cropper + lrz 实现头像裁剪压缩上传
技术概述
- react-cropper主要是让用户可以自定义头像(图片剪切上传),lrz主要是对用户上传的文件进行压缩,避免因为图片过大而导致加载太慢,影响用户体验
- 学习该技术的原因:项目需要实现这个功能
- 难点:参考资料较老,官方给的demo是ts的
技术详述
-
我们先来了解下
react-cropper
组件- 使用方式
onCropperInit = cropper => { this.cropper = cropper } render() { <Cropper src="https://xxx/xxx.jpg" // 图片路径,也可以是base64的值 style={{ height: 400 , width: 600}} preview='.cropper-preview'//预览名 viewMode={1} // 定义cropper的视图模式 zoomable // 是否允许放大图像 aspectRatio={1} // 宽高比 guides // 显示在裁剪框上方的虚线 background // 是否显示背景的马赛克 rotatable //是否允许旋转 onInitialized={this.onCropperInit.bind(this)} //获取cropper对象 /> }
- 此时我们通过
getCroppedCanvas()
即可获得到二进制对象this.cropper.getCroppedCanvas().toBlob(async blob => { console.log(blob) ... })
- 其他更具体的参数值可以参见官网,若是觉得英文看起来很吃力,也可以看这位博主的博客
- 使用方式
-
我们再来了解下
lrz
组件- 使用方式
upload = () => { const avatar = document.querySelector('#avatar').file[0]; //lrz(file, [options]); lrz(avatar).then(res => { //压缩完的base64 console.log(res.base64) }).catch(err => { //捕获异常 ... }).always(() => { //总是执行的代码 ... }) } render() { <input id="avatar" type="file" /> }
- 如果
lrz
中传入的图片不是通过<input />
上传的,也可以通过传入文件路径lrz('./xxx/xx/x.png').then(res => { ... })
- options对象是可选参数,可以控制图片质量等
lrz('./xxx.png',{ quality:0.7, //图片压缩质量,取值 0 - 1,默认为 0.7 fieldName:"demo" //后端接收的字段名,默认:file,一般不用这项,我们要上传数据的话,可以自定义FormData对象 }).then(res => { console.log(res.base64) //压缩后的图片base64 console.log(res.origin) //原始的file对象,里面存放了一些原始文件的信息,例如大小、日期等 })
- 其他更具体的参数值可以参见官网也可以看这位博主的博客
- 使用方式
-
下面我们来看个具体例子,帮助我们巩固这两个组件的使用
下文使用的均是类式组件,这个demo我们可以分为两个页面,一个页面
Setup.js
放置<input />
标签方便用户进行头像上传,上传后通过lrz
组件进行图像压缩得到base64
编码,通过路由跳转携带着这个编码传给另一个页面EditAvatar.js
中的<Cropper />
组件进行头像裁剪。 -
流程图
-
首先我们要做一些基础工作
- 安装,通过
npm
或yarn
npm install --save react-cropper npm install --save lrz
- 文件引入
import Cropper from 'react-cropper' // 引入Cropper import 'cropperjs/dist/cropper.css' // 引入Cropper对应的css import lrz from 'lrz' //引入lrz
- 安装,通过
-
接着我们看
Setup.js
页面的代码通过
fileData
获取用户上传的图片对象
通过fileData.size
来判断图片大小,避免上传太大图片
通过readAsDataURL
读取指定file
对象,读取完成后触发onload
事件获取base64
编码
调用lrz
进行图片压缩
通过路由跳转携带参数(压缩后的base64编码,图片名称)跳转至EditAvatar.js
页面avatarChange = (e) => { e.preventDefault(); const fileData = e.target.files[0]; if (!(fileData.size / 1024 / 1024 < 2)) { Toast.fail('图片大小不能超过2M', 2) } const reader = new FileReader(); reader.readAsDataURL(fileData); reader.onload = () => { lrz(reader.result, {quality: 0.5}).then(res => { this.props.history.push({ pathname: '/main/profile/setup/avatar', state: { file: { url: res.base64, name: fileData.name } } }) }) } } render() { <div> <input onChange={this.avatarChange} id="avatar" type="file" accept="image/*" /> </div> }
-
最后我们来看下
EditAvatar.js
的代码通过
componentDidMount
,在页面渲染完毕后获取Setup.js
携带过来的参数file
并把它设置至state
中,此时通过src
属性,<Cropper />
组件便能显示
在裁剪完毕,上传时,调用this.cropper.getCroppedCanvas().toBlob()
获取blob
对象
后端接口实际要求是file
对象通过new File()
将blob
对象转为file
对象
将file
对象丢到FormData
里上传即可state = { file: { url: null, name: '' } } //获取cropper引用 onCropperInit = cropper => { this.cropper = cropper } //上传图片 handleSubmit = () => { const {name} = this.state.file; this.cropper.getCroppedCanvas().toBlob(async blob => { let formData = new FormData() const fileOfBlob = new File([blob], name); formData.append('file', fileOfBlob) this.uploadAvatar(formData); }) } uploadAvatar = (formData) => { //axios请求 ... } componentDidMount() { const {file} = this.props.location.state this.setState({ file, }) } render() { <div> <button onClick={this.handleSubmit}>上传</button> <Cropper style={{ height: "100%", width: "100%" }} background={false} src={this.state.file.url} onInitialized={this.onCropperInit.bind(this)} viewMode={1} zoomable={false} rotatable={true} aspectRatio={1} guides /> </div> }
技术使用中遇到的问题和解决过程
react-cropper
-
问题:调用
this.cropper.getCroppedCanvas().toBlob()
,this.cropper
显示是undefined
。- 网上的博客教程大多都比较老了,一直找不到解决方案,经过阅读官方文档发现当
version >= 2.0.0
时已经不支持通过ref="xxx"
或ref={cropper => this.cropper = cropper}
方式来获取cropper
对象了。 - 可以通过下面来获取
cropper
引用onCropperInit = cropper => { this.cropper = cropper } <Cropper ... onInitialized={this.onCropperInit.bind(this)} ... />
- 网上的博客教程大多都比较老了,一直找不到解决方案,经过阅读官方文档发现当
-
问题:IOS机型在上传图片时,图片会自动旋转
- 通过搜索引擎发现,时因为IOS机型在图片上传时图片的
Orientation
会被设置成一个特殊值。 - 本来时想通过
exif.js
来解决这个问题,但感觉太过麻烦,就通过设置<Cropper />
组件的rotatable={true}
属性。 - 并设置了一个旋转按钮,为按钮绑定了一个点击事件。
rotate = () => { this.cropper.rotate(90) }
- 通过搜索引擎发现,时因为IOS机型在图片上传时图片的
lrz
- 问题:调用lrz压缩完图片后,发现图片的大小并没有发生改变
- 搜索后发现好像是因为
quality
属性设置太小了,压缩率太低的话,会是原始的file对象。 quality
调高点就行
- 搜索后发现好像是因为
总结
- 其实感觉这两个组件用起来并不是很困难,样式、逻辑方面可能还会更花时间点,组件本身的使用并没有什么难度,主要是教程都比较老,大多数教程都是一个模子刻出来的,在使用
react-cropper
时ref
这个问题就折腾了比较久。感觉还是要多看官方的文档,不要因为全是英文就不想看。如果一个解决方案比较麻烦时,可以试着想想有没有其他取巧的办法可以达到要求。
参考
react-cropper
react:react-cropper插件,实现图片裁剪upload上传功能
lrz
localResizeIMG—lrz本地图片压缩裁剪插件