展示类

图片裁切

通过设定裁切框的宽高比例,自由裁切图片

使用场景

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 不读全局默认值。

旋转
-360 360
缩放
0.1 3



代码
<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 设置是否展示裁切框边角的调整块。

showResizeBox = false,并修改边框颜色


代码
<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。

旋转
-360 360
缩放
0.1 3

实时预览


代码
<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裁切框的初始宽高比,拖动时会随之变化。number1
fill裁切结果中非图片部分的填充色。string"rgba(0, 0, 0, 0)"
imgProps传给被裁切图片 img 标签的属性。src 与布局样式由组件决定,class 与 style 和组件自身的合并,onload 在组件处理完成后调用;alt 默认为空字符串,crossorigin 默认为 anonymous,均可覆盖。HTMLImgAttributes-
maxZoom最大缩放倍数。number3
minZoom最小缩放倍数。number0.1
onZoomChange滚轮缩放时调用,参数为新的缩放比例。Type(zoom: number) => void-
preview返回预览容器 DOM 的函数,裁切效果会实时镜像到该容器;可返回 null,表示容器尚未挂载。Type() => HTMLElement | null | undefined-
rotate图片旋转角度,单向绑定,组件不会改变它。number-
shape裁切框形状。CropperShape"rect"
showResizeBox是否展示裁切框边角的调整块。booleantrue
src被裁切图片的地址。string-
zoom缩放比例,叠加在图片适应裁切区域的初始缩放之上;可用 bind:zoom、函数绑定或单向传值,滚轮缩放时更新绑定值并调用 onZoomChange。number-
zoomStep滚轮缩放的步长。number0.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 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$color-cropper_mask-bgvar(--semi-color-overlay-bg)
$color-cropper_box-outlinevar(--semi-color-primary)
$color-cropper_box_corner-bgvar(--semi-color-primary)
$width-cropper_box-outline1px
$width-cropper_box_corner10px