基于vue3 封装一个图片裁切功能组件 vue-cropper.js

使用的vue-cropper.js,官方文档

https://github.com/xyxiao001/vue-cropper

附图片各格式之间的转换

https://www.cnblogs.com/huihuihero/p/17667325.html

注意:下载vue-cropper.js时,使用

yarn add vue-cropper@next下载最新版本(当前用的1.0.9),部分版本会有报错

组件代码 img-cropper.js(使用了element的对话框组件,如有需要可自行更换)

<template>
  <div class="img-cropper-container">
    <div class="img-cropper-content"></div>
    <div class="dialog-content">
      <el-dialog
        v-model="cropperDialog"
        width="600px"
        :close-on-click-modal="false"
        :close-on-press-escape="false"
        :show-close="false"
      >
        <div class="every-common-dialog">
          <div class="commonpop-top">
            <div class="poptop-title">图片裁切</div>
            <div class="poptop-cancel" @click="cancelCropper">
              <span class="iconfont icon-closel"></span>
            </div>
          </div>
          <div class="commonpop-content">
            <div class="cc-cropper-content">
              <vue-cropper
                style="width: 100%; height: 100%"
                ref="cropperRef"
                :img="cropperConfig.img"
                :outputSize="cropperConfig.outputSize"
                :outputType="cropperConfig.outputType"
                :info="cropperConfig.info"
                :canScale="cropperConfig.canScale"
                :autoCrop="cropperConfig.autoCrop"
                :autoCropWidth="cropperConfig.autoCropWidth"
                :autoCropHeight="cropperConfig.autoCropHeight"
                :fixed="cropperConfig.fixed"
                :fixedNumber="cropperConfig.fixedNumber"
                :full="cropperConfig.full"
                :fixedBox="cropperConfig.fixedBox"
                :canMove="cropperConfig.canMove"
                :canMoveBox="cropperConfig.canMoveBox"
                :original="cropperConfig.original"
                :centerBox="cropperConfig.centerBox"
                :high="cropperConfig.high"
                :infoTrue="cropperConfig.infoTrue"
                :maxImgSize="cropperConfig.maxImgSize"
                :enlarge="cropperConfig.enlarge"
                :mode="cropperConfig.mode"
                :limitMinSize="cropperConfig.limitMinSize"
                :fillColor="cropperConfig.fillColor"
              ></vue-cropper>
            </div>
          </div>
          <div class="commonpop-footer">
            <div class="common-cancel-btn" v-if="cropperConfig.allowOrigin" @click="clickOriginImgBtn">上传原图</div>
            <div class="common-submit-btn" @click="clickCropperImgBtn">上传裁切图</div>
          </div>
        </div>
      </el-dialog>
    </div>
  </div>
</template>

<script>
import { nextTick, reactive, toRefs } from 'vue';
import { showSuccessToast, showToast, showDialog, showConfirmDialog } from 'vant';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { blobToFile, fileToBase64 } from '@/utils/file/imgformat-deal';
import { randomNum } from '@/utils/data-handle/data-number';
export default {
  name: 'ImgCropper',
  components: {
    VueCropper,
  },
  props: {
    /**
     * 裁切配置,正常通过以下方式即可调用,更多配置参考cropperConfig里的注释
     * state.cropperConfig.file = fileInfo;
     * state.cropperConfig.fileName = fileInfo?.name ?? '';
     * state.imgCropperRef.showCropperDialog();
     */
    config: {
      type: Object,
      required: false,
      default: () => ({}),
    },
  },
  /**
   * 方法
   * emit("cancelCropper")  用户取消裁切事件。无参数返回
   * emit("originImgDone")  用户确认原图事件。无参数返回
   * emit("cropperImgDone")  用户确认裁切图事件。参数:裁切后的图片信息(file格式)
   */
  setup(props, { emit }) {
    const $route = useRoute();
    const $router = useRouter();
    const $store = useStore();
    const state = reactive({
      cropperRef: null,
      cropperDialog: false, //裁切弹窗
      cropperConfig: {
        //自定义配置
        file: null, //图片文件信息,实际应用时,若此字段有数据,则会基于此file字段转换为base64后赋值于img字段
        fileName: '', //文件名称,不传则在用到时采用随机数命名
        allowOrigin: true, //是否允许原图上传:true, false

        //vue-cropper规定配置
        img: '', //裁剪图片的地址:url 地址, base64, blob
        outputSize: 1, //裁剪生成图片的质量:0.1 ~ 1
        outputType: 'png', //裁剪生成图片的格式:jpeg, png, webp
        info: true, //裁剪框的大小信息:true, false
        canScale: true, //图片是否允许滚轮缩放:true, false
        autoCrop: true, //是否默认生成截图框:true, false
        autoCropWidth: '560', //默认生成截图框宽度:默认容器的80%,可选值0 ~ max
        autoCropHeight: '315', //默认生成截图框高度:默认容器的80%,可选值0 ~ max
        fixed: false, //是否开启截图框宽高固定比例:true, false;开启后,若配置了autoCropWidth,autoCropHeight,截图框一个边会以其中的较长边为准,另一个边根据比例自适应
        fixedNumber: [1, 1], //截图框的宽高比例:[ 宽度 ,  高度 ]
        full: false, //是否输出原图比例的截图:true, false
        fixedBox: false, //固定截图框大小:	true, false
        canMove: true, //上传图片是否可以移动:true, false
        canMoveBox: true, //截图框能否拖动:true, false
        original: false, //上传图片按照原始比例渲染:true, false
        centerBox: true, //截图框是否被限制在图片里面:true, false
        high: true, //是否按照设备的dpr 输出等比例图片:true, false
        infoTrue: false, //true为展示真实输出图片宽高 false为展示看到的截图框宽高:true, false
        maxImgSize: 2000, //限制图片最大宽度和高度:0 ~ max
        enlarge: 1, //图片根据截图框输出比例倍数:0 ~ max(不要太大不然会卡死)
        mode: 'contain', //图片默认渲染方式:contain , cover, 100px, 100% auto
        limitMinSize: 10, //裁剪框限制最小区域:Number, Array, String
        fillColor: '', //导出时背景颜色填充:#ffffff, white
      },
    });

    //弹出裁切弹窗
    function showCropperDialog() {
      Object.assign(state.cropperConfig, props.config);
      if (state.cropperConfig.file) {
        fileToBase64(state.cropperConfig.file).then((res) => {
          state.cropperConfig.img = res.data;
          nextTick(() => {
            state.cropperDialog = true;
          });
        });
      } else if (state.cropperConfig.img) {
        nextTick(() => {
          state.cropperDialog = true;
        });
      } else {
        showToast('图片地址项 cropperConfig.img 未配置!');
      }
    }
    //关闭裁切弹窗
    function hideCropperDialog() {
      state.cropperDialog = false;
    }

    //用户主动取消裁切
    function cancelCropper() {
      emit('cancelCropper');
      hideCropperDialog();
    }

    //用户确认原图
    function clickOriginImgBtn() {
      emit('originImgDone');
      hideCropperDialog();
    }

    //用户确认裁切图
    function clickCropperImgBtn() {
      let name = state.cropperConfig?.fileName ?? randomNum(10000, 1000000);
      state.cropperRef.getCropBlob((data) => {
        blobToFile(data, name).then((res) => {
          emit('cropperImgDone', res.data);
          hideCropperDialog();
        });
      });
    }

    return {
      showCropperDialog,
      cancelCropper,
      clickOriginImgBtn,
      clickCropperImgBtn,
      ...toRefs(state),
    };
  },
};
</script>

