VUE中使用vue-cropper实现图片裁剪,及问题总结

前言:

网上关于vue-cropper的使用有很多篇,但是能详细介绍并且把隐藏的问题总结的却很少(就很烦),正好题主最近用了这个插件,在此做个总结

需求:(这里题主的需求也是经过几次变更,逐步完善了vue-cropper组件的使用,下列序号表示需求的变更 )

  1. 对头像上传功能加入图片裁剪功能(这里只需能裁剪即可,无其他要求)
  2. 另一个功能点也需要加入图片裁剪功能,且要求裁剪后的图片固定宽高为108像素
  3. 对头像上传时裁剪后的图片加入限制,宽高不超过300像素

问题:

如果只是需要能裁剪图片,那根据其他大部分vue-cropper篇章即可实现;

这里说一下题主在这些需求下出现的问题及解决方法

需求1:正常引用vue-cropper即可实现裁剪功能;(这里把vue-cropper封装为组件,为方便读写,也预防后续其他地方引用)

需求2:这里加入了裁剪图片固定宽高的限制(截图框固定大小108)

        问题1:如果不注意vue-cropper参数的使用,在full: false时,在浏览器页面100%时裁剪图片不出错,当不是100%时,裁剪后的图片宽高会随浏览器页面大小的变化而变化

如:页面缩放比例为90%,截图框显示300,最终截图为270;页面缩放比例为110%,截图框显示300,最终截图为330

        解决:设置full: true

        

        问题2:加入了背景图可滚动后,在full: true时,滚动背景图也会影响裁剪图片的大小

        解决:这里找了很久但还是找到了解决方法,影响裁剪图片大小的问题解决不了,但是可以解决保存图片大小的问题,即使裁剪的图片大小被影响了,但是在保存图片时可以重置图片大小,保存成需要的图片大小

需求3:这里又加入了新限制,没有遇到新问题,只是对组件做了优化提升

这里题主直接上题主最终版的组件了

1.使用vue-cropper之前,先引入:

npm install vue-cropper

2.创建CropperImage组件,main.js中引入

import VueCropper from 'vue-cropper'
import CropperImage from '@/components/CropperImage.vue'
Vue.component('CropperImage', CropperImage)

3.CropperImage中定义

3.1 template (这里加入了放大、缩小、重置大小三个功能按钮)
<template>
  <el-dialog
    title="裁切封面"
    :visible.sync="show"
    width="1000px"
    :before-close="handleClose"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
  >
    <div class="cropper-content">
      <div class="cropper-box">
        <div class="cropper">
          <vue-cropper
            ref="cropper"
            :img="option.img"
            :output-size="option.outputSize"
            :info="option.info"
            :can-scale="option.canScale"
            :auto-crop="option.autoCrop"
            :auto-crop-width="option.autoCropWidth"
            :auto-crop-height="option.autoCropHeight"
            :fixed="option.fixed"
            :fixed-number="option.fixedNumber"
            :full="option.full"
            :fixed-box="option.fixedBox"
            :can-move="option.canMove"
            :can-move-box="option.canMoveBox"
            :original="option.original"
            :center-box="option.centerBox"
            :height="option.height"
            :info-true="option.infoTrue"
            :max-img-size="option.maxImgSize"
            :enlarge="option.enlarge"
            :mode="option.mode"
            :limit-min-size="option.limitMinSize"
            @realTime="realTime"
          />
        </div>
      </div>
      <!--预览效果图-->
      <div class="show-preview">
        <div :style="previews.div" class="preview">
          <img ref="previews" :src="previews.url" :style="previews.img">
        </div>
      </div>
    </div>
    <div class="mt-20">
      <el-button icon="el-icon-plus" @click="scaleBigger">放大</el-button>
      <el-button icon="el-icon-minus" @click="scaleSmaller">缩小</el-button>
      <el-button icon="el-icon-refresh-left" @click="reload">重置大小</el-button>
    </div>

    <span slot="footer" class="dialog-footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="onSubmit">确 定</el-button>
    </span>
  </el-dialog>
