vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue Cropper裁剪

在Vue3项目中使用VueCropper裁剪组件实现裁剪及预览效果

作者:kk楷楷

这篇文章主要介绍了在Vue3项目中使用VueCropper裁剪组件(裁剪及预览效果),本文分步骤结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

前言

某次看到后台系统中使用到了裁剪组件,感觉挺好玩的并且最近也在学Vue3和Ts,所以就研究了VueCropper组件,封装了裁剪组件,效果如下图。

在这里插入图片描述

在这里插入图片描述

一、使用步骤

1.安装库

npm i vue-cropper --save

2.引入库

代码如下(示例):

import ‘vue-cropper/dist/index.css'
import { VueCropper } from ‘vue-cropper'

3.在component文件夹中新建一个裁剪Vue文件

TipsDialog是我自己封装的dialog组件,可以替换成el-dialog

<template>
  <div>
    <input
      ref="reuploadInput"
      type="file"
      accept="image/*"
      @change="onChange"
      id="fileBtn"
      style="display: none"
    >
    <TipsDialog
      :visible="dialogVisible"
      :title="'图片裁剪'"
      :width="'40%'"
      :custom-class="'upload_dialog'"
      @close="dialogVisible = false"
    >
     // 核心内容
      <template #default>
        <div class="cropper">
        // 裁剪左侧内容
          <div class="cropper_left">
            <vueCropper
              :tyle="{ width: '400px'}"
              ref="cropperRef"
              :img="options.img"
              :info="true"
              :info-true="options.infoTrue"
              :auto-crop="options.autoCrop"
              :fixed-box="options.fixedBox"
              :can-move="options.canMoveBox"
              :can-scale="options.canScale"
              :fixed-number="fixedNumber"
              :fixed="options.fixed"
              :full="options.full"
              :center-box="options.centerBox"
              @real-time="previewHandle"
            />
            <div class="reupload_box">
              <div
                class="reupload_text"
                @click="uploadFile('reload')"
              >
                重新上传
              </div>
              <div>
                <el-icon
                  class="rotate_right"
                  @click="changeScale(1)"
                >
                  <CirclePlus />
                </el-icon>
                <el-icon
                  class="rotate_right"
                  @click="changeScale(-1)"
                >
                  <Remove />
                </el-icon>
                <el-icon
                  class="rotate_right"
                  @click="rotateRight"
                >
                  <RefreshRight />
                </el-icon>
              </div>
            </div>
          </div>
          <div class="cropper_right">
            <div class="preview_text">
              预览
            </div>
            <div
              :style="getStyle"
              class="previewImg"
            >
              <div :style="previewFileStyle">
                <img
                  :style="previews.img"
                  :src="previews.url"
                  alt=""
                >
              </div>
            </div>
          </div>
        </div>
      </template>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button
            type=""
            @click="refreshCrop"
          >重置</el-button>
          <el-button
            type="primary"
            @click="onConfirm"
          > 确认 </el-button>
        </span>
      </template>
    </TipsDialog>
  </div>
