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 />组件进行头像裁剪。

  • 流程图

  • 首先我们要做一些基础工作

    • 安装,通过npmyarn
        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)
          }
      

lrz

  • 问题:调用lrz压缩完图片后,发现图片的大小并没有发生改变
    • 搜索后发现好像是因为quality属性设置太小了,压缩率太低的话,会是原始的file对象。
    • quality调高点就行

总结

  • 其实感觉这两个组件用起来并不是很困难,样式、逻辑方面可能还会更花时间点,组件本身的使用并没有什么难度,主要是教程都比较老,大多数教程都是一个模子刻出来的,在使用react-cropperref这个问题就折腾了比较久。感觉还是要多看官方的文档,不要因为全是英文就不想看。如果一个解决方案比较麻烦时,可以试着想想有没有其他取巧的办法可以达到要求。

参考

react-cropper

react:react-cropper插件,实现图片裁剪upload上传功能

lrz

localResizeIMG—lrz本地图片压缩裁剪插件

posted @ 2021-06-26 15:10  AAAdmin  阅读(532)  评论(2编辑  收藏  举报