基于 vue-cropper 的简单图片裁剪插件实现

起由

最近在工作中遇到了在 VUE 下需要简单裁剪图片的需求,同时还要对图片进行相关大小和格式的限制

这个时候就想起了在 jq 时代大名鼎鼎的 cropper.js 了

简单的界面设计如下图:

效果图

分析

从图中可以看出,我们需要的功能主要有如下几方面的内容:

  • 获取到图片资源
  • 一个带模板的弹出层
  • 截图组件
  • 交互区域(这里指图中的确定取消按钮)
  • 足够的可扩展性

实现思路

首先需要完成的当然是获取到用户上传的文件,第一个想法是使用原生的方案,也即使用原生的 input 框进行相关事件的获取

<input type="file" />

这样的方式扩展是最灵活的,但是同时也是最繁琐的,事无巨细都需要一一考虑,这不符合我们敏捷开发的原则

考虑到项目中已引入 elementui 的环境,故考虑使用 \color{#409eff}{element}中的 \color{#409eff}{Upload} 组件来进行获取文件的操作

项目需求决定只能操作单张图片,进行相关配置后效果图如下:

触发选择框

获取到了文件之后首先需要对于 文件类型大小 进行校验,确定其为我们真实希望获得的类型和满足我们对于图片的大小限制

接着我们需要将获取到的图片传递给截图组件,且在用户按下确认或取消时能 立即响应, 完成获取截图的文件和截图等过程,最后将新截的图片加进预览并保存新的图片状态

以上便是我们在这个组件中整个想要完成的功能流程

Show me the code

其实在同样的思路下,会有千万种实现方法,以下仅是我个人对于这一想法的一些实践,欢迎讨论指正

首先是有 \color{#409eff}{Upload}标签(此处隐去引入库及挂载过程)

          <el-upload
            :before-upload="beforeUpload"      //  上传前的事件,在这里做文件类型和大小的检查
            :file-list="showImgUrl"            //   图片预览列表
            :http-request="picUpload"          //  上传事件,覆盖默认上传事件,应在此处将图片传入截图组件
            :limit="1"                         //  文件数量限制
            :multiple="false"                  //  选择文件时不允许选择多文件
            :on-exceed="overFile"              //  文件数量超出限制时的钩子函数,应在此处做覆盖以及提示
            :on-remove="removeFile"            //  图片删除时的钩子函数,应在此时清空图片的缓存相关操作 
            action                             //  默认跳转地址,这里设置为空
            list-type="picture-card"           //  文件选择框的样式以及插入动画
            ref="upload"                       //  DOM 钩子
          >
          <i class="el-icon-plus"></i>         //  样式需要的图标
          </el-upload>

我们整个流程都可以从 \color{#409eff}{Upload} 标签的生命周期处体现

接着我们来一个一个看看在这些钩子函数里我们都做了什么吧:

    // 图片上传前校验
   //  这里对图片类型和大小做出限制 
    beforeUpload(file) {
      const isLt5M = file.size < 5 * 1024 * 1024
      const fileType = file.type.split('/')[0]
      if (!isLt5M) {
        this.$message.error('图片大小不能超过5M')
        return false
      }
      if (fileType !== 'image') {
        this.$message.error('只能上传图片格式')
        return false
      }
    },

图片校验通过后即进入裁剪流程,我们是通过覆盖默认上传事件实现的

在这里由于从本地读出时没有相关的 url 信息,所以我们在这里使用 fileReader API 把获取到的图片文件转化成 base64 的格式,然后再进行相关的操作

  //  在生命周期挂载完成的时候判断浏览器中是否为现代浏览器
  //  若不支持 FileReader API 则提示并退出编辑操作
  mounted() {
    if (!window.FileReader) {
      this.$alert('您的浏览器版本过旧,请更换现代浏览器进行操作', '提示').then(
        () => {
          this.__cancel()
        }
      )
    }
  },
    // 图片上传前触发裁剪组件
    // 将图片读出并在完成时触发裁剪 
    picUpload(option) {
      let file = option.file
      if (file) {
        this.fileReader.readAsDataURL(file)
      }
      this.fileReader.onload = () => {
        let src = this.fileReader.result
        this.cropperShow = true
        this.cropperImg = src
      }
    },

这里应该调起裁剪插件,猛地发现,插件我都还没新增鸭

可怕!!

于是就回去翻了翻 cropperjs 的文档,emmmm, 真的没有意外,还是 jq 时代的样子,没有办法直接拿下来就用,那么这个轮子究竟有没有人已经造过了呢,目光下移, vue-cropper 进入了我的视线

(这里忽略下载引入的过程,直接从使用开始)

    //  图片裁剪引用
    //  el-dialog 为 element 中弹框组件
    //  cropperShow: 是否显示  load: 用户是否处于交互状态  cropperImg: 图片源 
    //  更多配置项请参考 vue-cropper 相关文档
    <el-dialog :visible.sync="cropperShow" width="640px" :close-on-click-modal="false" :show-close="false">
      <span slot="title">操作图片</span>        // 标题文字
      <vueCropper
        ref="cropper"                          // Dom钩子,用于获取绑定其上的相关属性及函数
        class="cropper"
        :img="cropperImg"                      // 裁剪图片源
        :can-scale="!load"                     // 是否允许滚轮缩放
        :auto-crop="true"                      // 是否默认生成截图框
        :fixed-box="true"                      // 是否固定截图框大小
        enlarge="2"                            // 输出比例倍数 
        :output-size="option.size"             // 生成图片质量
        :output-type="option.outputType"       // 生成图片格式
        :center-box="option.centerBox"         // 截图框是否限制在图片内
        :fixed-number="[1,1]"                  // 截图框的宽高比例
      ></vueCropper>
      <div class="cropper-btn">
        <el-button type="primary" @click="confirm" :loading="load">确定</el-button>
        <el-button @click="cancel" :disabled="load">取消</el-button>
      </div>
      <el-dialog                                // 用户交互中弹出的提示弹框
        width="30%"
        :visible.sync="load"
        append-to-body
      >
        <div slot="title">飞速处理图片中...</div>
      </el-dialog>
    </el-dialog>
     //  配置参数
      option: {
        size: 0,
        outputType: '',
        centerBox: false
      },

此时我们就能看到文章开头所呈现的相关效果了,并且我们也能通过插件提供的钩子函数获取到相应的图片数据

但是我们想要走完整个流程,还必须添加交互响应事件以及进行相关的上传操作

    // 确定选择
    // dom.getCropData() 获取当前截图框图像的 base64 格式的数据
    // 获取成功数据后调起上传操作
    confirm() {
      this.$refs.cropper.getCropData(data => {
        this.httpRequest(data)
        this.load = true
      })
    },
    // 取消
    cancel() {
      console.log('cancel edit picture')
      // 关闭裁剪组件相关操作
    }
    // 图片裁剪后上传
    // 这里可进行进一步优化将 config 请求配置写入 http 请求库中
    httpRequest(src) {
      let config = {
        url: '***',             // 这里填后端的图片上传链接
        method: 'post',
        data: {
          //  截取 base64 文件部分
          image: src.split(',')[1]
        }
      }
      this.$ehttp
        .postUpload(config)
        .then(res => {
          console.log(res, '*********************************************')
          if (res.data.code === 0) {
            this.params.properties.imageHash = res.data.data
            this.$message.success('图片编辑成功')
            this.$refs.upload.clearFiles()                              //  清空预览列表
            this.showImgUrl = [{ name: 'pic', url: src }]               // 将新上传图片插入上传列表  
            this.cropperShow = false
          } else {
            // this.$message.error('res.dat')
            this.$refs.upload.clearFiles()
          }
        })
        .catch(err => {
          this.$message.error(err)
        })
    },

结语

至此我们整个裁剪插件就已经全部完成了,通过上传回参可以任意处理裁剪后的图像了

整个流程下来主要的感觉是对于图片格式的转换会相对的比较繁琐

vue-cropper 插件本身只完成裁剪的功能,方便我们在此基础上开发个性化的裁剪插件

同时由于其基于 cropperjs 开发,所以在截图框的样式方面由插件写死了,若是我们对于截图框的样式也有个性化需求则可 down 下源码进行自定义修改,这已经超出了本文的范围故会在后面的文章中进行相关的研究(坑先挖好,填不填就看心情了hhh)


傲娇.jpg

参考资料

ElementUI: https://element.eleme.cn/#/zh-CN/component/upload
CropperJS: https://github.com/fengyuanchen/cropper
vue-cropper: https://github.com/xyxiao001/vue-cropper

PS

最后当然是日常求赞环节啦~

Clancy
2019.5.30

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容