</template>
3.2  script
import { VueCropper } from 'vue-cropper'
export default {
  name: 'CropperImage',
  components: {
    VueCropper
  },
  data() {
    return {
      show: false,
      previews: {},
      option: {
        img: '', // 裁剪图片的地址
        outputSize: 1, // 裁剪生成图片的质量(可选0.1 - 1)
        outputType: 'jpeg', // 裁剪生成图片的格式(jpeg || png || webp)
        info: false, // 图片大小信息
        canScale: true, // 图片是否允许滚轮缩放
        autoCrop: true, // 是否默认生成截图框
        autoCropWidth: 300, // 默认生成截图框宽度
        autoCropHeight: 300, // 默认生成截图框高度
        fixed: true, // 是否开启截图框宽高固定比例
        fixedNumber: [1, 1], // 截图框的宽高比例
        full: false, // false按原比例裁切图片,不失真
        fixedBox: false, // 固定截图框大小,不允许改变
        canMove: true, // 上传图片是否可以移动
        canMoveBox: true, // 截图框能否拖动
        original: true, // 上传图片按照原始比例渲染
        centerBox: true, // 截图框是否被限制在图片里面
        height: true, // 是否按照设备的dpr 输出等比例图片
        infoTrue: false, // true为展示真实输出图片宽高,false展示看到的截图框宽高
        maxImgSize: 3000, // 限制图片最大宽度和高度
        enlarge: 1, // 图片根据截图框输出比例倍数
        mode: '550px 400px', // 图片默认渲染方式
        limitMinSize: [108, 108], // 裁剪框限制最小区域
        minCropBoxWidth: 108, // 设置最小裁切框宽度
        minCropBoxHeight: 108 // 设置最小裁切框高度
      },
      file: null,
      form: {}
    }
  },
  methods: {
    // 展示裁剪弹窗
    handleOpen(val, obj, form) {
      this.option = { ...this.option, ...obj }
      this.file = val
      this.form = form
      this.show = true
      this.option.img = URL.createObjectURL(val.raw)
      const reader = new FileReader()
      reader.readAsDataURL(val.raw)
    },
    // 关闭弹窗
    handleClose() {
      this.show = false
    },
    // 实时预览函数
    realTime(data) {
      this.previews = data
    },
    // 缩放图片
    resizeBlob(blob, desiredWidth, desiredHeight) {
      return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = () => {
          const canvas = document.createElement('canvas')
          const ctx = canvas.getContext('2d')

          // 计算缩放比例
          const scaleX = desiredWidth / img.width
          const scaleY = desiredHeight / img.height
          const scale = Math.min(scaleX, scaleY)

          // 设置 Canvas 的宽度和高度
          canvas.width = desiredWidth
          canvas.height = desiredHeight

          // 绘制图片到 Canvas 上,并进行缩放
          ctx.drawImage(img, 0, 0, img.width * scale, img.height * scale)

          // 将 Canvas 中的图像转换为 Blob 对象
          canvas.toBlob((resizedBlob) => {
            resolve(resizedBlob)
          }, blob.type)
        }

        img.onerror = (error) => {
          reject(error)
        }

        img.src = URL.createObjectURL(blob)
      })
    },
    // 确定
    onSubmit() {
      // 获取截图的base64
      this.$refs.cropper.getCropBlob(async(data) => {
        const originalBlob = data // 原始 Blob 对象
        const desiredWidth = this.form.sizeWidth // 所需的宽度
        const desiredHeight = this.form.sizeHeight // 所需的高度
        if (this.form.type == 'fixed') {
          // 如果是需要固定宽高的图片,直接调用方法缩放图片
          this.handleResizeBlob(originalBlob, desiredWidth, desiredHeight)
        } else if (this.form.type == 'max') {
          // 如果是需要不超过xxx宽高的图片,先做判断
          var blob = new Blob([data], { type: 'image/jpeg' })
          var img = new Image()
          var url = URL.createObjectURL(blob)
          img.src = url
          const _this = this
          img.onload = function() {
            // 获取图像的宽度和高度
            var width = img.width
            var height = img.height

            if (width <= desiredWidth && height <= desiredHeight) {
              // 如果裁剪完未超过限制,则直接去上传
              var result = new File([data], `头像${(new Date()).getTime()}.jpeg`, { type: 'image/jpeg', lastModified: Date.now() })
              _this.handleClose()
              _this.$emit('handleUploadSuccess', result)
            } else {
              // 如果裁剪完超过限制,则调用方法缩放图片
              _this.handleResizeBlob(originalBlob, desiredWidth, desiredHeight)
            }
            // 清理 URL 对象
            URL.revokeObjectURL(url)
          }
        }
      })
    },
    handleResizeBlob(originalBlob, desiredWidth, desiredHeight) {
      this.resizeBlob(originalBlob, desiredWidth, desiredHeight)
        .then((resizedBlob) => {
          // 在此处使用缩放后的 Blob 对象
          var result = new File([resizedBlob], `头像${(new Date()).getTime()}.jpeg`, { type: 'image/jpeg', lastModified: Date.now() })
          this.handleClose()
          this.$emit('handleUploadSuccess', result)
        })
        .catch((error) => {
          console.error('Error resizing Blob:', error)
        })
    },
    // 放大
    scaleBigger() {
      this.$refs.cropper.changeScale(1)
    },
    // 缩小
    scaleSmaller() {
      this.$refs.cropper.changeScale(-1)
    },
    // 重置大小
    reload() {
      this.$refs.cropper.reload()
    }
  }
}
3.3  style
.cropper-content{
  display: flex;
  display: -webkit-flex;
  justify-content: flex-end;
  .cropper-box{
    width: 550px;
    .cropper{
      width: auto;
      height: 400px;
    }
  }

  .show-preview{
    flex: 1;
    -webkit-flex: 1;
    display: flex;
    display: -webkit-flex;
    justify-content: center;
    .preview{
      overflow: hidden;
      border:1px solid #67c23a;
      background: #cccccc;
    }
  }
}
.footer-btn{
  margin-top: 30px;
  display: flex;
  display: -webkit-flex;
  justify-content: flex-end;
  .scope-btn{
    display: flex;
    display: -webkit-flex;
    justify-content: space-between;
    padding-right: 10px;
  }
  .upload-btn{
    flex: 1;
    -webkit-flex: 1;
    display: flex;
    display: -webkit-flex;
    justify-content: center;
  }
  .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;
    -webkit-transition: .1s;
    transition: .1s;
    font-weight: 500;
    padding: 8px 15px;
    font-size: 12px;
    border-radius: 3px;
    color: #fff;
    background-color: #409EFF;
    border-color: #409EFF;
    margin-right: 10px;
  }
}
4 调用CropperImage
/**
 * 组件调用传参
 *  {
 *    file, // 文件对象
 *    { autoCropWidth: 108, autoCropHeight: 108 }, // 需要改变的组件option的参数值
 *    { type: 'fixed', sizeWidth: 108, sizeHeight: 108 } // type:fixed/max 固定大小/最大值,传值图片限定的宽高
 *  }
*/

<template>
    <CropperImage ref="cropperImage" @handleUploadSuccess="handleUploadSuccess" />
</template>


<script>
    getFile(file) {
      this.$refs.cropperImage.handleOpen(file, { autoCropWidth: 108, autoCropHeight: 108 }, { type: 'fixed', sizeWidth: 108, sizeHeight: 108 })
    },
    handleUploadSuccess(val) {
        // val: 截取后的图片file对象,可直接用于上传
        ......
    }
</script>

这一个组件也是出了好几次问题,不过最终都解决了,大家如有觉得书写不规范,或有其他解决方法、问题,可留言评论讨论。

哈哈^_^~~

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值