图片裁剪使用说明文档

306 阅读5分钟

 概述

本文档介绍如何在vue3+ts的芋道源码后台管理项目中使用基于 cropperjs 的图片裁剪能力,封装为以下组件,满足头像裁剪、图片裁剪预览、弹窗裁剪、配合上传等场景。

图片要压缩质量,必须使用image/jpeg才有效,使用image/jpeg后,图片透明色会变成黑色,所以需要变白处理。

  • 导出组件

  - CropperImage: 基础裁剪核心组件

  - CropperAvatar: 头像裁剪 + 弹窗选择上传的完整流程

  - CropperImg: 简易图片占位/预览卡片,触发外部裁剪逻辑

  - CopperModal: 内部使用的裁剪弹窗(可独立使用)

@/components/Cropper 引入:

import { CropperImage, CropperAvatar, CropperImg } from '@/components/Cropper'

快速开始

场景一:用户头像裁剪(推荐)

内置完整流程:选择图片 → 弹窗裁剪 → 确认后返回裁剪结果(二进制 Blob 与 base64)。

<template>

  <CropperAvatar

    :value="avatarUrl"

    :showBtn="true"

    btnText="更换头像"

    @change="onChange"

    ref="avatarRef"

  />

  <!-- 也可配合 v-model:value 使用 -->

</template>



<script setup lang="ts">

import { ref } from 'vue'

import { CropperAvatar } from '@/components/Cropper'



const avatarRef = ref()

const avatarUrl = ref('')



// { source: base64, data: Blob, filename: string }

const onChange = async ({ data, source, filename }) => {

  // 1) 将 Blob 上传到后端

  // 2) 服务端返回可访问 URL,更新本地头像

  // 3) 可调用 avatarRef.value.close() 关闭弹窗

}

</script>

参考项目实际上传逻辑可看 src/views/Profile/components/UserAvatar.vue 中的 handelUpload:使用 useUpload().httpRequestdata 作为文件上传,上传成功后更新用户资料并关闭弹窗。

场景二:手动控制裁剪弹窗(更灵活)

直接使用裁剪弹窗 CopperModal,适用于需要自定义触发/自定义外观的页面。

<template>

  <el-button type="primary" @click="open">上传并裁剪</el-button>

  <CopperModal ref="modalRef" :circled="true" @upload-success="onUploadSuccess" />

</template>



<script setup lang="ts">

import { ref } from 'vue'

import CopperModal from '@/components/Cropper/src/CopperModal.vue'



const modalRef = ref()



const open = () => {

  modalRef.value.openModal()

}



const onUploadSuccess = ({ source, data, filename }) => {

  // source: 裁剪后 base64

  // data:   裁剪后 Blob,可直接上传

  // filename: 原始文件名

}

</script>
  • 弹窗内置能力:选择图片、预览、重置、旋转、翻转、缩放、圆形/方形裁剪等。

  • 重要事件uploadSuccess 返回裁剪结果,业务方仅需上传 data 到后端

场景三:页面内嵌裁剪器(无弹窗)

在页面内直接使用 CropperImage,监听 cropend 实时获取裁剪结果。

<template>

  <CropperImage

    :src="imgSrc"

    :circled="false"

    height="360px"

    :size="500"

    :options="options"

    @ready="onReady"

    @cropend="onCropEnd"

    @cropendError="onError"

  />

  <!-- size>100 输出 jpeg 且质量为 size/1000;否则为 png -->

</template>



<script setup lang="ts">

import { ref } from 'vue'

import type Cropper from 'cropperjs'

import { CropperImage } from '@/components/Cropper'



const imgSrc = ref('https://example.com/demo.jpg')

const options: Cropper.Options = { aspectRatio: 16 / 9 }

const onReady = (cropper: Cropper) => { /* 可保存实例以便手动调用 API */ }

const onCropEnd = ({ imgBase64, imgInfo }) => {

  // imgBase64: 裁剪结果

  // imgInfo:   裁剪数据(x,y,width,height,rotate,scaleX,scaleY...)

}

const onError = () => { /* 处理错误 */ }

</script>

场景四:占位/预览卡片触发裁剪

使用 CropperImg 显示占位/预览,并通过事件让外部打开裁剪弹窗或路由到裁剪页。

<template>

  <CropperImg

    :modelValue="img"

    :height="'150px'"

    :width="'150px'"

    :showDelete="true"

    @cropperImg="openCropModal"

    @deleteImg="onDelete"

  />

</template>



<script setup lang="ts">

import { ref } from 'vue'

import { CropperImg } from '@/components/Cropper'



const img = ref('')

const openCropModal = () => { /* 打开 CopperModal 或跳转到裁剪页面 */ }

const onDelete = () => { img.value = '' }

</script>

组件 API

