第七穿插连第XXXX名士兵

记录学习的点滴,成长的历程。

导航

Vue2组件、功能插件实例运用 - vue-cropper(裁剪成 图形 图片)

问题起因

vue-cropper插件提供了非常好的图片裁剪功能,但美中不足的是,只能裁剪出长方形、正方形的图片,不能裁剪出其他图形的图片,尤其是圆形图片。
虽然可以先裁剪成正方形图片,然后给图片显示区域设置样式 border-radius: 50% 以达到显示成圆形的效果,但这抵不住一些产品,人家就要求裁剪成圆形图片,你这种方式就行不通了。

本文承接 Vue2组件、功能插件实例运用 - vue-cropper(图片裁剪) 继续。

1、重写图片裁剪弹出窗组件

1.1 template模板:

<template>
  <!--VueCropper图片裁剪组件加载,及该组件部分常用方法调用-->
  <div class="el-col el-col-24">
    <div class="cropper-w">
      <div class="cropper text-align-c" :class="graphicsState === 'square' ? '' : 'circular-w'">
        <VueCropper
          ref="cropper"
          :img="option.img"
          :outputSize="option.size"
          :outputType="option.outputType"
          :info="option.info"
          :full="option.full"
          :canMove="option.canMove"
          :canMoveBox="option.canMoveBox"
          :original="option.original"
          :autoCrop="option.autoCrop"
          :fixed="option.fixed"
          :fixedNumber="option.fixedNumber"
          :centerBox="option.centerBox"
          :infoTrue="option.infoTrue"
          :autoCropWidth="option.autoCropWidth ? option.autoCropWidth : 0"
          :autoCropHeight="option.autoCropHeight ? option.autoCropHeight : 0"
          :fixedBox="option.fixedBox"
        ></VueCropper>
      </div>
    </div>
    <div class="el-col el-col-24 flex-layout btn-w">
      <el-button type="success" @click.stop="rotateLeft">向左旋转</el-button>
      <el-button type="info" @click.stop="rotateRight">向右旋转</el-button>
      <el-button type="primary" @click.stop="getCropData">获取base64数据</el-button>
      <el-button type="primary" @click.stop="getCropBlob">获取blob数据</el-button>
    </div>
  </div>
</template>

1.2 配套样式:

<style scoped lang="less">
/*配套样式*/
.cropper-w {
  .cropper {
    width: auto;
    height: 50vh;
  }
}

.btn-w {
  margin-top: 20px;
}

/*新增样式如下*/
/*graphicsState等于circular(圆形)时起效,主要用于修改vue-cropper组件的原生样式*/
.circular-w {
  /deep/ .cropper-view-box {
    border-radius: 50%; // 将裁剪框由方形调整为圆形
  }
  /deep/ .cropper-face {
    background-color: transparent; // 清除裁剪框填充背景色
  }
}
</style>

1.3 组件引用及功能函数:

<script>
// 具体使用参照官方文档 https://github.com/xyxiao001/vue-cropper
// vue2 全局引用 和 组件内引用 用法不一样
import { VueCropper } from 'vue-cropper'

