Vben Cropper 图片裁剪
VCropper 是一个纯原生实现的图片裁剪组件,支持自由比例和固定比例裁剪,可通过方法调用获取裁剪后的图片。
如果文档内没有参数说明,可以尝试在在线示例内寻找
写在前面
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
基础用法
最基本的图片裁剪,支持自由比例调整。
vue
<script lang="ts" setup>
import { onBeforeUnmount, ref } from 'vue';
import { VCropper } from '@vben/common-ui';
const cropperRef = ref<InstanceType<typeof VCropper>>();
const imageUrl = ref('https://picsum.photos/seed/cropper-demo/800/600');
const croppedImage = ref('');
// 释放旧的 object URL 以避免内存泄漏
const revokeCroppedImage = () => {
if (croppedImage.value?.startsWith('blob:')) {
URL.revokeObjectURL(croppedImage.value);
}
};
const handleCrop = async () => {
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
if (blob instanceof Blob) {
// 释放旧的 URL
revokeCroppedImage();
croppedImage.value = URL.createObjectURL(blob);
}
};
const handleReset = () => {
// 释放 URL
revokeCroppedImage();
croppedImage.value = '';
// 重新加载图片以重置裁剪框
imageUrl.value = `https://picsum.photos/seed/cropper-${Date.now()}/800/600`;
};
// 组件卸载时清理
onBeforeUnmount(() => {
revokeCroppedImage();
});
</script>
<template>
<div>
<VCropper ref="cropperRef" :img="imageUrl" :width="500" :height="300" />
<div class="mt-4 flex gap-2">
<button
class="px-4 py-2 bg-blue-500 rounded hover:bg-blue-600"
@click="handleCrop"
>
裁剪图片
</button>
<button
class="px-4 py-2 bg-gray-500 rounded hover:bg-gray-600"
@click="handleReset"
>
重置
</button>
</div>
<div v-if="croppedImage" class="mt-4">
<p class="text-sm text-gray-500 mb-2">裁剪结果:</p>
<img :src="croppedImage" class="max-w-full rounded border" />
</div>
<div class="mt-4">
<p class="text-sm text-gray-500">提示:</p>
<ul class="mt-2 text-xs text-gray-400 list-disc pl-4">
<li>拖拽裁剪框中心区域可移动裁剪位置</li>
<li>拖拽四角或四边可调整裁剪框大小</li>
<li>默认为自由比例,可调整为任意比例</li>
</ul>
</div>
</div>
</template>固定比例裁剪
通过 aspectRatio 属性设置裁剪比例,格式为 "宽:高",如 "1:1"、"16:9"、"3:4" 等。
vue
<script lang="ts" setup>
import { onBeforeUnmount, ref } from 'vue';
import { VCropper } from '@vben/common-ui';
const cropperRef = ref<InstanceType<typeof VCropper>>();
const aspectRatio = ref('1:1');
const imageUrl = ref('https://picsum.photos/seed/cropper-ratio/800/600');
const croppedImage = ref('');
const aspectOptions = [
{ label: '1:1 (正方形)', value: '1:1' },
{ label: '16:9 (宽屏)', value: '16:9' },
{ label: '4:3 (标准)', value: '4:3' },
{ label: '3:4 (竖版)', value: '3:4' },
{ label: '3:2 (照片)', value: '3:2' },
];
// 释放旧的 object URL 以避免内存泄漏
const revokeCroppedImage = () => {
if (croppedImage.value?.startsWith('blob:')) {
URL.revokeObjectURL(croppedImage.value);
}
};
const handleCrop = async () => {
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
if (blob instanceof Blob) {
// 释放旧的 URL
revokeCroppedImage();
croppedImage.value = URL.createObjectURL(blob);
}
};
const handleReset = () => {
// 释放 URL
revokeCroppedImage();
croppedImage.value = '';
imageUrl.value = `https://picsum.photos/seed/cropper-${Date.now()}/800/600`;
};
// 组件卸载时清理
onBeforeUnmount(() => {
revokeCroppedImage();
});
</script>
<template>
<div>
<div class="mb-4">
<label class="text-sm text-gray-500 mr-2">选择比例:</label>
<select v-model="aspectRatio" class="px-3 py-1 border rounded text-sm">
<option
v-for="option in aspectOptions"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</div>
<VCropper
ref="cropperRef"
:img="imageUrl"
:width="500"
:height="300"
:aspect-ratio="aspectRatio"
/>
<div class="mt-4 flex gap-2">
<button
class="px-4 py-2 bg-blue-500 rounded hover:bg-blue-600"
@click="handleCrop"
>
裁剪图片
</button>
<button
class="px-4 py-2 bg-gray-500 rounded hover:bg-gray-600"
@click="handleReset"
>
重置
</button>
</div>
<div v-if="croppedImage" class="mt-4">
<p class="text-sm text-gray-500 mb-2">
裁剪结果 (比例: {{ aspectRatio }}):
</p>
<img :src="croppedImage" class="max-w-full rounded border" />
</div>
<div class="mt-4">
<p class="text-sm text-gray-500">提示:</p>
<ul class="mt-2 text-xs text-gray-400 list-disc pl-4">
<li>设置固定比例后,裁剪框始终维持该比例</li>
<li>切换比例会自动重新计算裁剪框大小</li>
<li>比例格式为 "宽:高",如 "16:9"</li>
</ul>
</div>
</div>
</template>API
Props
| 属性名 | 描述 | 类型 | 默认值 |
|---|---|---|---|
img | 图片地址(必填) | string | - |
width | 容器宽度 | number | 500 |
height | 容器高度 | number | 400 |
aspectRatio | 裁剪比例,格式如 "1:1"、"16:9" 等 | string | - |
Methods
通过 ref 调用组件方法:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { VCropper } from '@vben/common-ui';
const cropperRef = ref<InstanceType<typeof VCropper>>();
const handleCrop = async () => {
const result = await cropperRef.value?.getCropImage();
// result 为 Blob 或 base64 字符串
};
</script>getCropImage
裁剪并获取图片。
ts
interface GetCropImageOptions {
/** 输出图片格式 */
format?: 'image/jpeg' | 'image/png';
/** 压缩质量(0-1),仅对 jpeg 格式有效 */
quality?: number;
/** 输出类型 */
outputType?: 'base64' | 'blob';
/** 目标宽度(可选,不传则为原始裁剪宽度) */
targetWidth?: number;
/** 目标高度(可选,不传则为原始裁剪高度) */
targetHeight?: number;
}
getCropImage(
format?: 'image/jpeg' | 'image/png',
quality?: number,
outputType?: 'base64' | 'blob',
targetWidth?: number,
targetHeight?: number,
): Promise<Blob | string | undefined>参数说明:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
format | 'image/jpeg' | 'image/png' | 'image/png' | 输出图片格式 |
quality | number | 0.92 | 压缩质量(0-1),仅 jpeg 有效 |
outputType | 'base64' | 'blob' | 'blob' | 输出类型,base64 字符串或 Blob 对象 |
targetWidth | number | - | 目标宽度,不传则使用原始裁剪宽度 |
targetHeight | number | - | 目标高度,不传则使用原始裁剪高度 |
功能特性
裁剪操作
- 拖拽移动 - 拖拽裁剪框中心区域移动裁剪位置
- 边角调整 - 拖拽四角调整裁剪框大小
- 边缘调整 - 拖拽四边中点调整单边
比例控制
- 自由比例 - 不设置
aspectRatio时,可自由调整任意比例 - 固定比例 - 设置
aspectRatio后,裁剪框始终保持设定比例
高清屏适配
组件自动适配 Retina 等高清屏幕,保证输出图片清晰无模糊。
图片适配
- 图片自动等比缩放以完整显示在容器内
- 支持本地图片和网络图片
- 网络图片需目标服务端支持 CORS 才能导出裁剪结果
使用示例
vue
<script setup lang="ts">
import { ref } from 'vue';
import { VCropper } from '@vben/common-ui';
const cropperRef = ref<InstanceType<typeof VCropper>>();
const imageUrl = ref('https://example.com/image.jpg');
const croppedImage = ref('');
// 获取裁剪后的 Blob 对象
const handleCropBlob = async () => {
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
if (blob instanceof Blob) {
// 上传到服务器或创建预览URL
const url = URL.createObjectURL(blob);
croppedImage.value = url;
}
};
// 获取裁剪后的 base64 字符串
const handleCropBase64 = async () => {
const base64 = await cropperRef.value?.getCropImage('image/png', 1, 'base64');
if (typeof base64 === 'string') {
croppedImage.value = base64;
}
};
// 导出指定尺寸
const handleCropWithSize = async () => {
const blob = await cropperRef.value?.getCropImage(
'image/jpeg',
0.9,
'blob',
200, // 目标宽度
200, // 目标高度
);
};
</script>
<template>
<div>
<VCropper
ref="cropperRef"
:img="imageUrl"
:width="500"
:height="400"
aspect-ratio="1:1"
/>
<button @click="handleCropBlob">裁剪</button>
<img v-if="croppedImage" :src="croppedImage" />
</div>
</template>
JyQAQ