Vben Cropper Image Cropping
VCropper is a pure native image cropping component that supports both free and fixed aspect ratio cropping, with method-based access to cropped results.
If some details are not obvious from the docs, check the live demos as well.
Note
If you feel the current component implementation doesn't meet your needs, you can use native components directly or create your own component. The components provided by the framework are not constraints - use them at your discretion.
Basic Usage
Basic image cropping with free aspect ratio adjustment.
<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>Fixed Aspect Ratio
Set the cropping ratio via the aspectRatio prop. The format is "width:height", e.g. "1:1", "16:9", "3:4".
<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
| Property | Description | Type | Default |
|---|---|---|---|
img | Image URL (required) | string | - |
width | Container width | number | 500 |
height | Container height | number | 400 |
aspectRatio | Crop ratio, e.g. "1:1", "16:9" | string | - |
Methods
Call component methods via ref:
<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 is a Blob or base64 string
};
</script>getCropImage
Crop and retrieve the image.
getCropImage(
format?: 'image/jpeg' | 'image/png',
quality?: number,
outputType?: 'base64' | 'blob',
targetWidth?: number,
targetHeight?: number,
): Promise<Blob | string | undefined>Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
format | 'image/jpeg' | 'image/png' | 'image/png' | Output image format |
quality | number | 0.92 | Compression quality (0-1), only effective for jpeg |
outputType | 'base64' | 'blob' | 'blob' | Output type, base64 string or Blob object |
targetWidth | number | - | Target width, defaults to original crop width if omitted |
targetHeight | number | - | Target height, defaults to original crop height if omitted |
Features
Cropping Operations
- Drag to Move - Drag the center area of the crop box to move its position
- Corner Resize - Drag the four corners to resize the crop box
- Edge Resize - Drag the midpoints of edges to adjust a single side
Aspect Ratio Control
- Free Ratio - Without
aspectRatio, adjust the crop box to any ratio - Fixed Ratio - With
aspectRatioset, the crop box maintains the specified ratio
HiDPI Support
The component automatically adapts to Retina and other high-DPI screens, ensuring crisp output images.
Image Fitting
- Images are automatically scaled to fit within the container
- Supports both local and remote images
- Remote images require CORS support from the server to export cropped results
Usage Example
<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('');
// Get cropped Blob
const handleCropBlob = async () => {
const blob = await cropperRef.value?.getCropImage('image/jpeg', 0.9, 'blob');
if (blob instanceof Blob) {
// Upload to server or create preview URL
const url = URL.createObjectURL(blob);
croppedImage.value = url;
}
};
// Get cropped base64 string
const handleCropBase64 = async () => {
const base64 = await cropperRef.value?.getCropImage('image/png', 1, 'base64');
if (typeof base64 === 'string') {
croppedImage.value = base64;
}
};
// Export with specific dimensions
const handleCropWithSize = async () => {
const blob = await cropperRef.value?.getCropImage(
'image/jpeg',
0.9,
'blob',
200, // target width
200, // target height
);
};
</script>
<template>
<div>
<VCropper
ref="cropperRef"
:img="imageUrl"
:width="500"
:height="400"
aspect-ratio="1:1"
/>
<button @click="handleCropBlob">Crop</button>
<img v-if="croppedImage" :src="croppedImage" />
</div>
</template>
JyQAQ