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) 编辑 收藏 举报