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


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


代码
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { Button, Cropper, type CropperHandle } from '@fibrous/semi-ui-vue';
const containerStyle = {
width: '550px',
height: '300px',
margin: '20px',
};
const cropper = useTemplateRef<CropperHandle>('cropper');
const cropperUrl = ref('');
const onButtonClick = () => {
const canvas = cropper.value?.getCropperCanvas();
if (canvas) cropperUrl.value = canvas.toDataURL();
};
</script>
<template>
<Cropper ref="cropper" :aspectRatio="3 / 4" src="/assets/other/image.png" :style="containerStyle" />
<Button @click="onButtonClick">裁切</Button>
<br />
<br />
<img v-if="cropperUrl" alt="裁切结果" :src="cropperUrl" :style="{ height: '400px' }" />
</template>受控旋转/缩放图片
通过 rotate 和 zoom 控制图片旋转和缩放。zoom 可用 v-model:zoom 双向绑定,或用 :zoom 加 @zoomChange 单向绑定,滚轮缩放时同时触发 update:zoom 与 zoomChange;rotate 只能单向绑定,组件不会改变它。


代码
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { Button, Cropper, Slider, type CropperHandle } from '@fibrous/semi-ui-vue';
const containerStyle = {
width: '550px',
height: '300px',
margin: '20px',
};
const actionStyle = {
marginTop: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 'fit-content',
};
const rotate = ref(0);
const zoom = ref(1);
const cropper = useTemplateRef<CropperHandle>('cropper');
const cropperUrl = ref('');
const onSliderChange = (value: number | number[]) => {
if (typeof value === 'number') rotate.value = value;
};
const onZoomSliderChange = (value: number | number[]) => {
if (typeof value === 'number') zoom.value = value;
};
const onButtonClick = () => {
const canvas = cropper.value?.getCropperCanvas();
if (canvas) cropperUrl.value = canvas.toDataURL();
};
</script>
<template>
<div id="cropper-container">
<Cropper ref="cropper" v-model:zoom="zoom" :rotate="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" :modelValue="rotate" :step="1" :style="{ width: '500px' }" @update:modelValue="onSliderChange" />
</div>
<div :style="actionStyle">
<span id="cropper-rotate-zoom-zoom">缩放</span>
<Slider aria-labelledby="cropper-rotate-zoom-zoom" :max="3" :min="0.1" :modelValue="zoom" :step="0.1" :style="{ width: '500px' }" @update:modelValue="onZoomSliderChange" />
</div>
<br />
<Button @click="onButtonClick">裁切</Button>
<br />
<br />
<img v-if="cropperUrl" alt="裁切结果" :src="cropperUrl" :style="{ height: '400px' }" />
</div>
</template>裁切框设置
可通过 cropperBoxStyle, cropperBoxCls 自定义裁切框样式。可通过 showResizeBox 设置是否展示裁切框边角的调整块。


代码
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { Button, Cropper, type CropperHandle } from '@fibrous/semi-ui-vue';
const containerStyle = {
width: '550px',
height: '300px',
margin: '20px',
};
const cropper = useTemplateRef<CropperHandle>('cropper');
const cropperUrl = ref('');
const onButtonClick = () => {
const canvas = cropper.value?.getCropperCanvas();
if (canvas) cropperUrl.value = canvas.toDataURL();
};
</script>
<template>
<strong>showResizeBox = false,并修改边框颜色</strong>
<Cropper ref="cropper" :cropperBoxStyle="{ outlineColor: 'var(--semi-color-bg-0)' }" :showResizeBox="false" src="/assets/other/image.png" :style="containerStyle" />
<Button @click="onButtonClick">裁切</Button>
<br />
<br />
<img v-if="cropperUrl" alt="裁切结果" :src="cropperUrl" :style="{ height: '400px' }" />
</template>实时预览裁切效果
通过 preview 指定预览容器,实时预览裁切效果。preview 是返回容器 DOM 的函数,容器尚未挂载时可返回 null。


