前言:
网上关于vue-cropper的使用有很多篇,但是能详细介绍并且把隐藏的问题总结的却很少(就很烦),正好题主最近用了这个插件,在此做个总结
需求:(这里题主的需求也是经过几次变更,逐步完善了vue-cropper组件的使用,下列序号表示需求的变更 )
- 对头像上传功能加入图片裁剪功能(这里只需能裁剪即可,无其他要求)
- 另一个功能点也需要加入图片裁剪功能,且要求裁剪后的图片固定宽高为108像素
- 对头像上传时裁剪后的图片加入限制,宽高不超过300像素
问题:
如果只是需要能裁剪图片,那根据其他大部分vue-cropper篇章即可实现;
这里说一下题主在这些需求下出现的问题及解决方法
需求1:正常引用vue-cropper即可实现裁剪功能;(这里把vue-cropper封装为组件,为方便读写,也预防后续其他地方引用)
需求2:这里加入了裁剪图片固定宽高的限制(截图框固定大小108)
问题1:如果不注意vue-cropper参数的使用,在full: false时,在浏览器页面100%时裁剪图片不出错,当不是100%时,裁剪后的图片宽高会随浏览器页面大小的变化而变化
如:页面缩放比例为90%,截图框显示300,最终截图为270;页面缩放比例为110%,截图框显示300,最终截图为330
解决:设置full: true
问题2:加入了背景图可滚动后,在full: true时,滚动背景图也会影响裁剪图片的大小
解决:这里找了很久但还是找到了解决方法,影响裁剪图片大小的问题解决不了,但是可以解决保存图片大小的问题,即使裁剪的图片大小被影响了,但是在保存图片时可以重置图片大小,保存成需要的图片大小
需求3:这里又加入了新限制,没有遇到新问题,只是对组件做了优化提升
这里题主直接上题主最终版的组件了
1.使用vue-cropper之前,先引入:
npm install vue-cropper
2.创建CropperImage组件,main.js中引入
import VueCropper from 'vue-cropper'
import CropperImage from '@/components/CropperImage.vue'
Vue.component('CropperImage', CropperImage)
3.CropperImage中定义
3.1 template (这里加入了放大、缩小、重置大小三个功能按钮)
<template>
<el-dialog
title="裁切封面"
:visible.sync="show"
width="1000px"
:before-close="handleClose"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="cropper-content">
<div class="cropper-box">
<div class="cropper">
<vue-cropper
ref="cropper"
:img="option.img"
:output-size="option.outputSize"
:info="option.info"
:can-scale="option.canScale"
:auto-crop="option.autoCrop"
:auto-crop-width="option.autoCropWidth"
:auto-crop-height="option.autoCropHeight"
:fixed="option.fixed"
:fixed-number="option.fixedNumber"
:full="option.full"
:fixed-box="option.fixedBox"
:can-move="option.canMove"
:can-move-box="option.canMoveBox"
:original="option.original"
:center-box="option.centerBox"
:height="option.height"
:info-true="option.infoTrue"
:max-img-size="option.maxImgSize"
:enlarge="option.enlarge"
:mode="option.mode"
:limit-min-size="option.limitMinSize"
@realTime="realTime"
/>
</div>
</div>
<!--预览效果图-->
<div class="show-preview">
<div :style="previews.div" class="preview">
<img ref="previews" :src="previews.url" :style="previews.img">
</div>
</div>
</div>
<div class="mt-20">
<el-button icon="el-icon-plus" @click="scaleBigger">放大</el-button>
<el-button icon="el-icon-minus" @click="scaleSmaller">缩小</el-button>
<el-button icon="el-icon-refresh-left" @click="reload">重置大小</el-button>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose">取 消</el-button>
<el-button type="primary" @click="onSubmit">确 定</el-button>
</span>
</el-dialog>
</template>
3.2 script
import { VueCropper } from 'vue-cropper'
export default {
name: 'CropperImage',
components: {
VueCropper
},
data() {
return {
show: false,
previews: {},
option: {
img: '', // 裁剪图片的地址
outputSize: 1, // 裁剪生成图片的质量(可选0.1 - 1)
outputType: 'jpeg', // 裁剪生成图片的格式(jpeg || png || webp)
info: false, // 图片大小信息
canScale: true, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 300, // 默认生成截图框宽度
autoCropHeight: 300, // 默认生成截图框高度
fixed: true, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
full: false, // false按原比例裁切图片,不失真
fixedBox: false, // 固定截图框大小,不允许改变
canMove: true, // 上传图片是否可以移动
canMoveBox: true, // 截图框能否拖动
original: true, // 上传图片按照原始比例渲染
centerBox: true, // 截图框是否被限制在图片里面
height: true, // 是否按照设备的dpr 输出等比例图片
infoTrue: false, // true为展示真实输出图片宽高,false展示看到的截图框宽高
maxImgSize: 3000, // 限制图片最大宽度和高度
enlarge: 1, // 图片根据截图框输出比例倍数
mode: '550px 400px', // 图片默认渲染方式
limitMinSize: [108, 108], // 裁剪框限制最小区域
minCropBoxWidth: 108, // 设置最小裁切框宽度
minCropBoxHeight: 108 // 设置最小裁切框高度
},
file: null,
form: {}
}
},
methods: {
// 展示裁剪弹窗
handleOpen(val, obj, form) {
this.option = { ...this.option, ...obj }
this.file = val
this.form = form
this.show = true
this.option.img = URL.createObjectURL(val.raw)
const reader = new FileReader()
reader.readAsDataURL(val.raw)
},
// 关闭弹窗
handleClose() {
this.show = false
},
// 实时预览函数
realTime(data) {
this.previews = data
},
// 缩放图片
resizeBlob(blob, desiredWidth, desiredHeight) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 计算缩放比例
const scaleX = desiredWidth / img.width
const scaleY = desiredHeight / img.height
const scale = Math.min(scaleX, scaleY)
// 设置 Canvas 的宽度和高度
canvas.width = desiredWidth
canvas.height = desiredHeight
// 绘制图片到 Canvas 上,并进行缩放
ctx.drawImage(img, 0, 0, img.width * scale, img.height * scale)
// 将 Canvas 中的图像转换为 Blob 对象
canvas.toBlob((resizedBlob) => {
resolve(resizedBlob)
}, blob.type)
}
img.onerror = (error) => {
reject(error)
}
img.src = URL.createObjectURL(blob)
})
},
// 确定
onSubmit() {
// 获取截图的base64
this.$refs.cropper.getCropBlob(async(data) => {
const originalBlob = data // 原始 Blob 对象
const desiredWidth = this.form.sizeWidth // 所需的宽度
const desiredHeight = this.form.sizeHeight // 所需的高度
if (this.form.type == 'fixed') {
// 如果是需要固定宽高的图片,直接调用方法缩放图片
this.handleResizeBlob(originalBlob, desiredWidth, desiredHeight)
} else if (this.form.type == 'max') {
// 如果是需要不超过xxx宽高的图片,先做判断
var blob = new Blob([data], { type: 'image/jpeg' })
var img = new Image()
var url = URL.createObjectURL(blob)
img.src = url
const _this = this
img.onload = function() {
// 获取图像的宽度和高度
var width = img.width
var height = img.height
if (width <= desiredWidth && height <= desiredHeight) {
// 如果裁剪完未超过限制,则直接去上传
var result = new File([data], `头像${(new Date()).getTime()}.jpeg`, { type: 'image/jpeg', lastModified: Date.now() })
_this.handleClose()
_this.$emit('handleUploadSuccess', result)
} else {
// 如果裁剪完超过限制,则调用方法缩放图片
_this.handleResizeBlob(originalBlob, desiredWidth, desiredHeight)
}
// 清理 URL 对象
URL.revokeObjectURL(url)
}
}
})
},
handleResizeBlob(originalBlob, desiredWidth, desiredHeight) {
this.resizeBlob(originalBlob, desiredWidth, desiredHeight)
.then((resizedBlob) => {
// 在此处使用缩放后的 Blob 对象
var result = new File([resizedBlob], `头像${(new Date()).getTime()}.jpeg`, { type: 'image/jpeg', lastModified: Date.now() })
this.handleClose()
this.$emit('handleUploadSuccess', result)
})
.catch((error) => {
console.error('Error resizing Blob:', error)
})
},
// 放大
scaleBigger() {
this.$refs.cropper.changeScale(1)
},
// 缩小
scaleSmaller() {
this.$refs.cropper.changeScale(-1)
},
// 重置大小
reload() {
this.$refs.cropper.reload()
}
}
}
3.3 style
.cropper-content{
display: flex;
display: -webkit-flex;
justify-content: flex-end;
.cropper-box{
width: 550px;
.cropper{
width: auto;
height: 400px;
}
}
.show-preview{
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
.preview{
overflow: hidden;
border:1px solid #67c23a;
background: #cccccc;
}
}
}
.footer-btn{
margin-top: 30px;
display: flex;
display: -webkit-flex;
justify-content: flex-end;
.scope-btn{
display: flex;
display: -webkit-flex;
justify-content: space-between;
padding-right: 10px;
}
.upload-btn{
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
}
.btn {
outline: none;
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 8px 15px;
font-size: 12px;
border-radius: 3px;
color: #fff;
background-color: #409EFF;
border-color: #409EFF;
margin-right: 10px;
}
}
4 调用CropperImage
/**
* 组件调用传参
* {
* file, // 文件对象
* { autoCropWidth: 108, autoCropHeight: 108 }, // 需要改变的组件option的参数值
* { type: 'fixed', sizeWidth: 108, sizeHeight: 108 } // type:fixed/max 固定大小/最大值,传值图片限定的宽高
* }
*/
<template>
<CropperImage ref="cropperImage" @handleUploadSuccess="handleUploadSuccess" />
</template>
<script>
getFile(file) {
this.$refs.cropperImage.handleOpen(file, { autoCropWidth: 108, autoCropHeight: 108 }, { type: 'fixed', sizeWidth: 108, sizeHeight: 108 })
},
handleUploadSuccess(val) {
// val: 截取后的图片file对象,可直接用于上传
......
}
</script>
这一个组件也是出了好几次问题,不过最终都解决了,大家如有觉得书写不规范,或有其他解决方法、问题,可留言评论讨论。
哈哈^_^~~