二次封装vue-cropper实现图片裁剪

业务场景

        需要上传图片实现图片裁剪,固定裁剪框的大小,让裁剪出来图标是正方形。但是考虑到有些图标是长方形,裁剪后图标可能裁剪不全,可以填充底色且让裁剪框可以超出图片,对与超出裁剪框的位置给他填充颜色。

        点击+号可以放大,点击-号可以缩小。也可以进行图片的左旋右旋。颜色那里用的iview的颜色选择器,选择完颜色后,可以对超出裁剪框的颜色进行填充底色。点击🔍可以实现裁剪图片的预览。这里是基于我之前写的博客js实现下载本地文件_Humanideal的博客-CSDN博客中图片预览的实现,有需要可以去看之前的博客。vue-cropper源码是自己写了个modal遮罩层,写了一些样式。但是对于我的业务场景,已经打开了两个遮罩层了,再使用一个遮罩层图层太过复杂,且图片不突出,效果不明显,所以通过a标签预览的时候另外打开个弹窗进行图片预览。

Blob对象格式

Blob对象是二进制数据,但它是类似文件对象的二进制数据。file继承blob。

实现

基于iview的upload上传组件和vue-cropper - npm实现的。

先下载vue-cropper

npm i vue-cropper或yarn add vue-cropper。这里用的是最新版0.6.4。

父组件

<template>
  <Modal>
        <Upload
          v-if="!isUpload && !formData.iconUrl"
          :before-upload="handleUpload"
          action=""
        >
          <Button icon="ios-cloud-upload-outline">
            上传图片
          </Button>
        </Upload>

        <div
          v-else
          style="display: flex; align-items: center;"
        >
          <img
            ref="img"
            :src="formData.iconUrl"
            style="width: 100px; height: 100px; margin-right: 10px;"
          >
          <Button
            type="primary"
            ghost
            @click="cropperEvent"
          >
            裁剪图片
          </Button>
        </div>
      
    <Modal
      v-model="modal"
      title="图片裁剪"
      @on-ok="ok"
      @on-cancel="cancel"
    >
      <cropper
         v-if="cropperData.img"
        :cropper-data="cropperData"
        :cropper-style="cropperStyle"
        @update="img=>formData.iconUrl = img"
      />
    </Modal>
  </Modal>
</template>

<script>
import cropper from '../../components/cropper/cropper.vue'

export default {
  name: 'MediaOperatorModal',
  components: {
    cropper
  },
  data() {
    return {
      isUpload: false,
      file: null,
      modal: false,
      cropperStyle: {
        height: '300px'
      },
      cropperData: {
        img: '', // 裁剪图片地址
        outputSize: 1, // 裁剪生成图片质量
        outputType: 'png', // 裁剪生成图片格式
        canScale: true, // 图片是否允许滚轮播放
        autoCrop: true, // 是否默认生成截图框 false
        info: false, // 是否展示截图框信息
        autoCropWidth: 200, // 生成截图框的宽度
        autoCropHeight: 200, // 生成截图框的高度
        canMoveBox: true, // 截图框是否可以拖动
        fixedBox: true, // 固定截图框的大小
        canMove: false, // 上传图片是否可拖动
        centerBox: false, // 截图框限制在图片里面
      }
    }
  },
  methods: {
   //将base64转file
   base64ToFile(base64, fileName) {
      const arr = base64.split(',')
      const mime = arr[0].match(/:(.*?);/)[1]
      const bstr = atob(arr[1])
      let n = bstr.length
      const u8arr = new Uint8Array(n)

      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], fileName, { type: mime })
    },
    handleUpload(file) {
      this.file = file
      const reader = new FileReader()
      reader.onload = (e) => {
        this.cropperData.img = e.target.result // 这就是图片的base64值
      }
      reader.readAsDataURL(file)
      this.modal = true
      this.isUpload = true
      return false
    },
     // URL转Base64
    asciiToBase64(asciiString) {
      let base64String = ''
      let charCode
      for (let i = 0; i < asciiString.length; i++) {
        charCode = asciiString.charCodeAt(i)
        base64String += String.fromCharCode(charCode)
      }
      base64String = btoa(base64String)
      return base64String
    },
    cropperEvent() {
      this.modal = true
      this.cropperData.img = this.asciiToBase64(this.formData.iconUrl)
    },
  }
}
</script>

        cropper组件上的v-if是因为父子组件传值异步问题,子组件渲染的时候,对cropperDara.img操作赋值还未进行。使用v-if,当cropperDara.img有值后再渲染子组件。cropperStyle和cropperData都是考虑到封装之后属性的可操作性,方便用户自己传入参数和样式。同时子组件那边也对属性有默认值,如果用户没自定义这些属性,就用写好的默认值。

