element-ui + vue-cropper 图片裁剪后上传
本文最后更新于:2022年7月6日 上午
兵来将挡,水来土掩
前提
- vue-cropper
- element-ui upload
- 整体分为两个组件来做
cropper.vue
主要负责图片裁剪,将裁剪之后的数据传递出来crop-upload.vue
,引入cropper.vue
作为子组件,将裁剪后的数据上传、显示上传列表等功能
cropper.vue
这一步没什么难度,单独抽出来一个组件只是为了项目结构能清晰一点。
按照文档中的 demo 来设置即可,其中部分属性项目 demo 中没有提及,直接去看源码即可。
要注意的是,组件的外部一定要设置固定的宽高
<!--crop-box 设置固定宽高-->
<div class="crop-box">
<vue-cropper
:autoCrop="cropperOption.autoCrop"
:autoCropHeight="cropperOption.autoCropHeight"
:autoCropWidth="cropperOption.autoCropWidth"
:canMove="cropperOption.canMove"
:canMoveBox="cropperOption.canMoveBox"
:canScale="cropperOption.canScale"
:centerBox="cropperOption.centerBox"
:enlarge="cropperOption.enlarge"
:fixedBox="cropperOption.fixedBox"
:fixed="cropperOption.fixed"
:full="cropperOption.full"
:high="cropperOption.high"
:img="cropperOption.img"
:info="true"
:infoTrue="cropperOption.infoTrue"
:limitMinSize="cropperOption.limitMinSize"
:maxImgSize="cropperOption.maxImgSize"
:mode="cropperOption.mode"
:original="cropperOption.original"
:outputSize="cropperOption.size"
:outputType="cropperOption.outputType"
@cropMoving="onCropMoving"
@imgLoad="onImgLoad"
@realTime="onRealTime"
ref="cropper"
></vue-cropper>
</div>
组件中各个属性的含义如下:
cropperOption: {
img: '', // 需要裁剪的图片
size: 1, // 输出图片压缩比, 默认 1
full: false, // 是否输出原图比例的截图
infoTrue: false, // 截图信息展示是否时真实的输出宽高
outputType: 'png', // 输出的图片格式
canScale: false, // 是否开启滚轮缩放大小
canMove: false, // 能否拖动图片
canMoveBox: true, // 能否拖动截图框
fixed: true, // 项目要求方形裁剪,因此固定宽高比,默认宽高比是 1:1
fixedBox: false, // 截图框固定大小
original: false, // 上传图片时,是否显示原始宽高
autoCrop: true, // 是否自动生成截图框
// 只有自动截图开启 宽度高度才生效
autoCropWidth: 200,
autoCropHeight: 200,
centerBox: true, // 截图框是否限制在图片里(只有在自动生成截图框时才生效)
high: false, // 是否根据 dpr 生成适合屏幕的高清图片
cropData: {},
enlarge: 1, // 按照截图框比例输出,默认 1
mode: 'contain', // 图片的默认渲染方式
maxImgSize: 2000, // 上传图片时图片最大大小(默认会压缩尺寸到这个大小)
limitMinSize: [200, 200] // 截图框最小限制
}
最终确定裁剪时,调用下面的方法获取裁剪后的数据
// 获取 blob
this.$refs.cropper.getCropBlob((blob) => {
console.log('crop onConfirm -> blob', blob);
});
// 获取 base64
this.$refs.cropper.getCropData((blob) => {
console.log('crop onConfirm -> blob', blob);
});
cropper.vue
最终代码
<!--cropper.vue-->
<template>
<div>
<el-dialog :visible.sync="visible" title="图片裁剪" width="800px">
<div class="crop-container" v-show="!showPreview">
<div class="crop-box">
<vue-cropper
:autoCrop="cropperOption.autoCrop"
:autoCropHeight="cropperOption.autoCropHeight"
:autoCropWidth="cropperOption.autoCropWidth"
:canMove="cropperOption.canMove"
:canMoveBox="cropperOption.canMoveBox"
:canScale="cropperOption.canScale"
:centerBox="cropperOption.centerBox"
:enlarge="cropperOption.enlarge"
:fixedBox="cropperOption.fixedBox"
:fixed="cropperOption.fixed"
:full="cropperOption.full"
:high="cropperOption.high"
:img="cropperOption.img"
:info="true"
:infoTrue="cropperOption.infoTrue"
:limitMinSize="cropperOption.limitMinSize"
:maxImgSize="cropperOption.maxImgSize"
:mode="cropperOption.mode"
:original="cropperOption.original"
:outputSize="cropperOption.size"
:outputType="cropperOption.outputType"
@cropMoving="onCropMoving"
@imgLoad="onImgLoad"
@realTime="onRealTime"
ref="cropper"
></vue-cropper>
</div>
<div class="crop-action">
<el-button @click="onConfirm" type="primary">确 定</el-button>
<el-button @click="showPreview = true">预 览</el-button>
<el-button @click="onCancel">取 消</el-button>
</div>
</div>
<div class="crop-container" v-show="showPreview">
<div :style="previews.div" class="crop-preview">
<img :src="previews.url" :style="previews.img" alt />
</div>
<div class="crop-action">
<el-button @click="onConfirm" type="primary">确 定</el-button>
<el-button @click="showPreview = false">取消预览</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper';
export default {
name: 'cropper-upload',
data() {
return {
visible: false,
previews: {},
showPreview: false,
crap: false,
cropperOption: {
img: '',
size: 1, // 输出图片压缩比, 默认 1
full: false, // 是否输出原图比例的截图
infoTrue: false, // 截图信息展示是否时真实的输出宽高
outputType: 'png',
canScale: false, // 是否开启滚轮缩放大小
canMove: false, // 能否拖动图片
canMoveBox: true, // 能否拖动截图框
fixed: true, // 固定宽高比
fixedBox: false, // 截图框固定大小
original: false, // 上传图片时,是否显示原始宽高
autoCrop: true, // 是否自动生成截图框
// 只有自动截图开启 宽度高度才生效
autoCropWidth: 200,
autoCropHeight: 200,
centerBox: true, // 截图框是否限制在图片里(只有在自动生成截图框时才生效)
high: false, // 是否根据 dpr 生成适合屏幕的高清图片
cropData: {},
enlarge: 1, // 按照截图框比例输出,默认 1
mode: 'contain', // 图片的默认渲染方式
maxImgSize: 2000, // 上传图片时图片最大大小(默认会压缩尺寸到这个大小)
limitMinSize: [200, 200] // 截图框最小限制
}
};
},
computed: {
cropper() {
return this.$refs.cropper;
},
},
methods: {
show(file) {
this.cropperOption.img = file.url;
this.$nextTick(() => {
this.visible = true;
});
},
hide() {
this.visible = false;
},
// 实时预览函数
// data 中保存了需要预览的样式及 url,直接用就行了
onRealTime(data) {
// console.log('onRealTime -> data', data);
this.previews = data;
},
onImgLoad(msg) {
console.log('onImgLoad -> msg', msg);
// 图片加载完成后,获取图片的真实宽高
// 以最小的那个值作为裁剪框默认大小
const { trueWidth, trueHeight } = this.cropper;
const width = Math.min(trueWidth, trueHeight);
this.cropperOption.autoCropWidth = width;
this.cropperOption.autoCropHeight = width;
},
onCropMoving(data) {
this.cropperOption.cropData = data;
},
onConfirm() {
// 获取裁剪后的 blob 数据,传递到外部
this.$refs.cropper.getCropBlob(blob => {
console.log('crop onConfirm -> blob', blob);
this.hide();
this.$emit('on-finish', blob);
});
},
onCancel() {
this.hide();
this.$emit('on-cancel');
}
},
components: {
VueCropper
}
};
</script>
<style lang="stylus" scoped>
.crop-container {
display: flex;
align-items: center;
}
.crop-box {
margin: 0 auto;
width: 700px;
height: 600px;
}
.crop-preview {
margin: auto;
border: 1px dotted #e4e4e4;
overflow: hidden;
}
.crop-action {
width: 100px;
display: flex;
flex-direction: column;
align-items: center;
}
.el-button {
width: 98px;
margin-bottom: 20px;
margin-left: 10px;
}
</style>
crop-upload.vue
思路
- 因为需要先裁剪再上传,因此需要关闭自动上传设置:
auto-upload
设置为 false - 之后,利用上一步裁剪后的数据进行
FormData
上传即可 - 还有就是,没有用 el-upload 自带的
file-list
,主要是为了能获取到当前裁剪的是哪个图片,方便进行对应的更新
最终代码
<template>
<div class="crop-upload">
<div class="crop-upload-list">
<ul class="el-upload-list el-upload-list--picture-card">
<li
:key="item.url"
:tabindex="index"
class="el-upload-list__item is-success"
v-for="(item, index) in fileList"
>
<img :src="item.url" alt class="el-upload-list__item-thumbnail" />
<a class="el-upload-list__item-name">
<i class="el-icon-document"></i>
</a>
<label class="el-upload-list__item-status-label">
<i class="el-icon-upload-success el-icon-check"></i>
</label>
<i class="el-icon-close"></i>
<i class="el-icon-close-tip">按 delete 键可删除</i>
<!---->
<span class="el-upload-list__item-actions">
<span @click="onEdit(index)" class="el-upload-list__item-preview">
<i class="el-icon-edit"></i>
</span>
<span @click="onPreview(item)" class="el-upload-list__item-preview">
<i class="el-icon-zoom-in"></i>
</span>
<span @click="onRemove(index)" class="el-upload-list__item-delete">
<i class="el-icon-delete"></i>
</span>
</span>
</li>
</ul>
<el-upload
:action="uploadUrl"
:auto-upload="false"
:data="uploadData"
:headers="headers"
:limit="limit"
:on-change="onChange"
:on-exceed="onExceed"
:show-file-list="false"
list-type="picture-card"
ref="upload"
>
<i class="el-icon-plus"></i>
</el-upload>
</div>
<cropper
:visible.sync="cropperVisible"
@on-cancel="onCropCancel"
@on-finish="onCropFinish"
ref="cropper"
></cropper>
<el-dialog :visible.sync="dialogVisible">
<img :src="dialogImageUrl" alt width="100%" />
</el-dialog>
</div>
</template>
<script>
import Cropper from './cropper.vue';
export default {
props: {
limit: {
type: Number,
default: 10
},
fileList: {
type: Array,
default: () => []
}
},
data() {
return {
headers: {
// your headers
},
uploadUrl: 'upload url',
uploadData: {},
dialogVisible: false,
dialogImageUrl: '',
cropperVisible: false,
selectedCropIndex: -1
};
},
computed: {
upload() {
return this.$refs.upload;
},
cropper() {
return this.$refs.cropper;
}
},
created() {},
methods: {
// 删除
onRemove(index) {
const newFileList = this.fileList;
newFileList.splice(index, 1);
this.$emit('update:fileList', newFileList);
},
// 预览
onPreview(item) {
this.dialogImageUrl = item.url;
this.dialogVisible = true;
},
// 弹出裁剪组件
onEdit(index) {
this.selectedCropIndex = index;
const file = this.fileList[index];
this.cropper.show(file);
},
onExceed() {
this.$message({
type: 'error',
message: '超出上传数量限制,无法继续上传'
});
},
// 文件状态改变时的钩子,添加文件,弹出裁剪组件
onChange(file, fileList) {
this.cropper.show(file);
},
// 监听裁剪结束,进行上传
onCropFinish(data) {
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.doUpload(data)
.then(url => {
// 更新对应 index 的 url
const newFileList = this.fileList;
const item = { url, };
if (this.selectedCropIndex === -1) {
newFileList.push(item);
} else {
newFileList[this.selectedCropIndex] = item;
}
this.$emit('update:fileList', newFileList);
})
.finally(() => {
this.reset();
loading.close();
});
},
// 取消裁剪
onCropCancel() {
this.reset();
},
doUpload(data) {
// your custom upload
},
reset() {
this.selectedCropIndex = -1;
}
},
components: {
Cropper
}
};
</script>
<style lang="stylus" scoped>
.crop-upload-list {
display: flex;
}
</style>
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!