CropperImage

  • props

  - src: string 图片地址

  - alt: string

  - circled: boolean 是否圆形裁剪,默认 false

  - realTimePreview: boolean 是否实时回调 cropend,默认 true

  - height: string 组件高度,默认 360px

  - crossorigin: '' | 'anonymous' | 'use-credentials' | undefined

  - imageStyle: CSSProperties 外层 img 的样式

  - options: Cropper.Options 透传 cropperjs 配置

  - size: number 输出质量控制;>100 使用 image/jpeg 且质量为 size/1000

  • emits

  - ready(cropper: Cropper) 裁剪器实例已就绪

  - cropend({ imgBase64, imgInfo }) 裁剪结束/变更回调

  - cropendError 生成 base64 失败

  • 说明

  - 当 circled=true,组件内部会将透明背景转白(当 size>100),并以圆形遮罩输出。

CroppperAvatar

  • props

  - width: string 显示宽度,默认 200px

  - value: string 头像地址

  - showBtn: boolean 是否显示按钮,默认 true

  - btnText: string 按钮文案

  • emits

  - update:value 用于 v-model:value

  - change({ source, data, filename }) 裁剪完成,返回 base64、Blob、文件名

  • expose

  - open() 打开弹窗

  - close() 关闭弹窗

CopperModal(裁剪弹窗)

  • props

  - srcValue: string 进入弹窗时的默认图片

  - circled: boolean 是否圆形裁剪,默认 true

  - title: string 弹窗标题

  - fileSize: number 选择图片大小限制(MB),默认 5

  - fileType: string[] 允许类型,默认 ['image/jpeg','image/png','image/gif']

  • emits

  - uploadSuccess({ source, data, filename }) 点击“确定”时回调

  • expose

  - openModal()closeModal()

CropperImg(占位/预览卡片)

  • props

  - modelValue: string 图片地址

  - disabled: boolean 是否禁用,默认 false

  - height: string 默认 150px

  - width: string 默认 150px

  - borderradius: string 默认 8px

  - showDelete: boolean 默认 true

  - showBtnText: boolean 默认 true

  • emits

  - cropperImg() 点击添加/编辑时触发

  - deleteImg() 删除时触发

上传接口对接(示例)

  - 在 @change 回调中,拿到 data: Blob,通过自有上传方法 useUpload().httpRequest 发送到后端;

  - 成功后返回 URL,更新图片资料并关闭裁剪弹窗。

简化示例:

const onChange = async ({ data }) => {

  // 1) 通过接口上传 Blob

  // const { data: url } = await httpRequest({ file: data, filename: 'avatar.png' })

  // 2) 保存到图片资料

  // await updateUserProfile({ avatar: url })

  // 3) 关闭弹窗

}

常见问题与技巧

  • 图片跨域:远程图片需要支持跨域访问,可设置 crossorigin 或确保图片服务器正确的 CORS。

  • 输出格式与质量:通过文件Size控制。文件Size > 100image/jpeg 且质量为 文件Size/1000;否则输出 image/png

图片要压缩质量,必须使用image/jpeg才有效,当上传的图片为jpeg格式,只能用image/jpeg,否则将会变大

function croppered() {
  if (!cropper.value) {
    return
  }
  let imgInfo = cropper.value.getData()
  const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas()
  canvas.toBlob(
    (blob) => {
      if (!blob) {
        return
      }
      let fileReader: FileReader = new FileReader()
      fileReader.readAsDataURL(blob)
      fileReader.onloadend = (e) => {
        emit('cropend', {
          imgBase64: e.target?.result ?? '',
          imgInfo
        })
      }
      fileReader.onerror = () => {
        emit('cropendError')
      }
    },
     // 文件Size由父组件传过来
    pprops.size > 100 ? 'image/jpeg' : props.type === 'image/jpeg' ? 'image/jpeg' : 'image/png',
    props.size > 100 ? props.size / 1000 : 0.5
  )
}

使用image/jpeg后,图片透明色会变成黑色,所以需要变白处理

// Get a circular picture canvas
function getRoundedCanvas() {
  const sourceCanvas = cropper.value!.getCroppedCanvas()
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')!
  const width = sourceCanvas.width
  const height = sourceCanvas.height
  canvas.width = width
  canvas.height = height
  context.imageSmoothingEnabled = true
  context.drawImage(sourceCanvas, 0, 0, width, height)
  context.globalCompositeOperation = 'destination-in'
  context.beginPath()
  context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true)
  context.fill()
   if (props.size > 100 || props.type === 'image/jpeg') {
    // 将canvas的透明背景设置成白色
    var imageData = context.getImageData(0, 0, canvas.width, canvas.height)
    for (var i = 0; i < imageData.data.length; i += 4) {
      // 当该像素是透明的,则设置成白色
      if (imageData.data[i + 3] == 0) {
        imageData.data[i] = 255
        imageData.data[i + 1] = 255
        imageData.data[i + 2] = 255
        imageData.data[i + 3] = 255
      }
    }
    context.putImageData(imageData, 0, 0)
  }
  return canvas
}
  • 圆形裁剪circled=true 时自动生成圆形输出,透明背景会处理为白色(在 jpeg 场景)。

  • 高级控制:通过 CropperImage@ready 获取 cropper 实例,调用 cropper.rotate/zoom/scaleX/scaleY/reset 等方法。