效果图
npm install vue-cropper
- 新建一个图片剪裁的组件 img-cropper
<template>
<div class="vue-cropper-box">
<div class="vue-cropper-operate">
<button class="basicButton" @click="cropperChangeScale(1)">放大</button>
<button class="basicButton" @click="cropperChangeScale(-1)">缩小</button>
<button class="basicButton" @click="cropperRotateLeft">左旋转</button>
<button class="basicButton" @click="cropperRotateRight">右旋转</button>
<button class="basicButton" @click="cropperDown('blob')">下载</button>
<button class="basicButton" @click="cropperFinish('Blob')">确定裁剪</button>
</div>
<div class="vue-cropper-content" style="width:100%;height:310px">
<VueCropper ref="cropper" :img="img" :outputSize="cropperOption.outputSize" :outputType="outputType" :info="cropperOption.info" :full="cropperOption.full" :canMove="cropperOption.canMoveBox" :canMoveBox="cropperOption.canMoveBox" :original="cropperOption.original" :canScale="cropperOption.canScale" :autoCrop="true" :fixedBox="true" :fixed="cropperOption.fixed" :fixedNumber="fixedNumber" :centerBox="cropperOption.centerBox" :infoTrue="cropperOption.infoTrue" @realTime="realTime" @imgLoad="imgLoad"></VueCropper>
</div>
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper'
export default {
name: 'img-cropper',
components: { VueCropper },
// inject: ['reload'],
filters: {},
props: {
file: {
default: () => { },
required: true
},
img: {
default: "",
required: true
},
fixedNumber: { //截图框的宽高比例
type: Array,
required: true
},
outputType: { //截图框的宽高比例
type: String,
required: true
},
cropperOption: {
default: () => ({
// img: '', // 裁剪图片的地址
info: true, // 裁剪框的大小信息
outputSize: 0.8, // 裁剪生成图片的质量
// outputType: '', // 裁剪生成图片的格式
canScale: true, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
// autoCropWidth: 300, // 默认生成截图框宽度
// autoCropHeight: 200, // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
fixed: true, // 是否开启截图框宽高固定比例
// fixedNumber: [12, 5], // 截图框的宽高比例
full: true, // 是否输出原图比例的截图
canMoveBox: true, // 截图框能否拖动
original: false, // 上传图片按照原始比例渲染
centerBox: false, // 截图框是否被限制在图片里面
infoTrue: true // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
})
}
},
data () {
return {
};
},
computed: {
// computeFunction() {
// return value;
// }
},
watch: {
},
created () {
// this.init();
},
mounted () {
this.init();
},
methods: {
init () {
this.cropperOption.img = this.file
},
cropperChangeScale (num) {
num = num || 1;
this.$refs.cropper.changeScale(num);
},
// 左旋转
cropperRotateLeft () {
this.$refs.cropper.rotateLeft();
},
// 右旋转
cropperRotateRight () {
this.$refs.cropper.rotateRight();
},
// 下载图片
cropperDown (type) {
let aLink = document.createElement('a');
aLink.download = 'author-img';
if (type === 'blob') {
this.$refs.cropper.getCropBlob(data => {
this.downImg = window.URL.createObjectURL(data);
aLink.href = window.URL.createObjectURL(data);
aLink.click();
});
} else {
this.$refs.cropper.getCropData(data => {
this.downImg = data;
aLink.href = data;
aLink.click();
});
}
},
// 确定裁剪
cropperFinish (type) {
if (type === 'Blob') {
this.$refs.cropper.getCropBlob((data) => { //这个方法将转成Blob对象
console.log(data);
let file = data;
file.name = this.file.name;
this.$emit('cropperFinish', file, data);
});
} else {
this.$refs.cropper.getCropData(data => { // 这个方法将转成base64格式
// 将剪裁后base64的图片转化为file格式
let file = this.convertBase64UrlToBlob(data);
file.name = this.file.name;
this.$emit('cropperFinish', file, data);
});
}
},
// 将base64的图片转换为file文件
convertBase64UrlToBlob (urlData) {
let bytes = window.atob(urlData.split(',')[1]); // 去掉url的头,并转换为byte
// 转化为base64
// reader.readAsDataURL(file)
// 转化为blob
// 处理异常,将ascii码小于0的转换为大于0
let ab = new ArrayBuffer(bytes.length);
let ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: 'image/jpeg' });
},
// 实时预览函数
realTime (data) {
// console.log('realTime');
},
// 图片已加载
imgLoad (msg) {
// console.log('imgLoad');
// console.log(msg);
},
close () {
this.$emit('close');
}
}
};
</script>
<style lang="less" scope>
// 基础按钮
.basicButton {
font-size: 16px;
width: 100px;
padding: 8px 10px;
// line-height: 0.4rem;
border: none;
border-radius: 20px;
color: #fff;
background: #5cadff;
outline: none;
&:not(:last-child) {
margin-right: 20px;
}
}
.vue-cropper-header {
.title {
font-weight: 600;
display: flex;
justify-content: space-between;
padding-top: 20px;
margin-bottom: 10px;
.ivu-icon .ivu-icon-ios-close {
font-size: 20px;
}
}
}
/* 图片裁剪工具 */
.vue-cropper-box {
z-index: 3001;
// position: absolute;
top: 0;
background: #fff;
.vue-cropper-operate {
display: flex;
align-items: center;
justify-content: center;
padding: 0.2rem;
}
.vue-cropper-content {
position: relative;
overflow-y: auto;
.vue-cropper {
position: absolute;
}
}
}
</style>
- 新建一个elementui上传组件
<template>
<div>
<el-upload class="avatar-uploader" v-loading="loading" :action="action" :show-file-list="false" name="file" :http-request="picUpload" :before-upload="beforeUpload">
<img v-if="imgList[0]" :src="imgList[0].url" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
export default {
props: {
imgList: {
type: Array,
default () {
return []
}
},
limit: {
type: Number
}
},
data () {
return {
action: '上传地址',
disabled: false,
uploadData: {
file: '',
project: ''
},
loading: false,
httpCtx: process.env.VUE_APP_httpCtx,
}
},
computed: {
// fileList () {
// const val = this.imgList
// const arr = []
// if (val && val.length > 0) {
// val.forEach(item => {
// const { name, path, url } = item
// let imgUrl = ''
// imgUrl = path
// if (url) {
// imgUrl = url
// }
// if (imgUrl.startsWith('http') || imgUrl.startsWith('https')) {
// arr.push({ name: name, url: imgUrl })
// } else {
// arr.push({ name: name, url: `${process.env.VUE_APP_httpCtx}${imgUrl}` })
// }
// })
// return arr
// } else {
// return []
// }
// },
},
created () {
},
methods: {
// 图片上传前触发裁剪组件
// 将图片读出并在完成时触发裁剪
picUpload (option) {
let file = option.file
let reader = new FileReader();
if (file) {
reader.readAsDataURL(file)
}
reader.onload = () => {
let src = reader.result
let obj = {
cropperShow: true,
cropperImg: src
}
this.$emit("modalShowFn", obj)
}
},
handleRemove (file, fileList) {
console.log(file, fileList);
this.$emit('update:imgList', fileList);
},
handleExceed (files, fileList) {
this.$message.warning(
`当前限制选择 ${this.limit} 个文件,本次选择了 ${
files.length
} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
},
beforeUpload (file) {
const isLt10M = file.size / 1024 / 1024 < 10;
const isJPG = file.type === 'image/jpeg' || file.type === 'image/gif' || file.type === 'image/jpg' || file.type === 'image/bmp' || file.type === 'image/png';
if (!isLt10M) {
this.$message.error('上传图片大小不能超过 10MB!');
}
if (!isJPG) {
this.$message.error('上传图片只能是 JPG/JPEG/GIF/BMP 格式!');
}
return isJPG && isLt10M;
},
handProcess (event, file) {
this.loading = true;
}
}
}
</script>
<style scope>
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
border: 1px solid #dcdee2;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 200px;
height: 200px;
line-height: 148px;
text-align: center;
}
.avatar {
width: 200px;
height: 200px;
display: block;
position: relative;
z-index: 99;
}
.el-icon-plus:before {
line-height: 200px;
}
</style>
- 页面中使用
可以引入组件或者注册全局组件
<uploadImage :imgList.sync="addForm.cover" :limit="1" @modalShowFn="getModalStatus />
<el-dialog title="图片剪裁" :visible.sync="isShowCropper" width="50%">
<div class="cropperModalBody" style="width:100%;height:350px">
<img-cropper :img="cropperImg" :fixedNumber="fixedNumber" :outputType="outputType" :file="cropperFile" @cropperFinish="postUploadFile" @close="()=>{isShowCropper=false}"></img-cropper>
</div>
<div slot="footer"></div>
</el-dialog>
- data中定义
isShowCropper: false,
cropperImg: "",
cropperFile: {},
httpCtx: process.env.VUE_APP_httpCtx,
fixedNumber: [16, 15],
outputType: 'jpeg',
- methods中写下方法
getModalStatus (obj) {
this.cropperImg = obj.cropperImg;
this.isShowCropper = obj.cropperShow;
},
// 文件上传-接口-上传文件
async postUploadFile (blob) {
let suffix = blob.type.split('/')[blob.type.split('/').length - 1]
let filename = Date.parse(new Date()) / 1000 + '.' + suffix //生成文件名
//将blob转换为file
let file = new File([blob], filename, { type: blob.type, lastModified: Date.now() });
file.uid = Date.now();
var fd = new FormData();
fd.append("file", file, file.name);
fd.append("imageType", "101");
const res = await this.axios.uploadFiles(fd)
if (res.code === 0) {
this.addForm.cover = [{
name: res.data[0].name,
url: this.httpCtx + res.data[0].path,
}]
// this.$refs.upload.clearFiles()
this.isShowCropper = false;
this.cropperImg = null;
this.cropperFile = {};
}
},