【VUE】vue+vue-cropper实现上传剪裁图片以及上传时压缩图片

第一次做上传剪裁图片,找了许多框架,最后找到一个优雅的图片裁剪插件vue-cropper,很方便新手入手

安装

npm install vue-cropper

使用

import VueCropper from vue-cropper

import axios from 'axios'
const host = 'xxx';

//预上传
export function uploadBefore(md5, size, ext) {
    const url = host + "/file/upload/before";
    return axios.post(url, {
        md5: md5,
        size: size,
        ext: ext
    }).then((res) => {
        return Promise.resolve(res)
    })
}
//保存文件
export function saveImage(key) {
    const url = host + "/file/save";
    return axios.put(url, {
        key: key
    }).then((res) => {
        return Promise.resolve(res)
    })
}
import * as qiniu from 'qiniu-js'
import browserMD5File from 'browser-md5-file'
import { uploadBefore as preUploadFile, saveImage as saveFile } from 'api/uploadImage'

export function uploadFile(file) {
  return new Promise((resolve, reject) => {
    function checkError(res) {
      if (res.data.code !== 0) {
        reject(new Error(res.data.msg))
      }
    }

    // 预上传
    new Promise(preResolve => {
      // 生成文件 MD5 以便校验服务器上是否存在该文件
      browserMD5File(file, function (err, md5) {
        // size 转换成 kb
        const size = file.size / 1024
        const ext = file.name.substr(file.name.lastIndexOf('.') + 1)

        preUploadFile(md5, size, ext).then(res => {
          checkError(res)

          // 当 file_id 存在时表示文件已经上传过,所以进行秒传处理
          if (res.data.data.file_id) {
            resolve(res.data.data)
          } else {
            preResolve(res.data.data)
          }
        })
      })
    }).then(res => {
      let observable = qiniu.upload(file, res.key, res.token)
      return new Promise((uploadResolve, uploadReject) => {
        observable.subscribe({
          next(res) {
          },
          error(err) {
            uploadReject(err)
          },
          complete(res) {
            uploadResolve(res)
          }
        })
      })
    }).then(res => {
      const key = res.key

      return saveFile(key)
    }).then(res => {
      checkError(res)

      resolve(res.data.data)
    }).catch(e => {
      console.log(e)
      this.$message.warning(e.message)
    })
  })
}

<template>
  <div>
     <!-- element 上传图片按钮 -->
      <el-upload class="uploader"
                       action
                       :show-file-list="false"
                       :on-success="handleUploadSuccess"
                       :http-request="customUpload">
              <img v-if="fileinfo.url"
                   :src="fileinfo.url"
                   class="upload">
              <i v-else
                 class="el-icon-plus uploader-icon" />
            </el-upload>
......
    <!-- vueCropper 剪裁图片实现-->
    <div class="vue-cropper-box"
         v-if="isShowCropper">
      <div class="vue-cropper-content">
        <vueCropper ref="cropper"
                    :img="option.img"
                    :outputSize="option.outputSize"
                    :outputType="option.outputType"
                    :info="option.info"
                    :canScale="option.canScale"
                    :autoCrop="option.autoCrop"
                    :autoCropWidth="option.autoCropWidth"
                    :autoCropHeight="option.autoCropHeight"
                    :fixed="option.fixed"
                    :fixedNumber="option.fixedNumber"></vueCropper>
      </div>
      <el-button v-if="isShowCropper"
                 type="danger"
                 @click="onCubeImg">确定裁剪图片</el-button>

    </div>
  </div>
</template>

<script>
import VueCropper from "vue-cropper"
import { uploadFile } from './uploadFile'

