简体中文
ImageCropper 图片裁切
用于选择图片中需要保留的区域。
用法
拖动裁切区域或调整把手
组件结构
加粗的是必需部件。
data-scope="image-cropper":root · viewport · image · crop-area · crop-handle · grid · zoom-slider · rotate-slider · flip-trigger · hidden-input
示例
固定比例
以 16:9 裁切封面
圆形裁切
以 1:1 裁切头像
缩放与旋转
使用内置滑块调整视图
禁用
禁用后不可调整
翻转
两颗开关钮各管一条轴,图片与裁切框一起镜像;aria-pressed 报这条轴翻没翻
导出裁切结果
toCanvas 按所见出图:裁切矩形、旋转、翻转与圆形外形一并生效
设计指引
何时使用
- 裁切头像、封面或缩略图。
- 需要固定比例的图片输出。
何时不用
特性
- 使用源图自然像素记录裁切矩形。
- 支持拖动、八方向调整和键盘微调。
- 边缘把手显示为贴住裁切框边框的圆端短条;角部使用与可调容器相同的单拐角圆弧。
- 支持固定宽高比、圆形遮罩、缩放、旋转与水平 / 垂直翻转。缩放、旋转与翻转同时作用在图片与裁切框上,只改呈现,裁切矩形与源图像素的对应关系不变。
- 翻转由两颗
flip-trigger开关钮承担,各管一条轴(axis),aria-pressed报这条轴翻没翻;翻着时是无滑块开关的选中面(品牌淡底)。 - 方向键按屏幕方向移动:图片转了、翻了,框在屏幕上往哪边挪,按的就是哪个键;斜着的角度取最近的直角。
toCanvas()按所见出图:裁切矩形、旋转、翻转与圆形外形一并生效,像素取自 image 部件。圆形裁成内切于裁切矩形的椭圆(1:1 即正圆),椭圆外透明;旋转 90° 的倍数时画布宽高互换,其余角度画布是旋转后的外接矩形。纯函数cropToCanvas接受同一组rotation/flip/shape选项。- 支持受控裁切区域、受控翻转和原生表单提交。
onValueChangeEnd在一次调整结束时触发。
组合
最佳实践
- 为裁切区域设置合理的最小尺寸。
- 头像使用 1:1 比例和圆形遮罩。
- 在调整结束或确认时用
toCanvas()生成裁切结果,不要自己拿getCropRect()去drawImage:那样会丢掉旋转、翻转与圆形。 - 输出 JPEG 时给
background:圆形与斜角旋转留下的四角是透明的,JPEG 会把它们编码成黑块。 - 跨域图片应在加载前配置
crossorigin。
反模式
- 在每次位置变化时生成图片或请求服务端。
- 使用过小且无法键盘聚焦的调整把手。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-image-cropper> |
| Vue 组件 | XhImageCropperCropArea XhImageCropperCropHandle XhImageCropperFlipTrigger XhImageCropperGrid XhImageCropperHiddenInput XhImageCropperImage XhImageCropperRoot XhImageCropperRotateSlider XhImageCropperViewport XhImageCropperZoomSlider |
| 组合式函数 | useImageCropper |
| 状态机 | imageCropperMachine |
| 皮肤 | @xihan-ui/styles/image-cropper.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
src | string | 图片地址,原样写到 image 部件的 src 上。 | |
alt | string | 被裁切图片的替代文本,原样写到 image 部件的 alt 上。 未提供时 image 部件写 alt="":读屏跳过该图片,不朗读地址。 | |
aspectRatio | number | null | 宽高比(宽 ÷ 高)。提供后改尺寸时另一条边随之计算;null 与未提供都表示不锁定比例。 非有限数与非正数按不锁定处理。 | |
value | ImageCropperRect | 裁切矩形。提供即受控:内部不再自行修改,只发 onValueChange。 | |
defaultValue | ImageCropperRect | ||
minWidth | number | 裁切框的最小宽度,自然像素,默认 0。 | |
minHeight | number | 裁切框的最小高度,自然像素,默认 0。 | |
zoom | number | 显示缩放倍率,默认 1。提供即受控:setZoom 只发 onZoomChange。 | |
defaultZoom | number | ||
minZoom | number | 缩放滑杆的下限,默认 1。只约束滑杆,不夹取 setZoom。 | |
maxZoom | number | 缩放滑杆的上限,默认 3。只约束滑杆,不夹取 setZoom。 | |
zoomStep | number | 缩放滑杆的步长,默认 0.01。 | |
rotation | number | 显示旋转角度,单位度,默认 0。提供即受控:setRotation 只发 onRotationChange。 缩放与旋转只改变图片与裁切框的呈现,裁切矩形与源图像素的对应关系不变。 | |
defaultRotation | number | ||
minRotation | number | 旋转滑杆的下限,默认 -180。 | |
maxRotation | number | 旋转滑杆的上限,默认 180。 | |
rotationStep | number | 旋转滑杆的步长,默认 1。 | |
flip | ImageCropperFlip | 翻转,默认两条轴都不翻。提供即受控:setFlip / toggleFlip 只发 onFlipChange。 | |
defaultFlip | ImageCropperFlip | ||
shape | ImageCropperShape | 裁切框外形,默认 rect。 | |
ariaLabel | string | 不显示出来的名字,写成 aria-label 落在与字段标签同一个部件上。 放进字段(字段接线)或渲染了 label 部件时名字以它们为准,aria-labelledby 盖过它。 | |
disabled | boolean | 禁用:裁切框与把手退出 Tab 序列,指针与键盘都不可修改,也不参与表单提交。 | |
readOnly | boolean | 只读:仍可聚焦与被读屏朗读,不可修改。 | |
name | string | 表单字段名;提供后才参与提交,值序列化为 x,y,width,height。 | |
translations | Partial<ImageCropperTranslations> | ||
onValueChange | (details: ImageCropperValueChangeDetails) => void | 每次裁切矩形变化都发出;拖动过程中连续发出。 | |
onValueChangeEnd | (details: ImageCropperValueChangeEndDetails) => void | 只在一次拖动结束时发出一次,适合用于裁切导出。 | |
onZoomChange | (details: ImageCropperZoomChangeDetails) => void | 缩放变化意图;受控时是唯一出口。 | |
onRotationChange | (details: ImageCropperRotationChangeDetails) => void | 旋转变化意图;受控时是唯一出口。 | |
onFlipChange | (details: ImageCropperFlipChangeDetails) => void | 翻转变化意图;受控时是唯一出口。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
value-change | ImageCropperValueChangeDetails | 裁切矩形变化(拖动途中连续发出);detail 为 { value: { x, y, width, height } } |
value-change-end | ImageCropperValueChangeEndDetails | 一次指针拖动松开时发出一次,一次方向键微调也发出一次;detail 为 { value: { x, y, width, height } } |
zoom-change | ImageCropperZoomChangeDetails | 缩放倍率变化;detail 为 { zoom: number } |
rotation-change | ImageCropperRotationChangeDetails | 旋转角度变化;detail 为 { rotation: number } |
flip-change | ImageCropperFlipChangeDetails | 翻转变化;detail 为 { flip: { horizontal: boolean, vertical: boolean } } |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhImageCropperRoot | default | ImageCropperRootSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhImageCropperCropHandle | position | ImageCropperHandlePosition | 是 | 该把手拖动的方位。 |
XhImageCropperFlipTrigger | axis | ImageCropperFlipAxis | 是 | 这颗按钮翻哪条轴。 |
XhImageCropperRoot | children | SlotChildren<ImageCropperRootSlotProps> |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
flip-trigger | 'on' | 'off' |
以下名称仅用于内部状态机。
状态:dragging · idle · resizing
事件:VALUE.SET · ZOOM.SET · ROTATE.SET · FLIP.SET · FLIP.TOGGLE · IMAGE.LOAD · CROP.NUDGE · HANDLE.NUDGE · DRAG.START · RESIZE.START · DRAG.MOVE · DRAG.END · FORM.RESET · PRESS.START · PRESS.END
判据:canEdit · canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
value | ImageCropperRect | 当前裁切矩形,自然像素。 |
zoom | number | |
rotation | number | |
flip | ImageCropperFlip | |
natural | ImageCropperSize | 图片自然尺寸;未加载完成时为 0×0,此时裁切框无法测量位置。 |
dragging | boolean | 正在整体拖动裁切框。 |
resizing | boolean | 正在拉动某个把手。 |
disabled | boolean | |
readOnly | boolean | |
getCropRect | () => ImageCropperRect | 获取一份当前裁切矩形的副本。出图用 toCanvas,它连同旋转、翻转与圆形一起带上。 |
toCanvas | (options?: CropToCanvasOptions) => HTMLCanvasElement | null | 把当前裁切结果画到一张新画布上:裁切矩形、旋转、翻转与圆形外形一并生效,所见即所得。 像素取自 image 部件;图片未加载、没有裁切框或在服务端时返回 null。 |
setValue | (next: ImageCropperRect) => void | |
setZoom | (next: number) => void | |
setRotation | (next: number) => void | |
setFlip | (next: ImageCropperFlip) => void | |
toggleFlip | (axis: ImageCropperFlipAxis) => void | 翻转一条轴,另一条轴不动。 |
getRootProps | () => T['element'] | |
getViewportProps | () => T['element'] | |
getImageProps | () => T['img'] | |
getCropAreaProps | () => T['element'] | |
getCropHandleProps | (props: ImageCropperHandleProps) => T['button'] | |
getGridProps | () => T['element'] | 裁切框中的构图参考线,纯装饰。 |
getZoomSliderProps | () => T['input'] | 缩放滑杆,原生 range 输入。 |
getRotateSliderProps | () => T['input'] | 旋转滑杆,原生 range 输入。 |
getFlipTriggerProps | (props: ImageCropperFlipTriggerProps) => T['button'] | 翻转按钮,原生 button;aria-pressed 报这条轴此刻是否翻着。 |
getHiddenInputProps | () => T['input'] |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
ArrowLeft / ArrowRight / ArrowUp / ArrowDown | focus on crop-area, 未禁用且非只读 | 裁切框沿屏幕方向整体平移一个自然像素,尺寸不变;旋转取最近的直角、翻着的轴反向换算到图片上;走到图片边界就停住 |
Shift+ArrowLeft / Shift+ArrowRight / Shift+ArrowUp / Shift+ArrowDown | focus on crop-area, 未禁用且非只读 | 同上,一次走十个自然像素 |
ArrowLeft / ArrowRight / ArrowUp / ArrowDown | focus on crop-handle, 未禁用且非只读 | 这个把手负责的那条边或那个角沿屏幕方向挪一个自然像素,对面那条边钉住不动;锁了比例时另一条边跟着算 |
Shift+ArrowLeft / Shift+ArrowRight / Shift+ArrowUp / Shift+ArrowDown | focus on crop-handle, 未禁用且非只读 | 同上,一次走十个自然像素 |
Enter / Space | focus on flip-trigger, 未禁用 | 翻转这颗按钮管的那条轴,aria-pressed 随之翻转;按钮是原生 button,这两个键由平台翻成 click |
Enter / Space | held in flip-trigger, 未禁用 | 按住期间投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下 |
Tab / Shift+Tab | 未禁用 | 裁切框、八个把手、两条滑杆与翻转按钮各占一个 Tab 停靠点,按文档序依次走过 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
crop-area | aria-disabled | 'true' | 'false' |
crop-area | aria-label | props.ariaLabel |
crop-area | role | 'application' |
crop-handle | aria-disabled | 'true' | 'false' |
crop-handle | aria-label | label.handle(position) |
crop-handle | aria-valuemax | String(HANDLE_AXIS[position] === 'width' ? natural.wi… |
crop-handle | aria-valuemin | String(HANDLE_AXIS[position] === 'width' ? minWidth :… |
crop-handle | aria-valuenow | String(HANDLE_AXIS[position] === 'width' ? value.widt… |
crop-handle | aria-valuetext | label.valueText({ ...value }) |
crop-handle | role | 'slider' |
grid | aria-hidden | 'true' |
zoom-slider | aria-label | label.zoomSlider |
rotate-slider | aria-label | label.rotateSlider |
flip-trigger | aria-label | label.flip(axis) |
flip-trigger | aria-pressed | 'true' | 'false' |
样式参考
皮肤
@xihan-ui/styles/image-cropper.css 按 [data-scope="image-cropper"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-image-cropper 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-dragging | ''(条件成立时才出现) |
root | data-readonly | ''(条件成立时才出现) |
root | data-resizing | ''(条件成立时才出现) |
root | data-shape | props.shape |
viewport | data-disabled | ''(条件成立时才出现) |
viewport | data-dragging | ''(条件成立时才出现) |
viewport | data-readonly | ''(条件成立时才出现) |
viewport | data-resizing | ''(条件成立时才出现) |
crop-area | data-disabled | ''(条件成立时才出现) |
crop-area | data-dragging | ''(条件成立时才出现) |
crop-area | data-readonly | ''(条件成立时才出现) |
crop-area | data-resizing | ''(条件成立时才出现) |
crop-area | data-shape | props.shape |
crop-handle | data-disabled | ''(条件成立时才出现) |
crop-handle | data-position | position |
crop-handle | data-readonly | ''(条件成立时才出现) |
crop-handle | data-resizing | ''(条件成立时才出现) |
grid | data-shape | props.shape |
zoom-slider | data-disabled | ''(条件成立时才出现) |
rotate-slider | data-disabled | ''(条件成立时才出现) |
flip-trigger | data-axis | axis |
flip-trigger | data-disabled | ''(条件成立时才出现) |
flip-trigger | data-pressed | ''(条件成立时才出现) |
flip-trigger | data-state | 'on' | 'off' |
flip-trigger | data-xh-action-control | '' |
flip-trigger | data-xh-action-display | 'always' |
flip-trigger | data-xh-action-profile | 'text' |
flip-trigger | data-xh-action-size | 'sm' |
flip-trigger | data-xh-action-variant | 'outline' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-image-cropper-bg | viewport | background | default | --xh-bg-muted | image-cropper 的 viewport 部件 background 覆盖槽。 |
--xh-image-cropper-crop-area-radius | crop-area | border-radius | shape=round | --xh-shape-circle | image-cropper 的 crop-area 部件 border-radius 覆盖槽。 |
--xh-image-cropper-crop-border | crop-area | border-color | default | --xh-bg-surface | image-cropper 的 crop-area 部件 border-color 覆盖槽。 |
--xh-image-cropper-grid-line | grid | background-image | default | --xh-bg-subtle-opaque | image-cropper 的 grid 部件 background-image 覆盖槽。 |
--xh-image-cropper-handle-bg | crop-handle | backgroundborder | defaultis([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])position=neposition=nwposition=seposition=sw | --xh-bg-surface | image-cropper 的 crop-handle 部件 background、border 覆盖槽。 |
--xh-image-cropper-handle-bg-hover | crop-handle | backgroundborder | disabledhoveris([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])not([data-disabled], [data-readonly])position=neposition=nwposition=seposition=swreadonly | --xh-bg-subtle-opaque | image-cropper 的 crop-handle 部件 background、border 覆盖槽。 |
--xh-image-cropper-handle-bg-resizing | crop-handle | backgroundborder | is([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])position=neposition=nwposition=seposition=swresizing | --xh-bg-brand | image-cropper 的 crop-handle 部件 background、border 覆盖槽。 |
--xh-image-cropper-handle-border | crop-handle | border | is([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])position=neposition=nwposition=seposition=sw | --xh-_image-cropper-handle-color | image-cropper 的 crop-handle 部件 border 覆盖槽。 |
--xh-image-cropper-handle-length | crop-handle | block-sizeinline-size | is([data-position='e'], [data-position='w'])is([data-position='n'], [data-position='s'])position=eposition=nposition=sposition=w | --xh-space-8 | image-cropper 的 crop-handle 部件 block-size、inline-size 覆盖槽。 |
--xh-image-cropper-handle-radius | crop-handle | border-radius | default | --xh-shape-pill | image-cropper 的 crop-handle 部件 border-radius 覆盖槽。 |
--xh-image-cropper-handle-size | crop-handleroot | block-sizeinline-sizeinset | defaultis([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])position=neposition=nwposition=seposition=sw | --xh-control-indicator-size | image-cropper 的 crop-handle、root 部件 block-size、inline-size、inset 覆盖槽。 |
--xh-image-cropper-handle-thickness | crop-handle | block-sizeborder-block-end-widthborder-block-start-widthborder-inline-end-widthborder-inline-start-widthinline-size | is([data-position='e'], [data-position='w'])is([data-position='n'], [data-position='s'])position=eposition=nposition=neposition=nwposition=sposition=seposition=swposition=w | --xh-stroke-strong | image-cropper 的 crop-handle 部件 block-size、border-block-end-width、border-block-start-width、border-inline-end-width、border-inline-start-width、inline-size 覆盖槽。 |
--xh-image-cropper-mask | crop-area | box-shadow | default | --xh-bg-overlay | image-cropper 的 crop-area 部件 box-shadow 覆盖槽。 |
--xh-image-cropper-slider-accent | rotate-sliderzoom-slider | accent-color | default | --xh-bg-brand | image-cropper 的 rotate-slider、zoom-slider 部件 accent-color 覆盖槽。 |
--xh-image-cropper-slider-w | rotate-sliderzoom-slider | inline-size | default | 100% | image-cropper 的 rotate-slider、zoom-slider 部件 inline-size 覆盖槽。 |
--xh-image-cropper-viewport-radius | viewport | border-radius | default | --xh-shape-surface | image-cropper 的 viewport 部件 border-radius 覆盖槽。 |
--xh-image-cropper-w | root | inline-size | default | 100% | image-cropper 的 root 部件 inline-size 覆盖槽。 |
动效
动效角色:按压 · 状态(见动效规范)。
background-color · border-color 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
