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" > </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