export default {
  components: {
    VueCropper
  },
  data() {
    return {
     //裁剪组件的基础配置option
      option: {
        img: '',                         //裁剪图片的地址
        info: true,                      //裁剪框的大小信息
        outputSize: 1,                   // 裁剪生成图片的质量
        outputType: 'jpeg',              //裁剪生成图片的格式
        canScale: false,                 // 图片是否允许滚轮缩放
        autoCrop: true,                  // 是否默认生成截图框
        autoCropWidth: 150,              // 默认生成截图框宽度
        autoCropHeight: 150,             // 默认生成截图框高度
        fixed: false,                    //是否开启截图框宽高固定比例
        fixedNumber: [4, 4]              //截图框的宽高比例
      },
      isShowCropper: false,            //是否显示截图框
      fileUpload: null,
      fileinfo: {},
      form: {},
    }
},
  methods: {
    //上传按钮上传成功执行事件
    handleUploadSuccess(response, file, fileList) {
      this.log('Upload response is %o', response)
      this.fileinfo = response

      this.fileUpload = file;

      //上传成功后将图片地址赋值给裁剪框显示图片
      this.$nextTick(() => {
        this.option.img = file.url;
        this.isShowCropper = true
      })
    },

    // 确定裁剪图片
    onCubeImg() {
      // 获取cropper的截图的base64 数据
      this.$refs.cropper.getCropData(data => {
        this.fileinfo.url = data
        this.isShowCropper = false

       //先将显示图片地址清空,防止重复显示
        this.option.img = ''

       //将剪裁后base64的图片转化为file格式
        let file = this.convertBase64UrlToBlob(data)
        file.name = this.fileUpload.name

        //将剪裁后的图片执行上传
        this.uploadFile(file).then(res => {
          this.form.content = res.file_id    //将上传的文件id赋值给表单from的content
        })

      })
    },

    // 将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' });
    },

代码暂时都是从项目中抽出来的,只适合借鉴参考,等有时间再单独将这些功能单独写项目,欢迎大家提供更好用的方法或指出不足之处,一起进步。


2019-11-07最新更新,在elementUI封装成组件(在PC端)

//使用方式: 
...
     <upload-cover-item :url="form.url"
                           @change="changeAvatar" />
...
   // 图片
    changeAvatar (data) {
      this.form.url= data
    },
上传之前
上传之后
//upload-cover-item
<template>
  <div class="upload-box">
    <div class="avatar-uploader-box">
      <!-- 方框样式 -->
      <el-upload ref="avatarUploader"
                 class="avatar-uploader"
                 :action="actionUrl"
                 :show-file-list="false"
                 :on-change="handleAvatarChange"
                 :auto-upload="false"
                 accept="image/*"
                 v-if="type==='avatar' && !imageUrl">
        <span v-loading="loading"
              element-loading-text="上传中"
              element-loading-spinner="el-icon-loading"
              element-loading-background="rgba(0, 0, 0, 0.8)"
              style="line-height:30px;"
              v-if="!imageUrl">
          <i class="el-icon-plus avatar-uploader-icon"></i></span>
      </el-upload>

      <!-- 上传的图片 -->
      <img v-if="imageUrl"
           v-lazy="imageUrl"
           :src="imageUrl"
           class="avatar"
           @mouseover.stop="isShowPopup = true" />

      <!-- 显示查看和删除的按钮弹窗 -->
      <div class="avatar-uploader-popup"
           v-show="imageUrl && isShowPopup"
           @mouseleave="isShowPopup = false">
        <i class="el-icon-zoom-in"
           @click="dialogVisible = true"></i>
        <i class="el-icon-delete"
           @click="delImageUrl"></i>
      </div>
    </div>

    <!-- 查看大图 -->
    <el-dialog title="图片查看"
               center
               :visible.sync="dialogVisible"
               append-to-body>
      <img width="100%"
           :src="imageUrl"
           alt />
    </el-dialog>

    <!-- 裁剪图片 start-->
    <vue-cropper-item ref="VueCropperItem"
                      @confirm="confirmCropper" />
    <!-- 裁剪图片 end-->
  </div>
</template>

<script>
import * as url from '@/api/httpConfig'
import { uploadOss } from '@/api/modules/system' 
 // 上传图片 export const uploadOss = params => http.upload(`/sys/oss/upload`, params)

export default {
  name: 'upload-item',
  components: {
    VueCropper,
  },
  props: {
    type: {
      type: String,
      default: 'avatar'
    },
    url: {
      type: String,
      default: ''
    },
    autoCropWidth: {
      type: Number,
      default: 275
    },
    autoCropHeight: {
      type: Number,
      default: 206
    },
    fixedBox: {
      type: Boolean,
      default: false
    },
    isCompress: {  //是否压缩
      type: Boolean,
      default: true
    },
    compress: {  //压缩率
      type: String,
      default: '0.8'
    },
  },
  data () {
    return {
      loading: false,
      isShowPopup: false,
      dialogVisible: false,
      isProgress: false,
      percentage: 0,
      imageUrl: '',
      actionUrl: `${url.default.apiURL}/sys/oss/upload`,
    }
  },
  created () {
    this.imageUrl = this.url;
  },
  methods: {
    // 上传之前
    beforeAvatarUpload (file) {
      return new Promise((resolve, reject) => {
        if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(file.name)) {
          return reject('上传图片只能是JPG或PNG格式!');
        }

        if (file.size > 5 * 1024 * 1024) {
          return reject('上传图片大小不能超过5M!');
        }

        resolve("符合表單規則");
      });
    },

    // 上传改变
    async handleAvatarChange (file, fileList) {
      try {
        await this.beforeAvatarUpload(file)
        // console.log('上传改变', file)
        this.showVueCropperItem(file)

      } catch (e) {
        this.$message.warning(JSON.stringify(e))
      }
    },

    // 上传图片接口
    async uploadOssApi (data) {
      try {
        let params = new FormData()
        params.append('file', data)

        const res = await uploadOss(params)

        this.imageUrl = res.data.url;
        this.isProgress = false
        this.$emit('change', res.data.url)
      } catch (err) {
        this.$message.error('上传失败')
        this.imageUrl = ''
        this.isProgress = false
        this.$emit('change', '')
      }
    },

    // 删除图片
    delImageUrl () {
      this.imageUrl = ''
      this.$emit('change', '')
    },

    // 显示裁剪框
    showVueCropperItem (file) {
      this.$refs.VueCropperItem.init()
      this.$refs.VueCropperItem.fileName = file.name;

      this.$refs.VueCropperItem.options.img = URL.createObjectURL(file.raw);
      this.setVueCropperOptions()
    },

    // 确认裁剪后上传
    async confirmCropper (file) {
      this.loading = true
      await this.uploadOssApi(file)
      this.loading = false
    },

    // 设置裁剪的配置
    setVueCropperOptions () {
        this.$refs.VueCropperItem.options.autoCropWidth = this.autoCropWidth;
        this.$refs.VueCropperItem.options.autoCropHeight = this.autoCropHeight;
        this.$refs.VueCropperItem.options.fixedBox = this.fixedBox;

      this.$refs.VueCropperItem.isCompress = this.isCompress; //是否压缩图片
      this.$refs.VueCropperItem.compress = this.compress //压缩率
    },

  },
  watch: {
    url () {
      this.imageUrl = this.url;
    }
  }
}
</script>