上传给后端文件一般的格式是file,这里上传需要把之前的base64转file格式才能上传。

注意:这里之前是使用v-model="cropperData.img",但是在后面重新上传文件进行图片裁剪的时候发现出现了问题,通过打断点发现数据传递过去后会立马被之前的数据给覆盖,就是父子组件一直在操作这个cropperData.img数据的过程中赋值出现了问题,所以这边没有通过v-model,组件监听来改变这个数据。而是在父组件使用一个副本formData.iconUrl来接收子组件,传递给子组件的cropperData.img在父组件中只有上传文件后转成base64格式进行了一下处理,其他的都交给子组件处理。

什么是base64

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。

打印的base64格式

base64转file
base64ToFile(base64, fileName) {
      const arr = base64.split(',')
      const mime = arr[0].match(/:(.*?);/)[1]
      const bstr = atob(arr[1])
      let n = bstr.length
      const u8arr = new Uint8Array(n)

      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], fileName, { type: mime })
}

        这个函数首先将base64字符串分割为两部分:数据类型和实际的base64数据。然后,它使用atob()函数将base64数据解码为二进制字符串,然后将该字符串转换为Uint8Array(Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。)。最后,它使用这个Uint8Array来创建一个新的File对象。 注意,你需要提供一个文件名和扩展名,这应该与你的base64数据的原始文件类型匹配。

    arr[0].match(/:(.*?);/)[1] 这段代码是在使用正则表达式匹配字符串。arr[0] 是base64字符串的前半部分,它包含了数据类型信息,例如 "data:image/jpeg;base64"。.match(/:(.*?);/) 是在使用正则表达式匹配这个字符串。正则表达式 :(.*?); 的意思是匹配以冒号 ":" 开始,以分号 ";" 结束的任何字符。其中的 .*? 是一个非贪婪匹配,它会尽可能少的匹配字符。[1] 是在获取匹配结果的第二个元素。因为 .match() 方法返回的是一个数组,数组的第一个元素(索引为0)是完整的匹配结果,第二个元素(索引为1)是第一个括号内匹配的结果。在这个例子中,它将返回数据类型,例如 "image/jpeg"。所以,整个表达式的意思是从base64字符串的前半部分中提取出数据类型。

url转base64

asciiToBase64(asciiString) {
      let base64String = ''
      let charCode
      for (let i = 0; i < asciiString.length; i++) {
        charCode = asciiString.charCodeAt(i)
        base64String += String.fromCharCode(charCode)
      }
      base64String = btoa(base64String)
      return base64String
 }

         charCodeAt() 方法可返回指定位置的字符的 Unicode 编码 

         fromCharCode()是字符串对象的一个方法,它将Unicode转换为字符(字符串)。

    btoa() 方法可以将一个二进制字符串(例如,将字符串中的每一个字节都视为一个二进制数据字节)编码为 Base64 编码的 ASCII 字符串。

子组件

<template>
  <div class="cropper">
    <vueCropper
      ref="cropper"
      v-bind="option"
      :style="initCropperStyle"
    />
    <div style="margin-top: 10px;">
      <Button
        type="primary"
        icon="md-add"
        @click="changeScale(1)"
      />
      <Button
        type="primary"
        icon="md-remove"
        @click="changeScale(-1)"
      />
      <Button
        type="primary"
        icon="md-return-left"
        @click="turnLeft"
      />
      <Button
        type="primary"
        icon="md-return-right"
        @click="turnRight"
      />
      <Button
        type="primary"
        icon="md-search"
        @click="preview"
      />
      <ColorPicker v-model="option.fillColor"  @on-change="change"/>
    </div>
    <a
      ref="previewBox"
      href=""
      target="_blank"
    />
  </div>
</template>

