利用vue-cropper剪裁图片并上传

7,520 阅读3分钟

因为需求所以利用vue的插件vue-cropper进行图片剪裁。

安装

npm install vue-cropper

使用

import VueCropper from vue-cropper

效果图

此次主要是利用Element-ui+vue-cropper完成此需求,话不多说上代码。

<div>
    <el-upload
          :action="''"
          list-type="picture-card"
          :file-list="MastergraphPicList"
          :limit="10"
          :on-change='changeUpload'
          :auto-upload="false"
          :show-file-list="isFlag"
          :http-request="ServiceUpLoad"
          :on-success="(response,file,fileList) => handlePostSuccess(response,file,fileList)"
          :on-remove="(response,file,fileList) => handleDelete(response,file,fileList)"
          >
          <i class="el-icon-plus"></i>
          <div slot="tips" class="el-upload__tip">支持jpg、jpge、png格式,大小不超过2m,限10张</div>
        </el-upload>
</div>

上传图片时会触发changeUpload此钩子函数,由于剪裁图片是以弹框形式唤起,因此我们的行为可以在此钩子函数中写。

      changeUpload(file, fileList) {
        <!--限制文件格式、大小-->
        const isLt2M = file.size / 1024 / 1024 < 2
        if (!isLt2M) {
          this.$message.error('上传文件大小不能超过 2MB!')
          return false
        }
        <!--写入回显列表-->
        this.MastergraphPicList.push(file);
        <!--触发子级方法,唤起剪裁框-->
        this.$children[0].isShow();
      },

剪裁框模板 cropper.vue

<template>
    <div>
        <el-dialog title="图片剪裁" :visible.sync="dialogVisible" append-to-body>
        <div class="cropper-content">
            <div class="cropper" style="text-align:center">
            <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"
                :fixed="option.fixed"
                :fixedNumber="option.fixedNumber"
                :centerBox="option.centerBox"
                :infoTrue="option.infoTrue"
                :fixedBox="option.fixedBox"
            ></vueCropper>
            </div>
        </div>
        <div slot="footer" class="dialog-footer">
            <el-button @click="cancel">取 消</el-button>
            <el-button type="primary" @click="finish" :loading="loading">确认</el-button>
        </div>
        </el-dialog>
    </div>
</template>

<script>
import axios from 'axios'
import { VueCropper } from 'vue-cropper'
import md5 from 'js-md5';
import { postossImage } from '../../../api/index'
    export default {
        name:'cropper',
        data() {
            return {
                // 裁剪组件的基础配置option
                option: {
                    img: '', // 裁剪图片的地址
                    info: true, // 裁剪框的大小信息
                    outputSize: 0.8, // 裁剪生成图片的质量
                    outputType: 'jpeg', // 裁剪生成图片的格式
                    canScale: true, // 图片是否允许滚轮缩放
                    autoCrop: true, // 是否默认生成截图框
                    autoCropWidth: 600, // 默认生成截图框宽度
                    autoCropHeight: 300, // 默认生成截图框高度
                    fixedBox: false, // 固定截图框大小 不允许改变
                    fixed: false, // 是否开启截图框宽高固定比例
                    fixedNumber: [7, 5], // 截图框的宽高比例
                    full: true, // 是否输出原图比例的截图
                    canMoveBox: true, // 截图框能否拖动
                    original: true, // 上传图片按照原始比例渲染
                    high: true,
                    centerBox: true, // 截图框是否被限制在图片里面
                    infoTrue: true // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
                },
                picsList: [],  //页面显示的数组
                // 防止重复提交
                loading: false,
                dialogVisible:false,        // 控制弹框的显示
                fileinfo:[],      
                RequestSource:'',           // oss直传源地址
                baseurl:'',                 // 裁剪后的base64地址
                isVictory:false,        // 识别用户是点否关闭
            }
        },
        components:{VueCropper},
        created() {
            this.$on('is-Show',this.isShow);
        },
        methods:{
            isShow(file){
                this.fileinfo = file;
                // 传递需要剪裁的地址 以弹框形式打开
                this.$nextTick( () => {
                    this.option.img = file.url;
                    this.dialogVisible = true;
                    this.handlePostSuccess(file.url);
                })
            },
            // 关闭图片剪裁
            cancel() {
                this.dialogVisible = false;
                // 点击取消时 去除已经回显的图片
                this.$parent.dislodge(this.fileinfo);
            },
            // 确认剪裁后触发
            finish() {
                this.$refs.cropper.getCropData((data) => {
                    this.baseurl = data;
                    //将剪裁后base64的图片转化为file格式
                    let file = this.convertBase64UrlToBlob(data)
                    file.name = this.fileinfo.name
                    <!--let file = URL.createObjectURL(data).match(/[^blob:]+/g).join('');-->
                    this.Directpass(this.RequestSource,file).then( (res) => {
                        if (res.status === 200) {
                            this.$parent.handleSuccess({
                                uid:this.fileinfo.uid,
                                imgUrl:res.config.url.split('?')[0]
                            });
                            this.dialogVisible = false;
                            this.isVictory = true;
                            this.loading = true;
                            this.$message('上传成功!');
                            // 防止用户不上传图片 点击关闭时 不能及时删除回显的问题
                            setTimeout(() => {
                                this.isVictory = false;
                            },1000)
                        }
                    })
                    
                })
            },
            // 上传服务主图
            handlePostSuccess(file) {
                postossImage({
                    fileMd5:md5(file)
                }).then( (res) => {
                    this.RequestSource = res.data;
                })
            },
            Directpass(url,file){
                return new Promise( (resolve,reject) => {
                axios.put(url,file,{headers:{"x-oss-meta-author":"deshe","Content-Type":"application/octet-stream"}}).then( (res) => {
                    resolve(res)
                })
            })
        },
        // 将base64的图片转换为file文件
        convertBase64UrlToBlob(urlData) {
            let bytes = window.atob(urlData.split(',')[1]);//去掉url的头,并转换为byte
            //处理异常,将ascii码小于0的转换为大于0
            let ab = new ArrayBuffer(bytes.length);
            let ia = new Uint8Array(ab);
            for (var i = 0; i < bytes.length; i++) {
                ia[i] = bytes.charCodeAt(i);
            }
            return new Blob([ab], { type: 'image/jpeg' });
        }
    },
    watch:{
        dialogVisible() {
            if (!this.dialogVisible && !this.isVictory) {
                this.loading = false
                // 点击关闭时 去除已经回显的图片
                this.$parent.dislodge(this.fileinfo);
            } else {
                this.loading = false;
            }
        }
    }
}
</script>

<style scoped lang="less">
.cropper-content {
    .cropper {
        width: auto;
        height: 300px;
    }
}
</style>

总结:

在cropper.vue中主要起剪裁作用的是finish()方法以及convertBase64UrlToBlob()。 需要注意的是:剪切后的图片返回的是一串没有意义的字符串,传递到服务器无法识别,在这里我用了 this.$refs.cropper.getCropData() 把剪切的图片以base64的形式返回,接着把base64数据做一层处理,转化为Blob对象,里面存储着剪裁后图片的数据序列,接着把处理好的地址往服务器丢就搞定了。

还有就是: fixed设为false将不限剪裁的宽高

以上仅个人看法。