<style lang="scss" scoped>
.upload-box {
  .avatar-uploader-box {
    position: relative;
    line-height: 0;
    width: fit-content; //  收缩与包裹,收缩到合适
    max-width: 400px;
    .avatar-uploader {
      /deep/ .el-upload {
        border: 1px dashed #d9d9d9;
        border-radius: 6px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
        &:hover {
          border-color: #409eff;
        }
      }

      .avatar-uploader-icon {
        font-size: 28px;
        color: #8c939d;
        min-width: 178px;
        height: 178px;
        line-height: 178px;
        text-align: center;
      }
      .avatar {
        min-width: 178px;
        display: block;
      }
    }
    .progress-box {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
      width: 99%;
      height: 99%;
      background: #fff;
      display: flex;
      justify-content: center;
      align-items: center;
    }

    .avatar-uploader-popup {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
      width: 100%;
      height: 100%;
      background: rgba($color: #000000, $alpha: 0.5);
      display: flex;
      justify-content: center;
      align-items: center;
      color: #fff;
      font-size: 20px;
      border-radius: 6px;
      i {
        width: 30%;
        text-align: center;
        padding: 0 5%;
        font-size: 24px;
      }
    }
  }
}
</style>
裁剪框
//vue-cropper-item

<template>
  <!-- 裁剪图片 start-->
  <el-dialog title="裁剪图片"
             width="700px"
             :close-on-click-modal="false"
             :close-on-press-escape="false"
             :show-close="false"
             :append-to-body="true"
             :visible.sync="isShowCropper">
    <!-- 主区域 start -->
    <div class="vue-cropper-box">
      <!-- 裁剪框 start -->
      <div class="vue-cropper-content">
        <div class="show-cropper"
             v-loading="loading"
             element-loading-text="加载中"
             element-loading-spinner="el-icon-loading"
             element-loading-background="rgba(0, 0, 0, 0.8)">
          <VueCropper ref="cropper"
                      :img="options.img"
                      :outputSize="options.size"
                      :outputType="options.outputType"
                      :info="options.info"
                      :centerBox="options.centerBox"
                      :canScale="options.canScale"
                      :autoCrop="options.autoCrop"
                      :autoCropWidth="options.autoCropWidth"
                      :autoCropHeight="options.autoCropHeight"
                      :fixedBox="options.fixedBox"
                      :fixed="options.fixed"
                      :fixedNumber="options.fixedNumber"
                      :original="options.original"
                      :full="options.full"
                      @realTime="realTime"
                      @imgLoad="imgLoad"></VueCropper>
        </div>
      </div>
      <!-- 裁剪框 end -->

      <!--底部 设置按钮 start-->
      <div class="vue-cropper-bottom">
        <p class="vue-cropper-bottom-item">
          自定义尺寸:<el-switch v-model="options.fixedBox"
                     active-color="#13ce66"
                     inactive-color="#ff4949"
                     :active-value="false"
                     :inactive-value="true"> </el-switch>
        </p>
        <p class="vue-cropper-bottom-item">
          <el-tooltip class="item"
                      effect="dark"
                      content="放大图片"
                      placement="bottom">
            <i class="el-icon-zoom-in scale"
               @click="changeScale(1)"></i>
          </el-tooltip>
        </p>
        <p class="vue-cropper-bottom-item">
          <el-tooltip class="item"
                      effect="dark"
                      content="缩小图片"
                      placement="bottom">
            <i class="el-icon-zoom-out scale"
               @click="changeScale(-1)"></i>
          </el-tooltip>
        </p>
        <p class="vue-cropper-bottom-item">
          <el-tooltip class="item"
                      effect="dark"
                      content="向左旋转90°"
                      placement="bottom">
            <i class="el-icon-refresh-left scale"
               @click="rotateLeft"></i>
          </el-tooltip>
        </p>
      </div>
      <!--底部 设置按钮 end-->
    </div>
    <!-- 主区域 end -->

    <span slot="footer"
          class="dialog-footer">
      <el-button v-if="isShowCropper"
                 @click="cancelCubeImg">取消</el-button>
      <el-button v-if="isShowCropper"
                 type="danger"
                 :disabled="loading"
                 :loading="isLoading"
                 @click="onCubeImg">确定</el-button>
    </span>
  </el-dialog>
  <!-- 裁剪图片 end-->
</template>

<script>
import { VueCropper } from "vue-cropper"
export default {
  name: 'vue-cropper-item',
  components: {
    VueCropper,
  },
  data () {
    return {
      isCompress: true, //是否压缩
      compress: 0.8, //压缩率
      isShowCropper: false,
      options: {
        img: '',
        info: true,
        size: 1,
        outputType: 'jpeg', //裁剪生成图片的格式
        centerBox: true, //截图框是否被限制在图片里面
        canScale: true, //图片是否允许滚轮缩放

        autoCrop: true,  // 是否默认生成截图框, 只有自动截图开启 宽度高度才生效
        autoCropWidth: 320,
        autoCropHeight: 200,
        fixedBox: false, //  固定截图框大小 不允许改变

        fixed: false,  // 开启宽度和高度比例
        fixedNumber: [4, 4],

        original: false,  // 上传图片按照原始比例渲染
        full: true  // 是否输出原图比例的截图
      },
      fileName: null,

      loading: false,
      isLoading: false,
      previews: {},
    }
  },
  methods: {
    // 初始化
    init () {
      this.isShowCropper = true
      this.loading = true
    },

    // 图片加载完成
    imgLoad (data) {
      this.loading = false
      this.isLoading = false
    },

    // 裁剪图放大或缩小
    changeScale (val) {
      this.$refs.cropper.changeScale(val)
    },

    // 向左旋转
    rotateLeft () {
      this.$refs.cropper.rotateLeft()
    },

    // 裁剪图片上传
    onCubeImg (file) {
      this.isLoading = true
      this.$refs.cropper.getCropData(data => {
        this.isLoading = false
        this.isShowCropper = false
        this.options.img = ''

        let file = null
        if (this.isCompress) {  //是否压缩
          let img = new Image()
          img.src = data
          img.onload = () => {
            let _data = this.onImgCompression(img)
            file = this.dataURLtoFile(_data, this.fileName)
            console.log('图片大小-压缩过:', (file.size / 1024).toFixed(2), 'kb,', '压缩率:', this.compress)
            this.$emit('confirm', file)
          }
        } else {
          file = this.dataURLtoFile(data, this.fileName)
          console.log('图片大小-未压缩:', (file.size / 1024).toFixed(2), 'kb')
          this.$emit('confirm', file)
        }
      })
    },

    // 将裁剪base64的图片转换为file文件
    dataURLtoFile (dataurl, filename) {
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, { type: mime });
    },


    // 压缩图片 
    onImgCompression (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 compress = this.compress || 0.8  //压缩率
      return canvas.toDataURL("image/jpeg", compress)
    },

    // 取消裁剪
    cancelCubeImg () {
      this.isShowCropper = false
      this.$message.info('取消裁剪图片!')
    },
  },
  watch: {
  }
}
</script>

