图片裁剪使用说明文档

概述

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

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

  • 导出组件
  • CropperImage: 基础裁剪核心组件

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

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

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

@/components/Cropper 引入:

javascript 复制代码
import { CropperImage, CropperAvatar, CropperImg } from '@/components/Cropper'

快速开始

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

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

xml 复制代码
<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,适用于需要自定义触发/自定义外观的页面。

xml 复制代码
<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 实时获取裁剪结果。

xml 复制代码
<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 显示占位/预览,并通过事件让外部打开裁剪弹窗或路由到裁剪页。

ini 复制代码
<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,更新图片资料并关闭裁剪弹窗。

简化示例:

javascript 复制代码
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才有效

javascript 复制代码
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有父组件传过来
    props.size > 100 ? 'image/jpeg' : 'image/png',
    props.size / 1000
  )
}

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

ini 复制代码
// 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)
  if (props.size > 100) {
    // 将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)
  }
  context.globalCompositeOperation = 'destination-in'
  context.beginPath()
  context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true)
  context.fill()
  return canvas
}
  • 圆形裁剪circled=true 时自动生成圆形输出,透明背景会处理为白色(在 jpeg 场景)。

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

相关推荐
武昌库里写JAVA2 小时前
使用 Java 开发 Android 应用:Kotlin 与 Java 的混合编程
java·vue.js·spring boot·sql·学习
HANK2 小时前
ECharts高效实现复杂图表指南
前端·vue.js
Juchecar2 小时前
Vue3 响应式 ref 和 reactive 原理详解及选择建议
前端·vue.js
zuo-yiran2 小时前
element table 表格多选框选中高亮
vue.js·elementui
Aotman_2 小时前
el-input 重写带图标密码框(点击小眼睛显示、隐藏密码)
前端·javascript·vue.js
lineo_2 小时前
手写 pinia 持久化缓存插件,兼容indexDB
前端·vue.js
王林不想说话2 小时前
新的枚举使用方式enum-plus
前端·vue.js·typescript
java水泥工3 小时前
Java项目:基于SpringBoot和VUE的在线拍卖系统(源码+数据库+文档)
java·vue.js·spring boot
Jinuss3 小时前
Vue3源码reactivity响应式篇之Map、Set等代理处理详解
前端·vue.js·vue3