<style scoped lang="less">
.img-cropper-container {
  .img-cropper-content {
  }
  .dialog-content {
    :deep(.el-dialog) {
      .el-dialog__header {
        display: none;
      }
      .el-dialog__body {
        padding: 0;
      }
      background-color: transparent;
    }
    .every-common-dialog {
      background-color: #fff;
      border-radius: 4px;
      overflow: hidden;
      .commonpop-top {
        display: flex;
        border-bottom: 1px solid #eee;
        .poptop-title {
          flex: 1;
          height: 50px;
          line-height: 50px;
          text-align: center;
          margin-left: 50px;
          font-size: 18px;
          color: #333;
        }
        .poptop-cancel {
          flex-shrink: 0;
          width: 50px;
          height: 50px;
          line-height: 50px;
          text-align: center;
          color: #999;
          cursor: pointer;
          transition: all 0.2s;
          .iconfont {
            font-size: 20px;
          }
          &:hover {
            background-color: #f5f5f5;
            color: @tc-main;
          }
        }
      }
      .commonpop-content {
        padding: 20px 20px;
        box-sizing: border-box;
        .cc-cropper-content {
          width: 100%;
          height: 315px;
        }
      }
      .commonpop-footer {
        display: flex;
        padding: 0 20px 20px;
        box-sizing: border-box;
        .common-cancel-btn,
        .common-submit-btn {
          flex: 1;
          height: 50px;
          line-height: 50px;
          text-align: center;
          font-size: 16px;
          border-radius: 4px;
          &:nth-child(n + 2) {
            margin-left: 20px;
          }
        }
        .common-cancel-btn {
          background-color: #e7efff;
          color: @tc-main;
          cursor: pointer;
          transition: all 0.2s;
          &:hover {
            box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
          }
        }
        .common-submit-btn {
          color: #fff;
          background-color: @tc-main;
          cursor: pointer;
          transition: all 0.2s;
          &:hover {
            box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
          }
        }
      }
    }
  }
}
</style>

如何调用裁切组件

<img-cropper
    ref="imgCropperRef"
    :config="cropperConfig"
    @originImgDone="originImgDone"
    @cropperImgDone="cropperImgDone"
    @cancelCropper="cancelCropper"
/>

const state = reactive({
    nowFileInfo: null,  //当前选中的图片文件信息

    //图片裁切相关
    imgCropperRef: null, //图片裁切实例
    cropperConfig: { //图片裁切配置
        file: '',
        fileName: '',
        fixed: true,
        fixedNumber: [16, 9],
    },
})

//调用裁切组件
function handleCropper(fileInfo){  
    state.nowFileInfo = fileInfo;
    state.cropperConfig.file = fileInfo;  //将读取到的file信息传递给裁切组件
    state.cropperConfig.fileName = fileInfo?.name ?? '';  //为裁切组件图片命名
    state.imgCropperRef.showCropperDialog();  //父组件中获取裁切组件实例,并调用裁切组件的弹窗方法
}
//用户确认原图上传事件
function originImgDone() {
    state.isUploading = true;
    uploadFile(state.nowFileInfo);  //调用接口将图片上传至服务器
}
//用户确认裁切图上传事件
function cropperImgDone(fileInfo) {
    state.nowFileInfo = fileInfo;
    state.isUploading = true;
    uploadFile(state.nowFileInfo);  //调用接口将图片上传至服务器
}
//用户取消裁切事件
function cancelCropper() {
    state.isUploading = false;
}
   
posted @ 2023-08-30 15:21  huihuihero  阅读(1295)  评论(0编辑  收藏  举报