</template>
<script lang="ts" setup>
// 需要引入的库
import 'vue-cropper/dist/index.css'
import { VueCropper } from 'vue-cropper'
import { ref, watch, reactive } from 'vue'
import TipsDialog from '~/components/TipsDialog/TipsDialog.vue' // 封装的dialog组件
import { ElMessage } from 'element-plus'
import { commonApi } from '../../api' // 封装的api
const dialogVisible = ref<boolean>(false) // dialog的显示与隐藏
const emits = defineEmits(['confirm']) // 自定义事件
// 裁剪组件需要使用到的参数
interface Options {
  img: string | ArrayBuffer | null // 裁剪图片的地址
  info: true // 裁剪框的大小信息
  outputSize: number // 裁剪生成图片的质量 [1至0.1]
  outputType: string // 裁剪生成图片的格式
  canScale: boolean // 图片是否允许滚轮缩放
  autoCrop: boolean // 是否默认生成截图框
  autoCropWidth: number // 默认生成截图框宽度
  autoCropHeight: number // 默认生成截图框高度
  fixedBox: boolean // 固定截图框大小 不允许改变
  fixed: boolean // 是否开启截图框宽高固定比例
  fixedNumber: Array<number> // 截图框的宽高比例  需要配合centerBox一起使用才能生效
  full: boolean // 是否输出原图比例的截图
  canMoveBox: boolean // 截图框能否拖动
  original: boolean // 上传图片按照原始比例渲染
  centerBox: boolean // 截图框是否被限制在图片里面
  infoTrue: boolean // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
  accept: string // 上传允许的格式
}
// 父组件传参props
interface IProps {
  type: string // 上传类型, 企业logo / 浏览器logo
  allowTypeList: string[] // 接收允许上传的图片类型
  limitSize: number // 限制大小
  fixedNumber: number[] // 截图框的宽高比例
  fixedNumberAider?: number[] // 侧边栏收起截图框的宽高比例
  previewWidth: number // 预览宽度
  title?: string // 裁剪标题
}
// 预览样式
interface IStyle {
  width: number | string,
  height: number | string
}
/* 父组件传参 */
const props = withDefaults(defineProps<IProps>(), {
  type: 'systemLogo',
  allowTypeList: () => ['jpg', 'png', 'jpeg'],
  limitSize: 1,
  fixedNumber: () => [1, 1],
  fixedNumberAider: () => [1, 1],
  previewWidth: 228,
  title: 'LOGO裁剪'
})
// 裁剪组件需要使用到的参数
const options = reactive<Options>({
  img: '', // 需要剪裁的图片
  autoCrop: true, // 是否默认生成截图框
  autoCropWidth: 150, // 默认生成截图框的宽度
  autoCropHeight: 150, // 默认生成截图框的长度
  fixedBox: false, // 是否固定截图框的大小 不允许改变
  info: true, // 裁剪框的大小信息
  outputSize: 1, // 裁剪生成图片的质量 [1至0.1]
  outputType: 'png', // 裁剪生成图片的格式
  canScale: true, // 图片是否允许滚轮缩放
  fixed: true, // 是否开启截图框宽高固定比例
  fixedNumber: [1, 1], // 截图框的宽高比例 需要配合centerBox一起使用才能生效 1比1
  full: true, // 是否输出原图比例的截图
  canMoveBox: false, // 截图框能否拖动
  original: false, // 上传图片按照原始比例渲染
  centerBox: true, // 截图框是否被限制在图片里面
  infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
  accept: 'image/jpeg,image/jpg,image/png,image/gif,image/x-icon'
})
const getStyle = ref<IStyle>({
  width: '',
  height: ''
})
/* 允许上传的类型 */
const acceptType = ref<string[]>([])
// 裁剪后的预览样式信息
const previews: any = ref({})
const previewFileStyle = ref({})
// 裁剪组件Ref
const cropperRef: any = ref({})
// input组件Ref
const reuploadInput = ref<HTMLElement | null | undefined>()
// 回显图片使用的方法
const onChange = (e: any) => {
  const file = e.target.files[0]
  const URL = window.URL || window.webkitURL
  // 上传图片前置钩子,用于判断限制类型用
  if (beforeUploadEvent(file)) {
    options.img = URL.createObjectURL(file)
    dialogVisible.value = true
  }
}
/* 上传图片前置拦截函数 */
const beforeUploadEvent = (file: File) => {
  const type = file.name.substring(file.name.lastIndexOf('.') + 1) // 获得图片上传后缀
  // 判断是否符合上传类型
  const isAllowTye = props.allowTypeList.some(item => {
    return item === type
  })
  if (!isAllowTye) {
    ElMessage.error(`仅支持${acceptType.value.join('、')}格式的图片`)
    return false
  }
  return true
}
/* 重置裁剪组件 */
const refreshCrop = () => {
  // cropperRef裁剪组件自带很多方法,可以打印看看
  cropperRef.value.refresh()
}
/* 右旋转图片 */
const rotateRight = () => {
  cropperRef.value.rotateRight()
}
/* 放大缩小图片比例 */
const changeScale = (num: number) => {
  const scale = num || 1
  cropperRef.value.changeScale(scale)
}
// 缩放的格式
const tempScale = ref<number>(0)
// 点击上传
const uploadFile = (type: string): void => {
  /* 打开新的上传文件无需生成新的input元素 */
  if (type === 'reupload') {
    reuploadInput.value?.click()
    return
  }
  let input: HTMLInputElement | null = document.createElement('input')
  input.type = 'file'
  input.accept = options.accept
  input.onchange = onChange
  input.click()
  input = null
}
/* 上传成功方法 */
const cropperSuccess = async (dataFile: File) => {
  const fileFormData = new FormData()
  fileFormData.append('file', dataFile)
  // 在接口请求中需要上传file文件格式, 并且该接口需要改header头部为form-data格式
  const { code, data } = await commonApi.uploadFile(fileFormData)
  if (code.value === 200 && data.value) {
    return data.value
  }
  // 之前调用接口的方式
  // axios('http://localhost:3001/adminSystem/common/api/upload', {
  //   data: fileFormData,
  //   method: 'POST',
  //   headers: {
  //     'Content-Type': 'multipart/form-data'
  //   }
  // }).then(async (result: any) => {
  //   const res = await result
  //   console.log(res, 'res')
  // }).catch((err: any) => {
  //   console.log(err, 'err')
  // })
}
 // base64转图片文件
