使用场景
Cropper 用于裁切图片,支持自定义裁切框样式,可通过拖动调整裁切框位置,被裁切图片位置;可缩放,旋转被裁切图片。
代码演示
如何引入
<script lang="ts">
import { Cropper } from '@fibrae/semi-ui-svelte';
</script>
基本用法
通过 src 设置被裁切的图片; 可通过 shape 设置裁切框形状,默认为方形。


代码
<script lang="ts">
import { Button, Cropper, Radio, RadioGroup, type CropperShape, type RadioValue } from '@fibrae/semi-ui-svelte';
const containerStyle = 'width: 550px; height: 300px; margin: 20px';
let cropper = $state<ReturnType<typeof Cropper>>();
let shape = $state<RadioValue | undefined>('rect');
let cropperUrl = $state('');
const onButtonClick = () => {
const canvas = cropper?.getCropperCanvas();
if (canvas) cropperUrl = canvas.toDataURL();
};
</script>
<RadioGroup aria-label="裁切框形状" bind:value={shape} name="demo-cropper-shape">
<Radio value="rect">rect</Radio>
<Radio value="round">round</Radio>
<Radio value="roundRect">roundRect</Radio>
</RadioGroup>
<Cropper bind:this={cropper} shape={shape as CropperShape} src="/assets/other/image.png" style={containerStyle} />
<Button onclick={onButtonClick}>裁切</Button>
<br />
<br />
{#if cropperUrl}
<img alt="裁切结果" src={cropperUrl} style="height: 400px" />
{/if}自定义裁切框比例
可通过 defaultAspectRatio 初始的裁切框比例(默认为 1)。可通过 aspectRatio 设置固定的裁切框比例。
设置 defaultAspectRatio仅对初始的裁切框比例生效, 拖动时,裁切框比例会随着拖动而变化。
设置 aspectRatio 时,裁切框比例固定,拖动时将裁切框将以此比例变化。图片加载后改变 aspectRatio,裁切框会按新比例重排:保持高度,宽度随比例变化,超出容器时缩到容器内,中心尽量保持不动;aspectRatio 变为 undefined 时不重排。


代码
<script lang="ts">
import { Button, Cropper } from '@fibrae/semi-ui-svelte';
const containerStyle = 'width: 550px; height: 300px; margin: 20px';
let cropper = $state<ReturnType<typeof Cropper>>();
let cropperUrl = $state('');
const onButtonClick = () => {
const canvas = cropper?.getCropperCanvas();
if (canvas) cropperUrl = canvas.toDataURL();
};
</script>
<Cropper aspectRatio={3 / 4} bind:this={cropper} src="/assets/other/image.png" style={containerStyle} />
<Button onclick={onButtonClick}>裁切</Button>
<br />
<br />
{#if cropperUrl}
<img alt="裁切结果" src={cropperUrl} style="height: 400px" />
{/if}受控旋转/缩放图片
通过 rotate 和 zoom 控制图片旋转和缩放。zoom 可用 bind:zoom、函数绑定 bind:zoom={get, set} 或单向传值 zoom={x},滚轮缩放时同时调用 onZoomChange(zoom);rotate 只能单向传值,组件不会改变它。zoom 不读全局默认值。


代码
<script lang="ts">
import { Button, Cropper, Slider } from '@fibrae/semi-ui-svelte';
const containerStyle = 'width: 550px; height: 300px; margin: 20px';
const actionStyle = 'margin-top: 20px; display: flex; align-items: center; justify-content: center; width: fit-content';
let rotate = $state(0);
let zoom = $state(1);
let cropper = $state<ReturnType<typeof Cropper>>();
let cropperUrl = $state('');
const onSliderChange = (value: number | number[]) => {
if (typeof value === 'number') rotate = value;
};
const onZoomSliderChange = (value: number | number[]) => {
if (typeof value === 'number') zoom = value;
};
const onButtonClick = () => {
const canvas = cropper?.getCropperCanvas();
if (canvas) cropperUrl = canvas.toDataURL();
};
</script>
<div id="cropper-container">
<Cropper bind:this={cropper} bind:zoom {rotate} src="/assets/other/image.png" style={containerStyle} />
<div style={actionStyle}>
<span id="cropper-rotate-zoom-rotate">旋转</span>
<Slider aria-labelledby="cropper-rotate-zoom-rotate" max={360} min={-360} onChange={onSliderChange} step={1} style="width: 500px" value={rotate} />
</div>
<div style={actionStyle}>
<span id="cropper-rotate-zoom-zoom">缩放</span>
<Slider aria-labelledby="cropper-rotate-zoom-zoom" max={3} min={0.1} onChange={onZoomSliderChange} step={0.1} style="width: 500px" value={zoom} />
</div>
<br />
<Button onclick={onButtonClick}>裁切</Button>
<br />
<br />
{#if cropperUrl}
<img alt="裁切结果" src={cropperUrl} style="height: 400px" />
{/if}
</div>裁切框设置
可通过 cropperBoxStyle, cropperBoxCls 自定义裁切框样式。可通过 showResizeBox 设置是否展示裁切框边角的调整块。


代码
<script lang="ts">
import { Button, Cropper } from '@fibrae/semi-ui-svelte';
const containerStyle = 'width: 550px; height: 300px; margin: 20px';
let cropper = $state<ReturnType<typeof Cropper>>();
let cropperUrl = $state('');
const onButtonClick = () => {
const canvas = cropper?.getCropperCanvas();
if (canvas) cropperUrl = canvas.toDataURL();
};
</script>
<strong>showResizeBox = false,并修改边框颜色</strong>
<Cropper bind:this={cropper} cropperBoxStyle="outline-color: var(--semi-color-bg-0)" showResizeBox={false} src="/assets/other/image.png" style={containerStyle} />
<Button onclick={onButtonClick}>裁切</Button>
<br />
<br />
{#if cropperUrl}
<img alt="裁切结果" src={cropperUrl} style="height: 400px" />
{/if}实时预览裁切效果
通过 preview 指定预览容器,实时预览裁切效果。preview 是返回容器 DOM 的函数,容器尚未挂载时可返回 null 或 undefined。


代码
<script lang="ts">
import { Button, Cropper, Slider } from '@fibrae/semi-ui-svelte';
const containerStyle = 'width: 550px; height: 300px; margin: 20px';
const actionStyle = 'margin-top: 20px; display: flex; align-items: center; justify-content: center; width: fit-content';
let rotate = $state(0);
let zoom = $state(1);
let cropperUrl = $state('');
let cropper = $state<ReturnType<typeof Cropper>>();
let previewWrapper = $state<HTMLDivElement>();
const onSliderChange = (value: number | number[]) => {
if (typeof value === 'number') rotate = value;
};
const onZoomSliderChange = (value: number | number[]) => {
if (typeof value === 'number') zoom = value;
};
const onButtonClick = () => {
const canvas = cropper?.getCropperCanvas();
if (canvas) cropperUrl = canvas.toDataURL();
};
// a function so the cropper reads the wrapper when it needs it; the element is undefined until it has mounted
const preview = () => previewWrapper;
</script>
<div>
<Cropper bind:this={cropper} bind:zoom {preview} {rotate} src="/assets/root-web-sites/abstract.jpg" style={containerStyle} />
<div style={actionStyle}>
<span id="cropper-preview-rotate">旋转</span>
<Slider aria-labelledby="cropper-preview-rotate" max={360} min={-360} onChange={onSliderChange} step={1} style="width: 500px" value={rotate} />
</div>
<div style={actionStyle}>
<span id="cropper-preview-zoom">缩放</span>
<Slider aria-labelledby="cropper-preview-zoom" max={3} min={0.1} onChange={onZoomSliderChange} step={0.1} style="width: 500px" value={zoom} />
</div>
<br />
<div style="display: flex">
<div style="width: 50%; flex-grow: 1">
<strong>实时预览</strong>
<div bind:this={previewWrapper} style="height: 300px; margin-top: 8px"></div>
</div>
<div style="width: 50%; flex-grow: 1; padding-left: 10px">
<Button onclick={onButtonClick}>裁切</Button>
<br />
<br />
{#if cropperUrl}
<img alt="裁切结果" src={cropperUrl} style="width: 90%" />
{/if}
</div>
</div>
</div>API
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| aspectRatio | 固定的裁切框宽高比;加载后改变时,裁切框按新比例重排。 | number | - |
| cropperBoxCls | 裁切框类名。 | string | - |
| cropperBoxStyle | 裁切框样式字符串。 | string | - |
| defaultAspectRatio | 裁切框的初始宽高比,拖动时会随之变化。 | number | 1 |
| fill | 裁切结果中非图片部分的填充色。 | string | "rgba(0, 0, 0, 0)" |
| imgProps | 传给被裁切图片 img 标签的属性。src 与布局样式由组件决定,class 与 style 和组件自身的合并,onload 在组件处理完成后调用;alt 默认为空字符串,crossorigin 默认为 anonymous,均可覆盖。 | HTMLImgAttributes | - |
| maxZoom | 最大缩放倍数。 | number | 3 |
| minZoom | 最小缩放倍数。 | number | 0.1 |
| onZoomChange | 滚轮缩放时调用,参数为新的缩放比例。 | Type(zoom: number) => void | - |
| preview | 返回预览容器 DOM 的函数,裁切效果会实时镜像到该容器;可返回 null,表示容器尚未挂载。 | Type() => HTMLElement | null | undefined | - |
| rotate | 图片旋转角度,单向绑定,组件不会改变它。 | number | - |
| shape | 裁切框形状。 | CropperShape | "rect" |
| showResizeBox | 是否展示裁切框边角的调整块。 | boolean | true |
| src | 被裁切图片的地址。 | string | - |
| zoom | 缩放比例,叠加在图片适应裁切区域的初始缩放之上;可用 bind:zoom、函数绑定或单向传值,滚轮缩放时更新绑定值并调用 onZoomChange。 | number | - |
| zoomStep | 滚轮缩放的步长。 | number | 0.1 |
Methods
| 方法 | 说明 | 类型 |
|---|---|---|
| getCropperCanvas | 按图片原始分辨率,把裁切框内的区域绘制到新的 canvas 并返回;图片加载前调用会抛出错误。 | Type() => HTMLCanvasElement |
class、style 与原生属性和事件(如 id、data-*、aria-*、onkeydown)落在根 <div> 上;role 可被覆盖。getCropperCanvas() 经 bind:this 取得的组件实例调用。
Accessibility
- 根元素传入
aria-label或aria-labelledby且未传role时,自动带role="group";传入的role优先 - 裁切框是可聚焦的命名分组(
role="group",名称与角色描述取自语言包,并通过aria-describedby指向操作提示),图片加载完成后才可聚焦。被裁切的img是装饰性的,alt默认为空字符串,可经imgProps覆盖 - 只有主按钮的鼠标按下,以及 touch、pen 触点,才开始拖动裁切框、调整块和图片;鼠标中键、右键按下不会开始拖动
键盘和焦点
- 裁切框是组件内唯一的 Tab 停靠点,图片加载完成后才可聚焦
- 焦点在裁切框上时,方向键每次移动裁切框 10px,不会移出容器
- Shift + 方向键每次移动裁切框右下角 10px 来调整大小,限制在容器内;设置了
aspectRatio时保持该比例(横向的变化换算为高度)。仅在showResizeBox为 true(有调整块)时可用,为 false 时 Shift + 方向键不起作用 - 缩放与旋转没有内置的键盘操作,需要使用方提供可用键盘操作的控件(如示例中的 Slider)
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $color-cropper_mask-bg | var(--semi-color-overlay-bg) | |
| $color-cropper_box-outline | var(--semi-color-primary) | |
| $color-cropper_box_corner-bg | var(--semi-color-primary) | |
| $width-cropper_box-outline | 1px | |
| $width-cropper_box_corner | 10px |