docs(@vben/docs): 添加 VCropper 图片裁剪组件文档 (#7904)
* docs(@vben/docs): 添加 VCropper 图片裁剪组件文档 - 新增中文文档 docs/src/components/common-ui/vben-cropper.md - 新增英文文档 docs/src/en/components/common-ui/vben-cropper.md - 新增基础用法示例 demos/vben-cropper/basic - 新增固定比例裁剪示例 demos/vben-cropper/aspect-ratio - 更新侧边栏配置添加 Cropper 入口 * fix: 更正跨域图片描述并修复 demo 内存泄漏 - 文档更正:网络图片导出裁剪结果需服务端 CORS 支持 - 修复 URL.createObjectURL 内存泄漏:添加 revokeObjectURL 释放master^2
parent
ba60bc3c14
commit
aac4e88353
|
|
@ -198,6 +198,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||
link: 'common-ui/vben-ellipsis-text',
|
||||
text: 'EllipsisText',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-cropper',
|
||||
text: 'Cropper',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-tiptap',
|
||||
text: 'Tiptap RichTextEditor',
|
||||
|
|
|
|||
|
|
@ -196,6 +196,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||
link: 'common-ui/vben-ellipsis-text',
|
||||
text: 'EllipsisText 省略文本',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-cropper',
|
||||
text: 'Cropper 图片裁剪',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-tiptap',
|
||||
text: 'Tiptap 富文本编辑器',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben Cropper 图片裁剪
|
||||
|
||||
`VCropper` 是一个纯原生实现的图片裁剪组件,支持自由比例和固定比例裁剪,可通过方法调用获取裁剪后的图片。
|
||||
|
||||
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
|
||||
|
||||
::: info 写在前面
|
||||
|
||||
如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
|
||||
|
||||
:::
|
||||
|
||||
## 基础用法
|
||||
|
||||
最基本的图片裁剪,支持自由比例调整。
|
||||
|
||||
<DemoPreview dir="demos/vben-cropper/basic" />
|
||||
|
||||
## 固定比例裁剪
|
||||
|
||||
通过 `aspectRatio` 属性设置裁剪比例,格式为 `"宽:高"`,如 `"1:1"`、`"16:9"`、`"3:4"` 等。
|
||||
|
||||
<DemoPreview dir="demos/vben-cropper/aspect-ratio" />
|
||||
|
||||
## 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>
|
||||
```
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
::: info 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.
|
||||
|
||||
<DemoPreview dir="demos/vben-cropper/basic" />
|
||||
|
||||
## Fixed Aspect Ratio
|
||||
|
||||
Set the cropping ratio via the `aspectRatio` prop. The format is `"width:height"`, e.g. `"1:1"`, `"16:9"`, `"3:4"`.
|
||||
|
||||
<DemoPreview dir="demos/vben-cropper/aspect-ratio" />
|
||||
|
||||
## 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`:
|
||||
|
||||
```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 is a Blob or base64 string
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
#### getCropImage
|
||||
|
||||
Crop and retrieve the image.
|
||||
|
||||
```ts
|
||||
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 `aspectRatio` set, 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
|
||||
|
||||
```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('');
|
||||
|
||||
// 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>
|
||||
```
|
||||
Loading…
Reference in New Issue