生态产品多个设计器现已发布候选版本

查看生态总览
跳转到内容

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 ​

属性类型必填说明
srcstring图片地址,原样写到 image 部件的 src 上。
altstring被裁切图片的替代文本,原样写到 image 部件的 alt 上。 未提供时 image 部件写 alt="":读屏跳过该图片,不朗读地址。
aspectRationumber | null宽高比(宽 ÷ 高)。提供后改尺寸时另一条边随之计算;null 与未提供都表示不锁定比例。 非有限数与非正数按不锁定处理。
valueImageCropperRect裁切矩形。提供即受控:内部不再自行修改,只发 onValueChange。
defaultValueImageCropperRect
minWidthnumber裁切框的最小宽度,自然像素,默认 0。
minHeightnumber裁切框的最小高度,自然像素,默认 0。
zoomnumber显示缩放倍率,默认 1。提供即受控:setZoom 只发 onZoomChange。
defaultZoomnumber
minZoomnumber缩放滑杆的下限,默认 1。只约束滑杆,不夹取 setZoom。
maxZoomnumber缩放滑杆的上限,默认 3。只约束滑杆,不夹取 setZoom。
zoomStepnumber缩放滑杆的步长,默认 0.01。
rotationnumber显示旋转角度,单位度,默认 0。提供即受控:setRotation 只发 onRotationChange。 缩放与旋转只改变图片与裁切框的呈现,裁切矩形与源图像素的对应关系不变。
defaultRotationnumber
minRotationnumber旋转滑杆的下限,默认 -180。
maxRotationnumber旋转滑杆的上限,默认 180。
rotationStepnumber旋转滑杆的步长,默认 1。
flipImageCropperFlip翻转,默认两条轴都不翻。提供即受控:setFlip / toggleFlip 只发 onFlipChange。
defaultFlipImageCropperFlip
shapeImageCropperShape裁切框外形,默认 rect。
ariaLabelstring不显示出来的名字,写成 aria-label 落在与字段标签同一个部件上。 放进字段(字段接线)或渲染了 label 部件时名字以它们为准,aria-labelledby 盖过它。
disabledboolean禁用:裁切框与把手退出 Tab 序列,指针与键盘都不可修改,也不参与表单提交。
readOnlyboolean只读:仍可聚焦与被读屏朗读,不可修改。
namestring表单字段名;提供后才参与提交,值序列化为 x,y,width,height。
translationsPartial<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-changeImageCropperValueChangeDetails裁切矩形变化(拖动途中连续发出);detail 为 { value: { x, y, width, height } }
value-change-endImageCropperValueChangeEndDetails一次指针拖动松开时发出一次,一次方向键微调也发出一次;detail 为 { value: { x, y, width, height } }
zoom-changeImageCropperZoomChangeDetails缩放倍率变化;detail 为 { zoom: number }
rotation-changeImageCropperRotationChangeDetails旋转角度变化;detail 为 { rotation: number }
flip-changeImageCropperFlipChangeDetails翻转变化;detail 为 { flip: { horizontal: boolean, vertical: boolean } }

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhImageCropperRootdefaultImageCropperRootSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhImageCropperCropHandlepositionImageCropperHandlePosition是该把手拖动的方位。
XhImageCropperFlipTriggeraxisImageCropperFlipAxis是这颗按钮翻哪条轴。
XhImageCropperRootchildrenSlotChildren<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() 返回对应部件的宿主属性。

成员类型说明
valueImageCropperRect当前裁切矩形,自然像素。
zoomnumber
rotationnumber
flipImageCropperFlip
naturalImageCropperSize图片自然尺寸;未加载完成时为 0×0,此时裁切框无法测量位置。
draggingboolean正在整体拖动裁切框。
resizingboolean正在拉动某个把手。
disabledboolean
readOnlyboolean
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 / ArrowDownfocus on crop-area, 未禁用且非只读裁切框沿屏幕方向整体平移一个自然像素,尺寸不变;旋转取最近的直角、翻着的轴反向换算到图片上;走到图片边界就停住
Shift+ArrowLeft / Shift+ArrowRight / Shift+ArrowUp / Shift+ArrowDownfocus on crop-area, 未禁用且非只读同上,一次走十个自然像素
ArrowLeft / ArrowRight / ArrowUp / ArrowDownfocus on crop-handle, 未禁用且非只读这个把手负责的那条边或那个角沿屏幕方向挪一个自然像素,对面那条边钉住不动;锁了比例时另一条边跟着算
Shift+ArrowLeft / Shift+ArrowRight / Shift+ArrowUp / Shift+ArrowDownfocus on crop-handle, 未禁用且非只读同上,一次走十个自然像素
Enter / Spacefocus on flip-trigger, 未禁用翻转这颗按钮管的那条轴,aria-pressed 随之翻转;按钮是原生 button,这两个键由平台翻成 click
Enter / Spaceheld in flip-trigger, 未禁用按住期间投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下
Tab / Shift+Tab未禁用裁切框、八个把手、两条滑杆与翻转按钮各占一个 Tab 停靠点,按文档序依次走过

ARIA ​

以下属性由 connect 生成。