<style lang="scss" scoped>
.vue-cropper-box {
  width: 100%;
  height: 450px;
  .vue-cropper-content {
    width: 100%;
    height: 90%;
    padding-bottom: 20px;
    text-align: center;
    display: flex;
    justify-content: space-between;

    .show-cropper {
      // width: 50%;
      width: 100%;
      height: 100%;
    }
    .show-preview {
      width: 50%;
      height: 100%;
      margin: 5px;
      overflow: hidden;
    }
  }

  .vue-cropper-bottom {
    display: flex;
    flex-flow: wrap row;
    // justify-content: space-between;
    align-items: center;
    &-item {
      margin-right: 15px;
    }
    .scale {
      font-size: 24px;
      color: dodgerblue;
      vertical-align: middle;
      &:hover {
        color: mediumseagreen;
        box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5);
      }
    }
  }
}
</style>

2019-11-07最新更新,在Vant封装成组件 (移动端)

//使用方式: 
...
     <upload-cover-item :url="form.url"
                           @change="changeAvatar" />
...
   // 图片
    changeAvatar (data) {
      this.form.url= data
    },
上传之前
上传之后
//upload-cover-item
<template>
  <div class="upload-box">
    <div class="avatar-uploader-box">
      <van-uploader :before-read="beforeRead"
                    v-if="!imageUrl">
        <!-- 背景图,可更换 -->
        <img src="../../assets/img/common/product-cover-upload.png" />
      </van-uploader>

     <!-- 上传后的图片显示 -->
      <div class="avatar-img-box"
           v-if="imageUrl">
        <img v-lazy="imageUrl"
             :src="imageUrl"
             class="avatar-img"
             @click="openPreview" />

        <van-icon name="clear"
                  class="delete-icon"
                  @click="delImageUrl" />
      </div>
    </div>

    <!-- 预览图片 -->
    <van-image-preview v-model="isShowPreview"
                       :images="previewImgs">
      <template v-slot:index>预览图片</template>
    </van-image-preview>

    <!-- 裁剪图片 -->
    <vue-cropper-item ref="VueCropperItem"
                      @confirm="confirmCropper" />

  </div>