export default {
  name: 'imageCropper',
  components: { VueCropper },
  props: {
    dialogPar: {
      type: Object,
      required: false,
      default: () => {
        return {}
      }
    },
    /**新增props参数如下**/
    graphicsState: {
      // 此参数为新增参数,设置裁剪图形形状 square:方形、circular:圆形(默认:square(方形))
      type: String,
      required: false,
      default: () => {
        return 'square'
      }
    },
    autoCropOption: {
      // 此参数为新增参数,设置裁剪框参数,参数属性配置参照下面option
      type: Object,
      required: false,
      default: () => {
        return {
          info: false, // 是否显示裁剪框的宽高信息
          autoCropWidth: 300,
          autoCropHeight: 300
        }
      }
    }
  },
  data() {
    return {
      // 裁剪组件基础配置option,更多属性或更多具体说明参考官方文档
      option: {
        img: this.dialogPar.imgUrl, // 裁剪图片的地址
        info: false, // 裁剪框的大小信息(即是否显示裁剪框的宽高信息)
        outputSize: 0.9, // 裁剪生成图片的质量(0.1~1之间)
        outputType: 'jpeg', // 裁剪生成图片的格式(jpg(jpg 需要传入jpeg))
        canScale: false, // 图片是否允许滚轮缩放(这个属性貌似没得用,不管设置true,false都可以滚轮缩放)
        autoCrop: true, // 是否默认生成截图框
        // 注:这里需要注意,如果是裁剪成圆形图片,那么截图框的宽高就是必须设置的,且最好宽高一样
        autoCropWidth: 0, // 默认生成截图框宽度
        autoCropHeight: 0, // 默认生成截图框高度
        fixedBox: true, // 固定截图框大小 不允许改变
        fixed: true, // 是否开启截图框宽高固定比例
        fixedNumber: [1, 1], // 截图框的宽高比例(这是比例,按两个值的比值大小进行截图框的宽高的设置,[1,1]和[100,100]是一样的)
        full: true, // 是否输出原图比例的截图
        canMove: true, // 上传图片是否可以移动
        canMoveBox: false, // 截图框能否拖动
        original: false, // 上传图片按照原始比例渲染
        centerBox: false, // 截图框是否被限制在图片里面
        infoTrue: true // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
      },
      base64Data: {
        dataURL: '', // 用url方式表示的base64图片数据
        type: 'image/jpeg' //文件类型
      }
    }
  },
  created() {
    this.option = {
      ...this.option,
      ...this.autoCropOption
    }
  },
  methods: {
    /**
     * 向左边旋转90度
     */
    rotateLeft() {
      this.$refs.cropper.rotateLeft() // 只能固定向左边旋转90度,不接受设定旋转角度
    },
    /**
     * 向右边旋转90度
     */
    rotateRight() {
      this.$refs.cropper.rotateRight() // 只能固定向右边旋转90度,不接受设定旋转角度
    },

    /**
     * 将 base64数据 直接转换为 file对象
     */
    dataUrlToFile: function (dataUrl, fileName) {
      let arr = dataUrl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], fileName, { type: mime })
    },

    /**
     * base64 转Blob
     */
    base64ToBlob(base64Data) {
      let arr = base64Data.dataURL.split(',')
      let mime = arr[0].match(/:(.*?);/)[1] || base64Data.type
      // 去掉url的头,并转化为byte
      let bytes = window.atob(arr[1])
      // 处理异常,将ascii码小于0的转换为大于0
      let ab = new ArrayBuffer(bytes.length)
      // 生成视图(直接针对内存):8位无符号整数,长度1个字节
      let ia = new Uint8Array(ab)
      for (let i = 0; i < bytes.length; i++) {
        ia[i] = bytes.charCodeAt(i)
      }
      return new Blob([ab], {
        type: mime
      })
    },

    /**
     * 获取截图的 base64 数据
     */
    getCropData() {
      this.$refs.cropper.getCropData(data => {
        // 由于服务端文件上传接口统一接收file对象,从而 base64编码 数据在提交前需要将编码数据转为file对象
        // 将 base64编码数据 转成 file对象,有两种方式
        // 1、直接用 new File()方法 把 base64数据 转成 file对象
        let file = this.dataUrlToFile(data, this.dialogPar.fileInfo.name)

        // 2、先将 base64编码数据 转成 blob数据,然后用file对象内置方法,将blob数据转成file对象
        // this.base64Data.dataURL = data
        // let blobData = this.base64ToBlob(this.base64Data)
        // const file = new window.File([blobData], this.dialogPar.fileInfo.name, { type: data.type })

        // 获取图片数据后,将数据上传给父组件(imgUrl:图片预览链接地址,fileData:截取的图片数据)
        // 由于 base64 链接数据,img标签src属性可以直接使用,故此处不做处理
        this.$emit('emitPar', { imgUrl: data, fileData: file })
      })
    },

    /**
     * 获取截图的 blob 数据
     */
    getCropBlob() {
      this.$refs.cropper.getCropBlob(data => {
        if (this.graphicsState === 'circular') {
          // 此时的blob数据实际上是一个autoCropWidth*autoCropHeight的方形图片数据
          // 因此需要在此图的基础上进一步裁剪
          this.drawAndClipImage(data, this.dialogPar.fileInfo.name)
        } else {
          // 由于服务端文件上传接口统一接收file对象,从而数据在提交前需要将blob数据转为file对象,转换方法如下:
          const file = new window.File([data], this.dialogPar.fileInfo.name, { type: data.type })
          // 获取图片数据后,将数据上传给父组件(imgUrl:图片本地预览 blob数据地址,fileData:截取的图片数据 转换成的file对象)
          this.$emit('emitPar', { imgUrl: URL.createObjectURL(data), fileData: file })
        }
      })
    },

    /**
     * 新增方法(截圆形图时使用)
     * 将截图的 blob 数据绘制成图片,然后进一步根据需要裁剪成圆形图片
     */
    drawAndClipImage(file, fileName) {
      const reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onload = e => {
        const src = e.target.result
        const image = new Image()
        image.src = src
        image.onload = () => {
          const canvas = document.createElement('canvas')
          const width = image.width
          const height = image.height
          canvas.width = width
          canvas.height = height
          // 计算圆形图片的圆心及图片半径
          const circle = {
            x: width / 2,
            y: height / 2,
            r: width / 2
          }
          const context = canvas.getContext('2d')
          context.clearRect(0, 0, width, height)
          // 在canvas开始绘制前填充白色透明背景并设置透明度,用以清除图片裁剪后透明区域变成黑色的问题
          context.fillStyle = 'rgba(255, 255, 255, 0)'
          context.fillRect(0, 0, width, height)

          // 开始路径画圆,剪切处理
          context.save() // 保存当前canvas的状态
          context.beginPath()
          context.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2, false) // 创建弧/曲线(用于创建圆形或部分圆)
          context.clip() // 从原始画布剪切任意形状和尺寸的区域
          context.drawImage(image, 0, 0)
          context.restore() // 返回之前保存过的路径状态和属性,恢复状态

          // 将canvas图片转换成 blob数据
          canvas.toBlob(blob => {
            // 内部注释参考 getCropBlob方法 else部分
            const file = new File([blob], fileName, { type: blob.type })
            this.$emit('emitPar', { imgUrl: URL.createObjectURL(blob), fileData: file })
          })
        }
        image.onerror = err => {}
      }
      reader.onerror = err => {}
    }
  }
}
</script>

