Vue+element-ui 图片上传剪裁组件

vue

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

回到顶部