</template>

<script>
import { uploadOss } from '@/api/index'
 // 上传图片 export const uploadOss = params => http.upload(`/sys/oss/upload`, params)

export default {
  name: 'upload-item',
  props: {
    type: {
      type: String,
      default: 'avatar'
    },
    url: {
      type: String,
      default: ''
    },
    autoCropWidth: {
      type: Number,
      default: 275
    },
    autoCropHeight: {
      type: Number,
      default: 206
    },
    fixedBox: {
      type: Boolean,
      default: false
    },
    isCompress: {  //是否压缩
      type: Boolean,
      default: true
    },
    compress: {  //压缩率
      type: String,
      default: '0.8'
    },
  },
  data () {
    return {
      isShowPreview: false,
      imageUrl: '', //require('../../assets/img/logo.png'),
      previewImgs: []
    }
  },
  created () {
    this.imageUrl = this.url;
  },
  methods: {
    // 预览图片
    openPreview () {
      this.isShowPreview = true;
      this.previewImgs[0] = this.imageUrl
    },

    // 上传之前
    beforeRead (file) {
      if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(file.name)) {
        return this.$toast('上传图片只能是JPG或PNG格式!');
      }

      if (file.size > 5 * 1024 * 1024) {
        return this.$toast('上传图片大小不能超过5M!');
      }

      // console.log('选择完成显示裁剪框', file)
      this.showVueCropperItem(file)
    },

    // 显示裁剪框
    showVueCropperItem (file) {
      this.$refs.VueCropperItem.init()
      this.$refs.VueCropperItem.fileName = file.name;
      this.$refs.VueCropperItem.options.img = URL.createObjectURL(file);
      this.setVueCropperOptions()
    },

    // 设置裁剪的配置
    setVueCropperOptions () {
        this.$refs.VueCropperItem.options.autoCropWidth = this.autoCropWidth;
        this.$refs.VueCropperItem.options.autoCropHeight = this.autoCropHeight;
        this.$refs.VueCropperItem.options.fixedBox = this.fixedBox;

      this.$refs.VueCropperItem.isCompress = this.isCompress; //是否压缩图片
      this.$refs.VueCropperItem.compress = this.compress //压缩率
    },

    // 确认裁剪后上传
    async confirmCropper (file) {
      await this.uploadOssApi(file)
    },

    // 上传图片接口
    async uploadOssApi (data) {
      this.$showLoading('上传中...');
      try {
        let params = new FormData()
        params.append('file', data)

        const res = await uploadOss(params)

        this.imageUrl = res.data.url;

        this.$emit('change', res.data.url)
      } catch (err) {
        this.$toast.fail('上传失败')
        this.imageUrl = ''

        this.$emit('change', '')
      }
      this.$toast.clear()
    },

    // 删除图片
    delImageUrl () {
      this.imageUrl = ''
      this.$emit('change', '')
    }
  },
  watch: {
    url () {
      this.imageUrl = this.url;
    }
  }
}
</script>