2、问题处理

2.1 图片裁剪完成后圆形区域以外的四个角,透明区域变成了黑色背景或纯白色背景

这是因为 在canvas开始绘制之前 没有设置背景色或者设置了纯白色背景色,但没有设置透明度造成的。

解决方法:在 canvas开始绘制前 设置白色填充色,并将透明度设置为0。直接使用 rgba()函数进行设置即可。 注:canvas的 fillStyle属性支持 rgba() 函数

const canvas = document.createElement('canvas')
const width = image.width
const height = image.height
canvas.width = width
canvas.height = height
const context = canvas.getContext('2d')
context.clearRect(0, 0, width, height)
// 在canvas开始绘制前填充白色背景并设置透明度,用以清除图片裁剪后透明区域变成黑色的问题
context.fillStyle = 'rgba(255, 255, 255, 0)'
context.fillRect(0, 0, width, height)

(网页截图)

2.2 设置白色填充色,并将透明度设置为0后,图片四周有白色方形边线

2.3 图片自身背景透明 或 底图拖动后,截图框裁剪到底图以外的透明区域,绘制成图后 透明区域变成了黑色背景

解决方法:插件配置参数 option 内的 outputType 设置为 png 即可。

option: {
  img: this.dialogPar.imgUrl, // 裁剪图片的地址
    info: false, // 裁剪框的大小信息(即是否显示裁剪框的宽高信息)
    outputSize: 0.9, // 裁剪生成图片的质量(0.1~1之间)
    // outputType: 'jpeg', // 裁剪生成图片的格式(jpg(jpg 需要传入jpeg))
    outputType: 'png', // 将生成图片的格式设置为png格式,可以防止图片背景透明或底图移动后,截图框裁剪到底图以外的透明区域,插件自动为截取的图片填充黑色背景
    canScale: false, // 图片是否允许滚轮缩放(这个属性貌似没得用,不管设置true,false都可以滚轮缩放)
    autoCrop: true, // 是否默认生成截图框
    // 注:这里需要注意,如果是裁剪成圆形图片,那么截图框的宽高就是必须设置的,且最好宽高一样
    autoCropWidth: 0, // 默认生成截图框宽度
    autoCropHeight: 0, // 默认生成截图框高度
    fixedBox: true, // 固定截图框大小 不允许改变
    fixed: true, // 是否开启截图框宽高固定比例
    fixedNumber: [1, 1], // 截图框的宽高比例(这是比例,按两个值的比值大小进行截图框的宽高的设置,[1,1]和[100,100]是一样的)
    full: true, // 是否输出原图比例的截图
    canMove: true, // 上传图片是否可以移动
    canMoveBox: false, // 截图框能否拖动
    original: false, // 上传图片按照原始比例渲染
    centerBox: false, // 截图框是否被限制在图片里面
    infoTrue: true // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
},

(outputType: 'jpeg')  (outputType: 'png')

posted on 2022-06-22 18:15  第七穿插连第XX名士兵  阅读(3599)  评论(1编辑  收藏  举报