部件属性值
crop-areaaria-disabled'true' | 'false'
crop-areaaria-labelprops.ariaLabel
crop-arearole'application'
crop-handlearia-disabled'true' | 'false'
crop-handlearia-labellabel.handle(position)
crop-handlearia-valuemaxString(HANDLE_AXIS[position] === 'width' ? natural.wi…
crop-handlearia-valueminString(HANDLE_AXIS[position] === 'width' ? minWidth :…
crop-handlearia-valuenowString(HANDLE_AXIS[position] === 'width' ? value.widt…
crop-handlearia-valuetextlabel.valueText({ ...value })
crop-handlerole'slider'
gridaria-hidden'true'
zoom-slideraria-labellabel.zoomSlider
rotate-slideraria-labellabel.rotateSlider
flip-triggeraria-labellabel.flip(axis)
flip-triggeraria-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 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-disabled''(条件成立时才出现)
rootdata-dragging''(条件成立时才出现)
rootdata-readonly''(条件成立时才出现)
rootdata-resizing''(条件成立时才出现)
rootdata-shapeprops.shape
viewportdata-disabled''(条件成立时才出现)
viewportdata-dragging''(条件成立时才出现)
viewportdata-readonly''(条件成立时才出现)
viewportdata-resizing''(条件成立时才出现)
crop-areadata-disabled''(条件成立时才出现)
crop-areadata-dragging''(条件成立时才出现)
crop-areadata-readonly''(条件成立时才出现)
crop-areadata-resizing''(条件成立时才出现)
crop-areadata-shapeprops.shape
crop-handledata-disabled''(条件成立时才出现)
crop-handledata-positionposition
crop-handledata-readonly''(条件成立时才出现)
crop-handledata-resizing''(条件成立时才出现)
griddata-shapeprops.shape
zoom-sliderdata-disabled''(条件成立时才出现)
rotate-sliderdata-disabled''(条件成立时才出现)
flip-triggerdata-axisaxis
flip-triggerdata-disabled''(条件成立时才出现)
flip-triggerdata-pressed''(条件成立时才出现)
flip-triggerdata-state'on' | 'off'
flip-triggerdata-xh-action-control''
flip-triggerdata-xh-action-display'always'
flip-triggerdata-xh-action-profile'text'
flip-triggerdata-xh-action-size'sm'
flip-triggerdata-xh-action-variant'outline'

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-image-cropper-bgviewportbackgrounddefault--xh-bg-mutedimage-cropper 的 viewport 部件 background 覆盖槽。
--xh-image-cropper-crop-area-radiuscrop-areaborder-radiusshape=round--xh-shape-circleimage-cropper 的 crop-area 部件 border-radius 覆盖槽。
--xh-image-cropper-crop-bordercrop-areaborder-colordefault--xh-bg-surfaceimage-cropper 的 crop-area 部件 border-color 覆盖槽。
--xh-image-cropper-grid-linegridbackground-imagedefault--xh-bg-subtle-opaqueimage-cropper 的 grid 部件 background-image 覆盖槽。
--xh-image-cropper-handle-bgcrop-handlebackground
border
default
is([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])
position=ne
position=nw
position=se
position=sw
--xh-bg-surfaceimage-cropper 的 crop-handle 部件 background、border 覆盖槽。
--xh-image-cropper-handle-bg-hovercrop-handlebackground
border
disabled
hover
is([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])
not([data-disabled], [data-readonly])
position=ne
position=nw
position=se
position=sw
readonly
--xh-bg-subtle-opaqueimage-cropper 的 crop-handle 部件 background、border 覆盖槽。
--xh-image-cropper-handle-bg-resizingcrop-handlebackground
border
is([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])
position=ne
position=nw
position=se
position=sw
resizing
--xh-bg-brandimage-cropper 的 crop-handle 部件 background、border 覆盖槽。
--xh-image-cropper-handle-bordercrop-handleborderis([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])
position=ne
position=nw
position=se
position=sw
--xh-_image-cropper-handle-colorimage-cropper 的 crop-handle 部件 border 覆盖槽。
--xh-image-cropper-handle-lengthcrop-handleblock-size
inline-size
is([data-position='e'], [data-position='w'])
is([data-position='n'], [data-position='s'])
position=e
position=n
position=s
position=w
--xh-space-8image-cropper 的 crop-handle 部件 block-size、inline-size 覆盖槽。
--xh-image-cropper-handle-radiuscrop-handleborder-radiusdefault--xh-shape-pillimage-cropper 的 crop-handle 部件 border-radius 覆盖槽。
--xh-image-cropper-handle-sizecrop-handle
root
block-size
inline-size
inset
default
is([data-position='nw'], [data-position='ne'], [data-position='sw'], [data-position='se'])
position=ne
position=nw
position=se
position=sw
--xh-control-indicator-sizeimage-cropper 的 crop-handle、root 部件 block-size、inline-size、inset 覆盖槽。
--xh-image-cropper-handle-thicknesscrop-handleblock-size
border-block-end-width
border-block-start-width
border-inline-end-width
border-inline-start-width
inline-size
is([data-position='e'], [data-position='w'])
is([data-position='n'], [data-position='s'])
position=e
position=n
position=ne
position=nw
position=s
position=se
position=sw
position=w
--xh-stroke-strongimage-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-maskcrop-areabox-shadowdefault--xh-bg-overlayimage-cropper 的 crop-area 部件 box-shadow 覆盖槽。
--xh-image-cropper-slider-accentrotate-slider
zoom-slider
accent-colordefault--xh-bg-brandimage-cropper 的 rotate-slider、zoom-slider 部件 accent-color 覆盖槽。
--xh-image-cropper-slider-wrotate-slider
zoom-slider
inline-sizedefault100%image-cropper 的 rotate-slider、zoom-slider 部件 inline-size 覆盖槽。
--xh-image-cropper-viewport-radiusviewportborder-radiusdefault--xh-shape-surfaceimage-cropper 的 viewport 部件 border-radius 覆盖槽。
--xh-image-cropper-wrootinline-sizedefault100%image-cropper 的 root 部件 inline-size 覆盖槽。

动效 ​

动效角色:按压 · 状态(见动效规范)。

background-color · border-color 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

Released under The MIT License