<style lang="scss" scoped>
.upload-box {
  /deep/ .van-uploader__wrapper {
    width: 80px;
    height: 60px;
    background: #f3f3f5;
    border: 1px solid #e1e1e3;
    border-radius: 8px;
    display: flex;
    justify-content: center;
  }

  .avatar-img-box {
    position: relative;
    width: 80px;
    height: 60px;
    border: 1px solid #f3f3f5;
    border-radius: 8px;
    .avatar-img {
      width: 100%;
      height: 100%;
      overflow: hidden;
    }
    .delete-icon {
      position: absolute;
      top: -10px;
      right: -10px;
      font-size: 22px;
    }
  }
}
</style>
裁剪图片框
//vue-cropper-item
<template>
  <!-- 裁剪图片 start-->
  <div class="vue-cropper-container"
       v-if="isShowCropper">

    <header class="header-nav">裁剪图片</header>

    <!-- 主区域 start -->
    <div class="vue-cropper-box">
      <!-- 裁剪框 start -->
      <div class="vue-cropper-content">
        <div class="show-cropper">
          <VueCropper ref="cropper"
                      :img="options.img"
                      :outputSize="options.size"
                      :outputType="options.outputType"
                      :info="options.info"
                      :centerBox="options.centerBox"
                      :canScale="options.canScale"
                      :autoCrop="options.autoCrop"
                      :autoCropWidth="options.autoCropWidth"
                      :autoCropHeight="options.autoCropHeight"
                      :fixedBox="options.fixedBox"
                      :fixed="options.fixed"
                      :fixedNumber="options.fixedNumber"
                      :original="options.original"
                      :full="options.full"
                      @imgLoad="imgLoad"></VueCropper>
        </div>
      </div>
      <!-- 裁剪框 end -->
    </div>
    <!-- 主区域 end -->

    <div class="vue-cropper-footer">
      <span @click="cancelCubeImg">{{$t('btn.cancel')}}</span>
      <span @click="onCubeImg">{{$t('btn.confirm')}}</span>
    </div>
  </div>
  <!-- 裁剪图片 end-->