const dataURLtoFile = (dataUrl: string, filename: string) => {
  const arr = dataUrl.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bstr = atob(arr[1])
  let len = bstr.length
  const u8arr = new Uint8Array(len)
  while (len--) {
    u8arr[len] = bstr.charCodeAt(len)
  }
  return new File([u8arr], filename, { type: mime })
}
// 上传图片(点击保存按钮)
const onConfirm = () => {
  cropperRef.value.getCropData(async (data: string) => {
    const dataFile: File = dataURLtoFile(data, 'images.png')
    const res = await cropperSuccess(dataFile)
    // 触发自定义事件
    emits('confirm', res)
    return res
  })
  dialogVisible.value = false
}
// 裁剪之后的数据
const previewHandle = (data: any) => {
  previews.value = data // 预览img图片
  tempScale.value = props.previewWidth / data.w
  previewFileStyle.value = {
    width: data.w + 'px',
    height: data.h + 'px',
    margin: 0,
    overflow: 'hidden',
    zoom: tempScale.value,
    position: 'relative',
    border: '1px solid #e8e8e8',
    'border-radius': '2px'
  }
}
watch(
  () => props,
  () => {
    /* 预览样式 */
    getStyle.value = {
      width: props.previewWidth + 'px', // 预览宽度
      height: props.previewWidth / props.fixedNumber[0] + 'px' // 预览高度
    }
    // 上传格式tips信息
    acceptType.value = []
    for (let i = 0; i < props.allowTypeList.length; i++) {
      acceptType.value.push(props.allowTypeList[i].toUpperCase())
    }
  }, {
    deep: true
  }
)
/* 向子组件抛出上传事件 */
defineExpose({
  uploadFile
})
</script>
<style lang="scss" scoped>
.cropper {
  width: 100%;
  height: 50vh;
  display: flex;
  overflow: hidden;
  .cropper_left {
    display: flex;
    flex-direction: column;
    .reupload_box {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-top: 10px;
      .reupload_text {
        color: var(--primary-color);
        cursor: pointer;
      }
      .rotate_right {
        margin-left: 16px;
        cursor: pointer;
      }
    }
  }
  .cropper_right {
    flex: 1;
    margin-left: 44px;
    .preview_text {
      margin-bottom: 12px;
    }
  }
}
</style>

4.在父组件中使用(HTML)

// 引入裁剪组件
import clipperDialog from '~/components/clipperDialog/clipperDialog.vue'
HTML
<clipperDialog
  ref="clipperRef"
  :type="clipperData.type"
  :allow-type-list="clipperData.allowTypeList"
  :limit-size="clipperData.limitSize"
  :fixed-number="clipperData.fixedNumber"
  :fixed-number-aider="clipperData.fixedNumberAider"
  :preview-width="clipperData.previewWidth"
   @confirm="onConfirm"
/>

5.定义props传参(TS)

JS
// 定义interface类型
interface IClipper {
  type: string // 上传类型
  allowTypeList: string[] // 接收允许上传的图片类型
  limitSize: number // 限制大小
  fixedNumber: number[] // 截图框的宽高比例
  fixedNumberAider?: number[] // 侧边栏收起截图框的宽高比例
  previewWidth: number // 预览宽度
  previewWidthAider?: number // 侧边栏收起预览宽度
}
const clipperData = ref<IClipper>({
  type: '',
  allowTypeList: [],
  limitSize: 1,
  fixedNumber: [],
  previewWidth: 0
})

6.核心方法(TS)

/* 浏览器logo上传 */
const browserUpload = (): void => {
  clipperData.value = {
    type: 'browserLogo', // 该参数可根据实际要求修改类型
    allowTypeList: ['png', 'jpg', 'jpeg', 'peeee'], // 允许上传的图片格式
    limitSize: 1, // 限制的大小
    fixedNumber: [1, 1],  // 截图比例,可根据实际情况进行修改
    previewWidth: 100 // 预览宽度
  }
  // 打开裁剪组件
  clipperRef.value.uploadFile()
}
/* 保存logo自定义事件, 实际业务在此编写 */
const onConfirm = (val: any): void => {
  console.log(val, '点击保存按钮后的图片信息')
}

总结

以上就是我封装的裁剪组件,或许存在一些不足之处,还请大佬们多多指教!

到此这篇关于在Vue3项目中使用VueCropper裁剪组件(裁剪及预览效果)的文章就介绍到这了,更多相关Vue3使用VueCropper裁剪组件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文