feat(cropper): expand image export API

This commit is contained in:
Maofeng
2026-09-29 17:46:16 +08:00
parent f56397fb97
commit 24b32ea5cc
13 changed files with 421 additions and 51 deletions
@@ -9,6 +9,8 @@ toc:
title: Geometry functions
- id: export
title: Exporting an image
- id: api
title: API
- id: cross-origin
title: Cross-origin images
---
@@ -34,6 +36,27 @@ const [value, setValue] = React.useState({ x: 0, y: 0, zoom: 1 })
## Exporting an image {#export}
The shortest path exports the current result through the component ref:
```tsx
import { Cropper, type CropperHandle } from "@workspace/cropper"
const cropperRef = React.useRef<CropperHandle>(null)
const result = await cropperRef.current?.getCropImage({
outputType: "base64",
type: "image/jpeg",
quality: 0.9,
width: 1200,
})
<Cropper ref={cropperRef} src={src} />
```
`getCropImage()` returns `undefined` until the image is ready. `outputType` defaults to `"blob"`; `"base64"` returns a data URL suitable for previews or form values. Quality must be between `0` and `1`.
Use the pure function when no component instance is available:
```tsx
const blob = await cropImage(src, area, {
type: "image/webp",
@@ -42,7 +65,24 @@ const blob = await cropImage(src, area, {
})
```
Supplying only `width` or `height` preserves the crop aspect ratio. `shape: "circle"` clips through an ellipse; provide `background` when an opaque JPEG background is required.
Supplying only `width` or `height` preserves the crop aspect ratio. `shape: "circle"` clips through an ellipse; provide `background` when an opaque JPEG background is required. `getCropImage(source, area, options)` is the corresponding pure function for Blob/base64 output.
## API {#api}
| Prop | Type | Description |
| ------------------------------ | ----------------------------------- | ------------------------------------------------ |
| `src` | `string \| Blob` | An image URL, Blob, or File. |
| `aspectRatio` | `number \| \`${number}:${number}\`` | Crop viewport ratio; defaults to `1`. |
| `width` / `height` | matching `CSSProperties` value | Optional inline container dimensions. |
| `value` / `defaultValue` | `CropperValue` | Controlled or uncontrolled translation and zoom. |
| `minZoom` / `maxZoom` | `number` | Zoom range; defaults to `1` through `4`. |
| `shape` | `"rectangle" \| "circle"` | Viewport shape. |
| `showGrid` | `boolean` | Whether to render the rule-of-thirds grid. |
| `onCropAreaChange` | `(area) => void` | Runs when the valid source-image crop changes. |
| `onCropComplete` | `(area, value) => void` | Runs after an interaction or reset completes. |
| `onImageLoad` / `onImageError` | callbacks | Observe image loading state. |
`CropperHandle` exposes `element`, `getCropArea()`, `getCropImage(options)`, and `reset()`. Use `rootRef` when a raw DOM ref is needed; the component `ref` is reserved for the imperative handle.
## Cross-origin images {#cross-origin}
@@ -10,31 +10,25 @@ toc:
## Example {#example}
```tsx
import { Cropper, cropImage, type CropArea } from "@workspace/cropper"
import { Cropper, type CropperHandle } from "@workspace/cropper"
function AvatarCropper({ src }: { src: string }) {
const [area, setArea] = React.useState<CropArea>()
const cropperRef = React.useRef<CropperHandle>(null)
async function saveAvatar() {
if (!area) return
const blob = await cropImage(src, area, {
const blob = await cropperRef.current?.getCropImage({
height: 512,
shape: "circle",
type: "image/png",
width: 512,
})
await uploadAvatar(blob)
if (blob instanceof Blob) await uploadAvatar(blob)
}
return (
<div className="space-y-4">
<Cropper
src={src}
shape="circle"
className="size-72"
onCropAreaChange={setArea}
/>
<button type="button" disabled={!area} onClick={saveAvatar}>
<Cropper ref={cropperRef} src={src} shape="circle" className="size-72" />
<button type="button" onClick={saveAvatar}>
Save avatar
</button>
</div>
@@ -21,7 +21,7 @@ Add the package source to the application's Tailwind stylesheet:
## Cropper {#cropper}
Give the component an explicit width; `aspectRatio` determines its height:
Use classes or pass `width` and `height` directly. `aspectRatio` accepts a number or a `"width:height"` string:
```tsx
import { Cropper, type CropArea } from "@workspace/cropper"
@@ -33,7 +33,7 @@ function ProductImageCropper({ src }: { src: string }) {
<Cropper
src={src}
alt="Product"
aspectRatio={4 / 3}
aspectRatio="4:3"
className="w-full max-w-xl rounded-xl"
onCropAreaChange={setArea}
/>
@@ -43,6 +43,8 @@ function ProductImageCropper({ src }: { src: string }) {
Users can drag the image, scroll to zoom, or focus the Cropper and use the arrow, `+`, `-`, `0`, and `Home` keys.
`src` can also be a `Blob` or `File` from a file picker. The component creates a temporary object URL and revokes it when the source changes or the component unmounts.
## External controls {#controls}
Control the crop with `value` and `onValueChange` to connect a Slider or reset button. `minZoom` is clamped to at least `1`, ensuring that the image always covers the viewport.
Control the crop with `value` and `onValueChange` to connect a Slider. For a quick reset, call `reset()` on the component ref. `minZoom` is clamped to at least `1`, ensuring that the image always covers the viewport.
+4 -1
View File
@@ -21,9 +21,12 @@ toc:
- Pinch, wheel, double-click, and keyboard zooming.
- Controlled and uncontrolled state.
- Rectangular and circular viewports.
- Numeric and `"width:height"` viewport ratios.
- URL and local `Blob` image sources with automatic object URL cleanup.
- Resize-aware image constraints.
- Standalone pure geometry functions.
- Canvas export to PNG, JPEG, or WebP Blob.
- Imperative reset, crop-area access, and export through a component ref.
- Canvas export to PNG, JPEG, or WebP Blob/base64 output.
## Data model {#data-model}
@@ -9,6 +9,8 @@ toc:
title: 几何函数
- id: export
title: 导出图片
- id: api
title: API
- id: cross-origin
title: 跨域图片
---
@@ -34,6 +36,27 @@ const [value, setValue] = React.useState({ x: 0, y: 0, zoom: 1 })
## 导出图片 {#export}
最短路径是通过组件 ref 导出当前结果:
```tsx
import { Cropper, type CropperHandle } from "@workspace/cropper"
const cropperRef = React.useRef<CropperHandle>(null)
const result = await cropperRef.current?.getCropImage({
outputType: "base64",
type: "image/jpeg",
quality: 0.9,
width: 1200,
})
<Cropper ref={cropperRef} src={src} />
```
`getCropImage()` 在图片尚未加载时返回 `undefined`。`outputType` 默认为 `"blob"`;设为 `"base64"` 时返回可直接用于预览或表单值的 data URL。质量必须位于 `0` 到 `1` 之间。
需要脱离组件使用时,继续使用纯函数:
```tsx
const blob = await cropImage(src, area, {
type: "image/webp",
@@ -42,7 +65,24 @@ const blob = await cropImage(src, area, {
})
```
只指定 `width` 或 `height` 时会保持裁剪区域宽高比。`shape: "circle"` 会使用椭圆路径裁剪;需要 JPEG 背景色时可以提供 `background`。
只指定 `width` 或 `height` 时会保持裁剪区域宽高比。`shape: "circle"` 会使用椭圆路径裁剪;需要 JPEG 背景色时可以提供 `background`。`getCropImage(source, area, options)` 是支持 Blob/base64 返回值的对应纯函数。
## API {#api}
| 属性 | 类型 | 说明 |
| ------------------------------ | ----------------------------------- | ------------------------------ |
| `src` | `string \| Blob` | 图片 URL、Blob 或 File。 |
| `aspectRatio` | `number \| \`${number}:${number}\`` | 裁剪视口比例,默认为 `1`。 |
| `width` / `height` | `CSSProperties` 对应值 | 可选的内联容器尺寸。 |
| `value` / `defaultValue` | `CropperValue` | 受控或非受控的平移、缩放状态。 |
| `minZoom` / `maxZoom` | `number` | 缩放范围,默认 `1` 到 `4`。 |
| `shape` | `"rectangle" \| "circle"` | 视口形状。 |
| `showGrid` | `boolean` | 是否显示三等分辅助线。 |
| `onCropAreaChange` | `(area) => void` | 有效原图裁剪区域变化时触发。 |
| `onCropComplete` | `(area, value) => void` | 一次交互或重置完成时触发。 |
| `onImageLoad` / `onImageError` | 回调函数 | 监听图片加载状态。 |
`CropperHandle` 提供 `element`、`getCropArea()`、`getCropImage(options)` 和 `reset()`。需要原始 DOM ref 时使用 `rootRef`,组件 `ref` 专用于命令式句柄。
## 跨域图片 {#cross-origin}
@@ -10,31 +10,25 @@ toc:
## 示例 {#example}
```tsx
import { Cropper, cropImage, type CropArea } from "@workspace/cropper"
import { Cropper, type CropperHandle } from "@workspace/cropper"
function AvatarCropper({ src }: { src: string }) {
const [area, setArea] = React.useState<CropArea>()
const cropperRef = React.useRef<CropperHandle>(null)
async function saveAvatar() {
if (!area) return
const blob = await cropImage(src, area, {
const blob = await cropperRef.current?.getCropImage({
height: 512,
shape: "circle",
type: "image/png",
width: 512,
})
await uploadAvatar(blob)
if (blob instanceof Blob) await uploadAvatar(blob)
}
return (
<div className="space-y-4">
<Cropper
src={src}
shape="circle"
className="size-72"
onCropAreaChange={setArea}
/>
<button type="button" disabled={!area} onClick={saveAvatar}>
<Cropper ref={cropperRef} src={src} shape="circle" className="size-72" />
<button type="button" onClick={saveAvatar}>
保存头像
</button>
</div>
@@ -21,7 +21,7 @@ toc:
## 裁剪器 {#cropper}
组件需要明确的宽度,`aspectRatio` 会确定高度:
可以使用 class,也可以直接传入 `width`、`height`。`aspectRatio` 接受数字或 `"宽:高"` 字符串:
```tsx
import { Cropper, type CropArea } from "@workspace/cropper"
@@ -33,7 +33,7 @@ function ProductImageCropper({ src }: { src: string }) {
<Cropper
src={src}
alt="商品图片"
aspectRatio={4 / 3}
aspectRatio="4:3"
className="w-full max-w-xl rounded-xl"
onCropAreaChange={setArea}
/>
@@ -43,6 +43,8 @@ function ProductImageCropper({ src }: { src: string }) {
用户可以拖拽图片、滚动缩放,或者在 Cropper 聚焦时使用方向键、`+`、`-`、`0` 和 `Home`。
`src` 也可以直接传入文件选择器获得的 `Blob` 或 `File`。组件会创建临时 object URL,并在图片改变或卸载时自动释放。
## 外部控件 {#controls}
通过 `value` 和 `onValueChange` 控制裁剪状态,即可接入 Slider 或重置按钮。`minZoom` 最小为 `1`,保证图片始终覆盖整个视口。
通过 `value` 和 `onValueChange` 控制裁剪状态,即可接入 Slider。快速重置可以直接调用组件 ref 的 `reset()`;`minZoom` 最小为 `1`,保证图片始终覆盖整个视口。
+4 -1
View File
@@ -21,9 +21,12 @@ toc:
- 双指、滚轮、双击和键盘缩放。
- 受控与非受控状态。
- 矩形和圆形视口。
- 支持数字或 `"宽:高"` 格式的视口比例。
- 支持 URL 和本地 `Blob` 图片源,并自动释放临时 object URL。
- 响应容器尺寸变化并自动约束图片位置。
- 独立的纯几何函数。
- 使用 Canvas 导出 PNG、JPEG 或 WebP Blob。
- 通过组件 ref 重置、读取区域并导出结果。
- 使用 Canvas 导出 PNG、JPEG 或 WebP Blob/base64。
## 数据模型 {#data-model}