Vue+element-ui 图片上传剪裁组件
1,安装插件
npm install vue-cropper
yarn add vue-cropper
2,引入
使用 注意: 需要关掉本地的mock服务, 不然图片转化会报错
组件内使用
import { VueCropper } from \'vue-cropper\' components: { VueCropper, },
main.js里面使用
import VueCropper from \'vue-cropper\' Vue.use(VueCropper)
cdn方式使用
<script src="vuecropper.js"></script> Vue.use(window[\'vue-cropper\'])
nuxt 使用方式
if(process.browser) { vueCropper = require(\'vue-cropper\') Vue.use(vueCropper.default) }
3,使用
创建index.vue文件
<template><div>
<!-- 多图片上传 -->
<el-upload v-if="multiple" action=\'string\' list-type="picture-card" accept="image/*" :on-preview="handlePreview" :auto-upload="false" :on-remove="handleRemove" :http-request="upload" :on-change="consoleFL" :file-list="uploadList">
<i class="el-icon-plus"></i>
</el-upload>
<!-- 单图片上传 -->
<el-upload v-else class="avatar-uploader" action="\'string\'" :auto-upload="false" :show-file-list="false" :on-change="handleCrop" :http-request="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar" ref="singleImg" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:width+\'px\',height:height+\'px\'}">
<i v-else class="el-icon-plus avatar-uploader-icon" :style="{width:width+\'px\',height:height+\'px\',\'line-height\':height+\'px\',\'font-size\':height/6+\'px\'}"></i>
<!-- 单图片上传状态显示 -->
<!-- <div v-if="imageUrl" class="reupload" ref="reupload" @click.stop="handlePreviewSingle" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:reuploadWidth+\'px\',height:reuploadWidth+\'px\',\'line-height\':reuploadWidth+\'px\',\'font-size\':reuploadWidth/5+\'px\'}">重新上传</div> -->
<div id="uploadIcon" v-if="imageUrl" ref="reupload" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:\'100%\'}">
<i class="el-icon-zoom-in" @click.stop="handlePreviewSingle" :style="{color:\'#2E2E2E\',fontSize:\'25px\',display:\'inline-block\',paddingRight:\'15px\'}"></i>
<i class="el-icon-upload" :style="{color:\'#2E2E2E\',fontSize:\'25px\',display:\'inline-block\'}"></i>
</div>
<div class="reupload" ref="uploading" :style="{width:reuploadWidth+\'px\',height:reuploadWidth+\'px\',\'line-height\':reuploadWidth+\'px\',\'font-size\':reuploadWidth/5+\'px\'}">上传中..</div>
<div class="reupload" ref="failUpload" :style="{width:reuploadWidth+\'px\',height:reuploadWidth+\'px\',\'line-height\':reuploadWidth+\'px\',\'font-size\':reuploadWidth/5+\'px\'}">上传失败</div>
</el-upload>
<!-- 多图片预览弹窗 -->
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
<!-- 剪裁组件弹窗 -->
<el-dialog :visible.sync="cropperModel" width="1100px" :before-close="beforeClose">
<Cropper :img-file="file" ref="vueCropper" :fixedNumber="fixedNumber" @upload="upload">
</Cropper>
</el-dialog>
</div>
</template>
<script>
import Cropper from \'./cropper\';
// import axios from \'@/assets/js/axios\'
export default {
name: \'uploader\',
props: {
targetUrl: {
// 上传地址
type: String,
// default: \'/storage/upload\'
default: `${process.env.API_ROOT}/sys/oss/upload`
},
multiple: {
// 多图开关
type: Boolean,
default: false
},
initUrl: {
// 初始图片链接
default: \'\'
},
fixedNumber: {
// 剪裁框比例设置
default: function () {
return [1.5, 1];
}
},
width: {
// 单图剪裁框宽度
type: Number,
default: 178
},
height: {
// 单图剪裁框高度
type: Number,
default: 178
}
},
data () {
return {
file: \'\', // 当前被选择的图片文件
imageUrl: \'\', // 单图情况框内图片链接
dialogImageUrl: \'\', // 多图情况弹窗内图片链接
uploadList: [], // 上传图片列表
reupload: true, // 控制"重新上传"开关
dialogVisible: false, // 展示弹窗开关
cropperModel: false, // 剪裁组件弹窗开关
reuploadWidth: this.height * 0.7, // 动态改变”重新上传“大小
};
},
updated () {
if (this.$refs.vueCropper) {
this.$refs.vueCropper.Update();
}
},
watch: {
initUrl: function (val) {
// 监听传入初始化图片
// console.info(\'watch\');
if (val) {
if (typeof this.initUrl === \'string\') {
this.imageUrl = val;
} else {
this.uploadList = this.formatImgArr(val);
// this.$emit(\'imgupload\', this.uploadList);
}
}
}
},
mounted () {
if (typeof this.initUrl === \'string\') {
this.imageUrl = this.initUrl;
} else {
this.uploadList = this.formatImgArr(this.initUrl);
}
},
methods: {
/** **************************** multiple多图情况 **************************************/
handlePreview (file) {
// 点击进行图片展示
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
handleRemove (file, fileList) {
// 删除图片后更新图片文件列表并通知父级变化
this.uploadList = fileList;
this.$emit(\'imgupload\', this.uploadList);
// this.$emit(\'imgupload\', this.formatImgArr(this.uploadList));
},
consoleFL (file, fileList) {
// 弹出剪裁框,将当前文件设置为文件
this.cropperModel = true;
this.file = file;
// this.uploadList = fileList;
},
/************************************************************************************/
/** **************************** single单图情况 **************************************/
handlePreviewSingle (file) { // 点击进行图片展示
this.dialogImageUrl = this.file.url;
this.dialogVisible = true;
},
mouseEnter () { // 鼠标划入显示“重新上传”
this.$refs.reupload.style.display = \'block\';
if (this.$refs.failUpload.style.display === \'block\') {
this.$refs.failUpload.style.display = \'none\';
}
this.$refs.singleImg.style.opacity = \'0.6\';
},
mouseLeave () {
// 鼠标划出隐藏“重新上传”
this.$refs.reupload.style.display = \'none\';
this.$refs.singleImg.style.opacity = \'1\';
},
handleCrop (file, files) {
// console.log(file);
// 点击弹出剪裁框
this.cropperModel = true;
this.file = file;
// this.imageUrl = file.url
},
/************************************************************************************/
async upload (data) {
// 自定义upload事件
if (!this.multiple) {
// 如果单图,则显示正在上传
this.$refs.uploading.style.display = \'block\';
}
let img = new Image();
img.src = data;
img.onload = async () => {
// let _data = this.compress(img);
let blob = this.dataURItoBlob(data);
let formData = new FormData();
formData.append(\'file\', blob, this.file.name); // 有的后台需要传文件名,不然会报错
this.imgUpload(formData);
};
},
async imgUpload(formData) {
const res = await this.$http({
url: \'sys/oss/upload\',
method: \'post\',
data: formData,
headers: {
\'Content-Type\': \'multipart/form-data\'
}
});
if (!this.multiple) {
// 上传完成后隐藏正在上传
this.$refs.uploading.style.display = \'none\';
}
if (res.data.code === 0) {
// 上传成功将照片传回父组件
const currentPic = res.data.url;
if (this.multiple) {
this.uploadList.push({
url: currentPic,
uid: \'111\'
});
this.$emit(\'imgupload\', this.uploadList);// 根据自己实际项目需要将照片返回给父组件
// this.uploadList.pop();
// this.$emit(\'imgupload\', this.formatImgArr(this.uploadList));
} else {
this.$emit(\'imgupload\', currentPic);
}
this.$refs.vueCropper.isDisabled = false;
} else {
// 上传失败则显示上传失败,如多图则从图片列表删除图片
if (!this.multiple) {
this.$refs.failUpload.style.display = \'block\';
} else {
this.uploadList.pop();
}
this.$refs.vueCropper.isDisabled = false;
}
this.cropperModel = false;
},
formatImgArr (arr) {
const result = arr.map((item, index) => {
if (typeof item === \'string\') {
return {
url: item,
uid: `index${index}`
};
} else {
return item.url;
}
});
return result;
},
beforeClose () {
// this.uploadList.pop();
console.log(this.uploadList);
this.cropperModel = false;
},
// 压缩图片
compress(img) {
let canvas = document.createElement(\'canvas\');
let ctx = canvas.getContext(\'2d\');
// let initSize = img.src.length;
let width = img.width;
let height = img.height;
canvas.width = width;
canvas.height = height;
// 铺底色
ctx.fillStyle = \'#fff\';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
// 进行压缩
let ndata = canvas.toDataURL(\'image/jpeg\', 0.8);
return ndata;
},
// base64转成bolb对象
dataURItoBlob(base64Data) {
let byteString;
if (base64Data.split(\',\')[0].indexOf(\'base64\') >= 0) { byteString = atob(base64Data.split(\',\')[1]); } else { byteString = unescape(base64Data.split(\',\')[1]); }
let mimeString = base64Data.split(\',\')[0].split(\':\')[1].split(\';\')[0];
let ia = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], { type: mimeString });
}
},
components: {
Cropper
}
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
color: #8c939d;
text-align: center;
}
.avatar {
display: block;
}
.reupload {
border-radius: 50%;
position: absolute;
color: #fff;
background-color: #000000;
opacity: 0.6;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
}
#uploadIcon{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
}
</style>
4,创建cropper.vue文件
<template><div>
<div class="cropper-content">
<!-- 剪裁框 -->
<div class="cropper">
<vueCropper ref="cropper" :img="option.img" :outputSize="option.size" :outputType="option.outputType" :info="true" :full="option.full" :canMove="option.canMove" :canMoveBox="option.canMoveBox" :original="option.original" :autoCrop="option.autoCrop" :autoCropWidth="option.autoCropWidth" :autoCropHeight="option.autoCropHeight" :fixedBox="option.fixedBox" @realTime="realTime" :fixed="option.fixed" :fixedNumber="fixedNumber"></vueCropper>
<!-- <vueCropper ref="cropper" :img="option.img" :outputSize="option.size" :outputType="option.outputType"></vueCropper> -->
</div>
<!-- 预览框 -->
<div class="show-preview" :style="{\'width\': \'500px\', \'height\': \'400px\', \'overflow\': \'hidden\', \'margin\': \'0 25px\', \'display\':\'flex\', \'align-items\' : \'center\'}">
<div :style="previews.div" class="preview">
<img :src="previews.url" :style="previews.img">
</div>
</div>
</div>
<div class="footer-btn">
<!-- 缩放旋转按钮 -->
<div class="scope-btn">
<el-button type="primary" icon="el-icon-zoom-in" @click="changeScale(1)"></el-button>
<el-button type="primary" icon="el-icon-zoom-out" @click="changeScale(-1)"></el-button>
<el-button type="primary" @click="rotateLeft">逆时针旋转</el-button>
<el-button type="primary" @click="rotateRight">顺时针旋转</el-button>
</div>
<!-- 确认上传按钮 -->
<div class="upload-btn">
<!-- <el-button type="primary" @click="uploadImg(\'blob\')">上传</el-button> -->
<el-button type="primary" :disabled="isDisabled" @click="uploadImg(\'base64\')">上传</el-button>
</div>
</div>
</div>
</template>
<script>
import { VueCropper } from \'vue-cropper\';
// console.log(VueCropper);
export default {
data () {
return {
previews: {}, // 预览数据
option: {
img: \'\', // 裁剪图片的地址 (默认:空)
outputSize: 1, // 裁剪生成图片的质量 (默认:1)
full: false, // 是否输出原图比例的截图 选true生成的图片会非常大 (默认:false)
outputType: \'png\', // 裁剪生成图片的格式 (默认:jpg)
canMove: true, // 上传图片是否可以移动 (默认:true)
original: false, // 上传图片按照原始比例渲染 (默认:false)
canMoveBox: true, // 截图框能否拖动 (默认:true)
autoCrop: true, // 是否默认生成截图框 (默认:false)
autoCropWidth: 480, // 默认生成截图框宽度 (默认:80%)
autoCropHeight: 320, // 默认生成截图框高度 (默认:80%)
fixedBox: false, // 固定截图框大小 不允许改变 (默认:false)
fixed: true, // 是否开启截图框宽高固定比例 (默认:true)
fixedNumber: [1.5, 1], // 截图框比例 (默认:[1:1])
enlarge: 1
},
isDisabled: false,
downImg: \'#\'
};
},
props: [\'imgFile\', \'fixedNumber\'],
methods: {
changeScale (num) {
// 图片缩放
num = num || 1;
this.$refs.cropper.changeScale(num);
},
rotateLeft () {
// 向左旋转
this.$refs.cropper.rotateLeft();
},
rotateRight () {
// 向右旋转
this.$refs.cropper.rotateRight();
},
Update () {
// this.file = this.imgFile
this.option.img = this.imgFile.url;
},
realTime (data) {
// 实时预览
this.previews = data;
},
uploadImg (type) {
// 将剪裁好的图片回传给父组件
event.preventDefault();
this.isDisabled = true;
let that = this;
if (type === \'blob\') {
this.$refs.cropper.getCropBlob(data => {
that.$emit(\'upload\', data);
});
} else {
this.$refs.cropper.getCropData(data => {
that.$emit(\'upload\', data);
});
}
}
},
components: { VueCropper }
};
</script>
<style>
.cropper-content {
display: flex;
display: -webkit-flex;
justify-content: flex-end;
-webkit-justify-content: flex-end;
}
.cropper-content .cropper {
width: 500px;
height: 400px;
}
.cropper-content .show-preview {
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
-webkit-justify-content: center;
overflow: hidden;
border: 1px solid #cccccc;
background: #cccccc;
margin-left: 40px;
}
.preview {
overflow: hidden;
border: 1px solid #cccccc;
background: #cccccc;
}
.footer-btn {
margin-top: 30px;
display: flex;
display: -webkit-flex;
justify-content: flex-end;
-webkit-justify-content: flex-end;
}
.footer-btn .scope-btn {
width: 250px;
display: flex;
display: -webkit-flex;
justify-content: space-between;
-webkit-justify-content: space-between;
}
.footer-btn .upload-btn {
flex: 1;
-webkit-flex: 1;
display: flex;
display: -webkit-flex;
justify-content: center;
-webkit-justify-content: center;
}
.footer-btn .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;
margin: 0;
-webkit-transition: 0.1s;
transition: 0.1s;
font-weight: 500;
padding: 8px 15px;
font-size: 12px;
border-radius: 3px;
color: #fff;
background-color: #67c23a;
border-color: #67c23a;
}
</style>
参考:https://www.jianshu.com/p/9b4de1c5b9c0
以上是 Vue+element-ui 图片上传剪裁组件 的全部内容, 来源链接: utcz.com/z/375918.html