代码
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { Button, Cropper, Slider, type CropperHandle } from '@fibrous/semi-ui-vue';
const containerStyle = {
width: '550px',
height: '300px',
margin: '20px',
};
const actionStyle = {
marginTop: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 'fit-content',
};
const rotate = ref(0);
const zoom = ref(1);
const cropperUrl = ref('');
const cropper = useTemplateRef<CropperHandle>('cropper');
const previewWrapper = useTemplateRef<HTMLDivElement>('previewWrapper');
const onSliderChange = (value: number | number[]) => {
if (typeof value === 'number') rotate.value = value;
};
const onZoomSliderChange = (value: number | number[]) => {
if (typeof value === 'number') zoom.value = value;
};
const onButtonClick = () => {
const canvas = cropper.value?.getCropperCanvas();
if (canvas) cropperUrl.value = canvas.toDataURL();
};
// a function so the cropper reads the wrapper when it needs it; the ref is null until the wrapper has mounted
const preview = () => previewWrapper.value;
</script>
<template>
<div>
<Cropper ref="cropper" v-model:zoom="zoom" :preview="preview" :rotate="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" :modelValue="rotate" :step="1" :style="{ width: '500px' }" @update:modelValue="onSliderChange" />
</div>
<div :style="actionStyle">
<span id="cropper-preview-zoom">缩放</span>
<Slider aria-labelledby="cropper-preview-zoom" :max="3" :min="0.1" :modelValue="zoom" :step="0.1" :style="{ width: '500px' }" @update:modelValue="onZoomSliderChange" />
</div>
<br />
<div :style="{ display: 'flex' }">
<div :style="{ width: '50%', flexGrow: 1 }">
<strong>实时预览</strong>
<div ref="previewWrapper" :style="{ height: '300px', marginTop: '8px' }" />
</div>
<div :style="{ width: '50%', flexGrow: 1, paddingLeft: '10px' }">
<Button @click="onButtonClick">裁切</Button>
<br />
<br />
<img v-if="cropperUrl" alt="裁切结果" :src="cropperUrl" :style="{ width: '90%' }" />
</div>
</div>
</div>
</template>API
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| src | 被裁切图片的地址。 | string | semiGlobalDefault("Cropper", "src") |
| imgProps | 传给被裁切图片 img 标签的属性。src 与布局样式由组件决定,class、style 与组件自身的合并,onLoad 在组件处理完成后调用;alt 默认为空字符串,crossorigin 默认为 anonymous,均可覆盖。 | ImgHTMLAttributes | semiGlobalDefault("Cropper", "imgProps") |
| shape | 裁切框形状。 | CropperShape | semiDefault("Cropper", "shape", "rect") |
| aspectRatio | 固定的裁切框宽高比;加载后改变时,裁切框按新比例重排。 | number | semiGlobalDefault("Cropper", "aspectRatio") |
| defaultAspectRatio | 裁切框的初始宽高比,拖动时会随之变化。 | number | semiDefault("Cropper", "defaultAspectRatio", 1) |
| zoom | 受控的缩放比例,叠加在图片适应裁切区域的初始缩放之上。 | number | semiGlobalDefault("Cropper", "zoom") |
| rotate | 图片旋转角度,单向绑定,组件不会改变它。 | number | semiGlobalDefault("Cropper", "rotate") |
| showResizeBox | 是否展示裁切框边角的调整块。 | boolean | semiDefault("Cropper", "showResizeBox", true) |
| cropperBoxStyle | 裁切框样式。 | StyleValue | semiGlobalDefault("Cropper", "cropperBoxStyle") |
| cropperBoxCls | 裁切框类名。 | string | semiGlobalDefault("Cropper", "cropperBoxCls") |
| fill | 裁切结果中非图片部分的填充色。 | string | semiDefault("Cropper", "fill", "rgba(0, 0, 0, 0)") |
| maxZoom | 最大缩放倍数。 | number | semiDefault("Cropper", "maxZoom", 3) |
| minZoom | 最小缩放倍数。 | number | semiDefault("Cropper", "minZoom", 0.1) |
| zoomStep | 滚轮缩放的步长。 | number | semiDefault("Cropper", "zoomStep", 0.1) |
| preview | 返回预览容器 DOM 的函数,裁切效果会实时镜像到该容器;可返回 null,表示容器尚未挂载。 | Type(() => HTMLElement | null | undefined) | undefined |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| update:zoom | 滚轮缩放时触发,用于 v-model:zoom。 | [zoom: number] |
| zoomChange | 滚轮缩放时触发,载荷与 update:zoom 相同。 | [zoom: number] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| 无 |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| getCropperCanvas | 按图片原始分辨率,把裁切框内的区域绘制到新的 canvas 并返回;图片加载前调用会抛出错误。 | Type() => HTMLCanvasElement |
class、style、data-*、aria-* 与原生监听(click、keydown 等)经 attrs 落在根元素上。组件 ref 只暴露 getCropperCanvas,不是根 DOM。zoom 与 rotate 的全局默认值设置后,所有 Cropper 都变为受控。
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 |