<script>
import { VueCropper } from 'vue-cropper'
export default {
  name: 'Cropper',
  components: {
    VueCropper
  },
  props: {
    cropperData: {
      type: Object
    },
    cropperStyle: {
      type: Object
    }
  },
  data() {
    return {
      previews: {},
      option: {
        img: '', // 裁剪图片地址,这里可以本地图片或者链接,链接不用require
        outputSize: 1, // 裁剪生成图片质量
        outputType: 'png', // 裁剪生成图片格式
        canScale: true, // 图片是否允许滚轮播放
        autoCrop: true, // 是否默认生成截图框 false
        info: false, // 是否展示截图框信息
        autoCropWidth: 200, // 生成截图框的宽度
        autoCropHeight: 200, // 生成截图框的高度
        canMoveBox: true, // 截图框是否可以拖动
        fixedBox: true, // 固定截图框的大小
        canMove: false, // 上传图片是否可拖动
        centerBox: false, // 截图框限制在图片里面
        fillColor: ''
      },
      initCropperStyle: {
        height: '300px',
        width: '100%'
      }
    }
  },
watch: {
    cropperData: {
      handler() {
        this.option = this.cropperData ? { ...this.cropperData } : this.option
      },
      deep: true,
      immediate: true
    }
  },
  mounted() {
    this.initCropperData()
  },
  methods: {
    change(data) {
      this.option.fillColor = data
      this.cropMoving()
    },
    initCropperData() {
      Object.assign(this.initCropperStyle, this.cropperStyle)
    },
    turnLeft() {
      this.$refs.cropper.rotateRight()
    },
    turnRight() {
      this.$refs.cropper.rotateLeft()
    },
    changeScale(num) {
      num = num || 1
      this.$refs.cropper.changeScale(num)
    },
    preview() {
      const link = this.$refs.previewBox
      this.$refs.cropper.getCropBlob((data) => {
        const blob = data
        const url = window.URL.createObjectURL(blob)
        link.href = url
        link.click()
        link.href = '#'
      })
    },
   cropMoving() {
      this.$refs.cropper.getCropData((data) => {
        this.value = data
      })
    }
  }
}
</script>

        子组件这边一上来就调用this.initCropperData(),将外部传来的参数与默认参数进行覆盖合并。如果modal模态框是写到子组件里面的,也可以监测modal的v-model属性,为true再进行调用this.initCropperData()。在子组件上绑上v-model="isShow",子组件内部的Modal上绑上v-model="isShow",@on-cancel="close",close方法内写this.$emit('input', false)。然后在watch监听isShow,为true的时候再调用this.initCropperData()

 <cropper
        v-if="cropperData.img"
        v-model="isShow"
        :data="cropperData"
        :cropper-style="cropperStyle"
 />
props: {
    value: {
      type: Boolean,
      default: false
    }
}

computed: {
    isShow: {
      get () {
        return this.value
      },
      set (val) {
        this.$emit('input', val)
      }
    }
}

watch: {
    isShow (val) {
      val && this.initCropperData()
    }
}

具体可以参考我之前的博客。利用v-model原理实现修改props_v-model绑定props-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要使用vant-uploader和vue-cropper来实现裁剪图片并上传,你可以按照以下步骤进行操作: 1. 首先,安装vant和vue-cropper插件。你可以使用npm或yarn来安装它们。 2. 在你的Vue组件中,引入vant-uploader和vue-cropper组件。 3. 在模板中,使用vant-uploader组件来实现图片上传功能。设置上传的action属性为你的上传接口地址,并设置on-success事件来处理上传成功后的逻辑。 4. 在on-success事件中,获取到上传成功后的图片地址,并将其传递给vue-cropper组件。 5. 在vue-cropper组件中,设置裁剪框的样式和裁剪比例等属性。使用v-model指令来绑定裁剪后的图片数据。 6. 在提交按钮的点击事件中,将裁剪后的图片数据上传到服务器。 下面是一个简单的示例代码: ```vue <template> <div> <van-uploader action="/upload" :on-success="handleUploadSuccess" ></van-uploader> <vue-cropper v-if="showCropper" :src="cropperSrc" :output-size="{ width: 200, height: 200 }" :output-type="'jpeg'" :fixed-box="true" :fixed-number="\[1, 1\]" v-model="croppedImage" ></vue-cropper> <button @click="handleSubmit">提交</button> </div> </template> <script> import { VanUploader } from 'vant'; import VueCropper from 'vue-cropper'; export default { components: { VanUploader, VueCropper, }, data() { return { showCropper: false, cropperSrc: '', croppedImage: '', }; }, methods: { handleUploadSuccess(response) { // 获取上传成功后的图片地址 const imageUrl = response.data.imageUrl; // 显示裁剪组件 this.showCropper = true; // 设置裁剪组件的图片地址 this.cropperSrc = imageUrl; }, handleSubmit() { // 提交裁剪后的图片数据到服务器 // this.croppedImage 包含裁剪后的图片数据 }, }, }; </script> ``` 请注意,以上代码只是一个简单的示例,你需要根据你的实际需求进行适当的修改和调整。同时,你还需要在后端实现相应的上传和裁剪功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值