VUE---图片裁剪组件
最近在开发一些项目,需要手机端上传图片,然后优化了一个裁剪的功能,遇到了一些神奇的BUG,然后就自己弄了一个插件。
首先采用的技术实现方案是:vue-cropper
这个是一个比较不错的图片裁剪功能,实现简单的裁剪是没有问题的,但是存在一个BUG,图片放大后,将裁剪框滑动的边缘,则无法缩小。
出现BUG的原因:该插件图片放大使用的是CSS3属性【scale】进行放大,缩小,由于限制了裁剪框在图片内,而【scale】的缩放的基准点是中心缩放,所以就会产生BUG,而且这个插件没有控制缩放基准点的配置,尝试过二次开发,放大和缩小,改变其基准点来解决BUG,功能实现了,但是效果体验不好。果断放弃该组件。
新的解决方案:使用【cropperjs】来做图片裁剪,该插件的缩放,采用的是图片拉升,更改图片的宽高来实现放大缩小,而非【scale】实现起来更加容易控制,封装代码插件如下:
安装:cropperjs
npm i cropperjs
组件:
index.vue 代码组件示例:
<template> <div> <button @click="handleCutting">裁剪</button> <cropperjs :imgFile="imgFile" :ratioWidth="ratioWidth" :ratioHeight="ratioHeight" @handleCancel="handleCancel" @handleUpload="handleUpload" @handleCropData="handleCropData" v-if="isCutting"> </cropperjs> </div> </template> <script> import cropperjs from './cropperjs' export default { name: 'test', components:{ cropperjs }, data () { return { isCutting: true,// 是否裁剪 imgFile: 'https://bpic.588ku.com/element_water_img/18/06/12/b2887846cb19ff36a5502401ac918809.jpg', ratioWidth: 4,// 裁剪比例:长 自由比例设置为: 0 即可 ratioHeight: 3,// 裁剪比例:宽 自由比例设置为: 0 即可 } }, mounted(){ this.init(); }, methods: { init(){}, // 裁剪 handleCutting(){ this.isCutting = true; }, // 取消 handleCancel(){ this.isCutting = false; }, // 上传 handleUpload(data){ console.log('上传'); console.log(data); }, // 裁剪 handleCropData(data){ console.log('裁剪数据'); console.log(data); } } } </script>
组件:cropperjs.vue 代码示例:
<template> <div> <div class="main-cropper"> <!-- 剪裁框 --> <div class="cropper"> <img class="img" ref="image" :src="imgFile" alt=""> </div> <!-- 底部 --> <div class="cropper-footer"> <div class="footer-handle"> <div class="item" @click="handleCancel">取消</div> <div class="item" @click="handleZoom(0.05)">放大</div> <div class="item" @click="handleZoom(-0.05)">缩小</div> <!-- <div class="item" @click="handleRotate(-90)">逆时针旋转</div> --> <div class="item" @click="handleRotate(90)">旋转</div> <div class="item" @click="handleConfirm">确定</div> </div> </div> </div> </div> </template> <script> import Cropper from 'cropperjs' import 'cropperjs/dist/cropper.css' export default { name: 'VueCropper', props: { imgFile: { type: String, default: 'https://bpic.588ku.com/element_water_img/18/06/12/b2887846cb19ff36a5502401ac918809.jpg' }, ratioWidth: { type: Number, default: 0 }, ratioHeight: { type: Number, default: 0 }, }, data() { return { myCropper: null, afterImg: '', ScaleX: 1, ScaleY: 1, fixed: false, fixedBox: false, inputRotate: 0, isDisabled: false } }, computed: {}, watch: { imgFile: function(file) { console.log(file); this.imgFile = file } }, mounted() { this.init(); }, methods: { // 初始化 init() { this.myCropper = new Cropper(this.$refs.image, { // responsive: true, // 在窗口大小变化后,重新渲染裁剪器 默认 true // restore: true, // 在窗口大小变化后,恢复被裁剪的区域 默认 true // checkCrossOrigin: true, // 检查图片是否跨域 // modal: true, // 是否在图片和裁剪框之间显示黑色蒙版 默认 true // guides: true, // 是否显示裁剪框的虚线 默认 true // center: true, // 是否显示裁剪框中心的指示器 默认 true // highlight: true, // 是否显示裁剪框上面的白色蒙版(突出显示裁剪框) 默认 true background: false, // 是否在容器内显示网格背景 默认 true // autoCrop: true, // 定义裁剪区域占图片的大小(百分比) 取值为 0-1 默认 0.8 // movable: true, // 是否可以移动图片 默认 true // rotatable :true, // 是否可以旋转图片 默认 true // scalable: true, // 是否可以缩放图片 默认 true // zoomable: true, // 是否可以缩放图片(以图片左上角为原点进行缩放)默认 true // zoomOnTouch: true, // 是否允许通过拖动来缩放图片 默认 true zoomOnWheel: true, // 是否允许通过鼠标滚轮缩放图片 默认 true // wheelZoomRatio: true, // 设置通过鼠标滚轮缩放图片时的缩放比例 默认 0.1 // cropBoxMovable: true, // 是否允许通过拖动来移动裁剪框 默认 true // cropBoxResizable: true, // 是否允许通过拖动来调整裁剪框的大小 默认 true // toggleDragModeOnDblclick: true, // 是否允许双击切换图片容器拖拽模式("crop"和"move") 默认 true // minContainerWidth: 200, // 设置裁剪容器的最小宽度 默认 200 // minContainerHeight: 100, // 设置裁剪容器的最小高度 默认 100 // minCanvasWidth: 0, // 设置图片容器的最小宽度 默认 0 // minCropBoxWidth: 0, // 设置裁剪框的最小宽度 默认 0 这个尺寸是相对于页面的,而不是图片 // minCropBoxHeight: 0, // 设置裁剪框的最小高度 默认 0 这个尺寸是相对于页面的,而不是图片 // preview: '.before', // 预览样式 如果需要预览 配置预览 class 即可 viewMode: 1, // 裁剪器配置 [0:没有限制 1:限制裁剪框不超过图片容器的范围 2:限制最片容器尺寸以在裁剪容器中展示。 如果图片容器和裁剪容器的比例不同,则图片容器以cover模式填充(图片容器保持原有比例,最长边和裁剪容器大小一致,短边等比缩放,可能会有部分区域不可见) 3:限制图片容器尺寸以在裁剪器中展示。 如果图片容器和裁剪容器的比例不同,则图片容器以contain模式填充(图片容器保持原有比例,最短边和裁剪容器大小一直,长边等比缩放,可能会有留白)] dragMode: 'move', // 定义裁剪器的拖动模式 [crop:创建一个新的裁剪框 move:图片容器可移动 none:什么也不做] 默认 crop // initialAspectRatio: 1, // 定义裁剪框的初始宽高比。默认和图片容器的宽高比相同 默认 NaN autoCropArea: 1, // 定义裁剪区域占图片的大小(百分比)。取值为 0 - 1 aspectRatio: this.ratioWidth / this.ratioHeight // 定义裁剪框的固定宽高比。默认是自由比例 NaN }) }, // 取消 handleCancel() { this.$emit('handleCancel') }, // 缩放 handleZoom(val) { this.myCropper.zoom(val) }, // 旋转 handleRotate(val) { this.myCropper.rotate(val) }, // 绝对角度旋转 handleRotateTo(val) { this.myCropper.rotateTo(val) }, // 裁剪 uploadImgs() { this.afterImg = this.myCropper.getCroppedCanvas({ imageSmoothingQuality: 'high' }).toDataURL('image/jpeg'); this.$emit('handleCropData', this.afterImg) }, // 确定 handleConfirm() { this.afterImg = this.myCropper.getCroppedCanvas({ imageSmoothingQuality: 'high' }).toDataURL('image/jpeg'); this.$emit('handleUpload', this.base64ToBlob(this.afterImg)) }, base64ToBlob(code) { const parts = code.split(';base64,'); const contentType = parts[0].split(':')[1]; const raw = window.atob(parts[1]); const rawLength = raw.length; const uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], { type: contentType }) }, // 重置 handleReset() { this.myCropper.reset(); this.ScaleX = 1; this.ScaleY = 1; }, // 移动 handleMove(val1, val2) { this.myCropper.move(val1, val2); }, // X轴翻转 handleCropperScaleX() { this.ScaleX = -this.ScaleX if (this.myCropper.getImageData().rotate === -90 || this.myCropper.getImageData().rotate === 90) { this.myCropper.scaleY(this.ScaleX) } else { this.myCropper.scaleX(this.ScaleX) } }, // y轴翻转 handleCropperScaleY() { this.ScaleY = -this.ScaleY if (this.myCropper.getImageData().rotate === -90 || this.myCropper.getImageData().rotate === 90) { this.myCropper.scaleX(this.ScaleY) } else { this.myCropper.scaleY(this.ScaleY) } } } } </script> <style> *{margin: 0; padding: 0;} .main-cropper{position: fixed; top: 0; right: 0; bottom: 0; left: 0;} .main-cropper .cropper{position: absolute; top: 0; right: 0; bottom: 54px; left: 0; overflow: hidden; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC);} .main-cropper .cropper .img{width: 90%; opacity: 0;} /**/ .main-cropper .cropper-footer{width: 100%; position: fixed; left: 0; bottom: 0;} .main-cropper .cropper-footer .footer-handle{padding: 10px 10px; display: flex; flex-direction:row; justify-content:space-between;} .main-cropper .cropper-footer .item{width: 60px; height: 34px; line-height: 34px; text-align: center; background: #28a745; color: #fff; cursor: pointer; border-radius: 4px; font-size: 15px;} .main-cropper .cropper-footer .item:first-child{background: #909399;} .main-cropper .cropper-footer .item:last-child{background: #409EFF;} </style>
打完收工!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
2017-08-28 问答项目---退出登陆简单示例!
2017-08-28 问答项目---登陆也要做验证!(JS和PHP验证)
2017-08-28 问答项目---用户注册的那些事儿(PHP验证)
2017-08-28 问答项目---用户注册的那些事儿(JS验证)
2017-08-28 问答项目---自定义标签调用栏目信息!
2017-08-28 问答项目---网站是否关闭/自动登陆信息如何做!
2017-08-28 问答项目---头部底部模板分离!