</template>

<script>
import { VueCropper } from "vue-cropper"
export default {
  name: 'vue-cropper-item',
  components: {
    VueCropper,
  },
  data () {
    return {
      isCompress: true, //是否压缩
      compress: 0.8, //压缩率
      isShowCropper: false,
      options: {
        img: require('../../assets/img/logo.png'),
        info: true,
        size: 1,
        outputType: 'jpeg', //裁剪生成图片的格式
        centerBox: true, //截图框是否被限制在图片里面
        canScale: true, //图片是否允许滚轮缩放

        autoCrop: true,  // 是否默认生成截图框, 只有自动截图开启 宽度高度才生效
        autoCropWidth: 275,
        autoCropHeight: 206,
        fixedBox: true, //  固定截图框大小 不允许改变

        fixed: false,  // 开启宽度和高度比例
        fixedNumber: [4, 4],

        original: false,  // 上传图片按照原始比例渲染
        full: true  // 是否输出原图比例的截图
      },
      fileName: null,
    }
  },
  methods: {
    // 初始化
    init () {
      this.isShowCropper = true
      this.$showLoading(this.$t('loading'));
    },

    // 图片加载完成
    imgLoad (data) {
      this.$toast.clear()
    },

    // 裁剪图片上传
    onCubeImg (file) {
      if (!this.isShowCropper) return

      this.$showLoading(this.$t('confirm'));
      this.$refs.cropper.getCropData(data => {
        this.$toast.clear()
        this.isShowCropper = false
        this.options.img = ''

        let file = null
        if (this.isCompress) {  //是否压缩
          let img = new Image()
          img.src = data
          img.onload = () => {
            let _data = this.onImgCompression(img)
            file = this.dataURLtoFile(_data, this.fileName)
            console.log('图片大小-压缩过:', (file.size / 1024).toFixed(2), 'kb,', '压缩率:', this.compress)
            this.$emit('confirm', file)
          }
        } else {
          file = this.dataURLtoFile(data, this.fileName)
          console.log('图片大小-未压缩:', (file.size / 1024).toFixed(2), 'kb')
          this.$emit('confirm', file)
        }
      })
    },

    // 将裁剪base64的图片转换为file文件
    dataURLtoFile (dataurl, filename) {
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, { type: mime });
    },

    // 压缩图片 
    onImgCompression (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 compress = this.compress || 0.8  //压缩率
      return canvas.toDataURL("image/jpeg", compress)
    },

    // 取消裁剪
    cancelCubeImg () {
      this.isShowCropper = false
      this.$toast(this.$t('tips.cancelText'))
    }
  },
  watch: {
  }
}
</script>

<style lang="scss" scoped>
.vue-cropper-container {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 99;
  width: 100%;
  height: 100%;
  background: #000;

  .vue-cropper-box {
    width: 100%;
    height: 70%;
    margin: 10% 0;
    .vue-cropper-content {
      width: 100%;
      height: 90%;
      padding-bottom: 20px;
      text-align: center;
      display: flex;
      justify-content: space-between;

      .show-cropper {
        width: 100%;
        height: 100%;
      }
    }
  }
}

.header-nav {
  width: 100%;
  height: 50px;
  line-height: 50px;
  text-align: center;
  color: #fff;
  font-size: 16px;
}

.vue-cropper-footer {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 50px;
  line-height: 40px;
  text-align: center;
  padding: 5px 0;
  color: #fff;
  border-top: 1px solid rgba(255, 255, 255, 0.4);
  span {
    width: 50%;
    &:first-child {
      border-right: 1px solid rgba(255, 255, 255, 0.4);
    }
  }
}
</style>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容