跳转到内容

ImageViewer 图片预览

查看大图:全屏浮层内可以缩放、旋转、翻转与翻页。

用法

触发器打开全屏查看:滚轮缩放、拖拽平移、工具条提供缩放/旋转/翻转/归零,Esc 或点击遮罩关闭

组件结构

加粗的是必需部件。

data-scope="image-viewer"trigger · backdrop · positioner · content · viewport · image · toolbar · zoom-in-trigger · zoom-out-trigger · rotate-left-trigger · rotate-right-trigger · flip-horizontal-trigger · flip-vertical-trigger · reset-trigger · prev-trigger · next-trigger · counter · close-trigger

示例

相册与翻页

多张图片共用一个查看浮层:两侧按钮或方向键翻页、计数报告第几张,缩放旋转在换图时归零

受控与文案

open 与 index 双受控;translations 更换工具条的可及名与计数文案

open:false,index:1

双指缩放

触屏上两指张开放大、捏合缩小,单指平移;缩放限制在 minScale 与 maxScale 之间

设计指引

何时使用

  • 图片细节重要(截图、单据、商品图)。
  • 一组图片需要连续浏览。

何时不用

  • 图片本身已经足够大时,不需要再加一层。
  • 需要编辑(裁切、标注)时,本组件是只读的查看器。

特性

  • collection 提供整组图片,index 决定当前一张,loop 决定是否循环。
  • 缩放步长与上下限可调。
  • 触屏上两指撑开放大、捏合缩小,单指平移;缩放以两指中点为锚。
  • 关闭后焦点归还触发器。
  • 逻辑关闭立即退出交互与可访问树;内容和遮罩完成退场后才释放模态资源,重开会撤销旧退场。
  • 底部控件带是一组有名称的控件,每个按钮各占一个 Tab 位;左右方向键与 Home/End 用于翻页,控件带内外一致。
  • 翻页按钮走 Action Control floating 档(48px 圆形),关闭按钮与控件带按钮走 icon 档;十个按钮共用取景器自己的深色半透明 chrome,不取页面语义面。

组合

  • 触发器使用图片;一组图片共用一个预览层。

最佳实践

  • 显示“第几张 / 共几张”,让用户知道剩余数量。
  • 工具栏按钮全部提供可访问名称,它们只有图标。

反模式

  • 打开后 Escape 无法关闭。
  • 缩放后没有复位入口。

API 参考

产物

自定义元素<xh-image-viewer>
Vue 组件XhImageViewerCloseTrigger XhImageViewerContent XhImageViewerCounter XhImageViewerFlipHorizontalTrigger XhImageViewerFlipVerticalTrigger XhImageViewerImage XhImageViewerNextTrigger XhImageViewerPrevTrigger XhImageViewerResetTrigger XhImageViewerRoot XhImageViewerRotateLeftTrigger XhImageViewerRotateRightTrigger XhImageViewerToolbar XhImageViewerTrigger XhImageViewerViewport XhImageViewerZoomInTrigger XhImageViewerZoomOutTrigger
组合式函数useImageViewer
状态机imageViewerMachine
皮肤@xihan-ui/styles/image-viewer.css

Props

属性类型必填说明
collectionImageViewerItem[]图片清单。查看单张时提供长度 1 的数组。默认为空,此时打开也只有工具条与空视口。
openboolean
defaultOpenboolean
indexnumber当前下标(0 起)。提供即受控:内部不再自行修改,只发 onIndexChange。
defaultIndexnumber非受控初值,默认 0。
loopboolean前后翻页到头是否回绕,默认 true。
zoomStepnumber缩放步长(加法),默认 0.5。
minScalenumber缩放下限,默认 0.25。
maxScalenumber缩放上限,默认 8。
closeOnEscapeboolean
closeOnInteractOutsideboolean点击遮罩(内容之外)关闭,默认 true。
restoreFocusboolean
variantOverlayBackdropVariant遮罩形态:opaque / blur / transparent。写在 backdrop 上,只影响该层的底色与模糊。
translationsPartial<ImageViewerTranslations>
onOpenChange(details: ImageViewerOpenChangeDetails) => voidopen 变化意图回调;受控时是唯一出口,非受控时随内部转移一并通知。
onIndexChange(details: ImageViewerIndexChangeDetails) => void下标变化意图回调;受控时是唯一出口,非受控时随内部写入一并通知。

事件

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
open-changeImageViewerOpenChangeDetailsopen 状态变化;detail 为 { open: boolean }
index-changeImageViewerIndexChangeDetails下标变化;detail 为 { index: number }

插槽

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhImageViewerRootdefaultImageViewerRootSlotProps

状态

公开状态写入 data-state

部件取值
trigger'open' | 'closed'
backdrop'open' | 'closed'
positioner'open' | 'closed'
content'open' | 'closed'
viewport'open' | 'closed'
image'open' | 'closed'
toolbar'open' | 'closed'
counter'open' | 'closed'

以下名称仅用于内部状态机。

状态open · closed

事件OPEN · CLOSE · INDEX.SET · INDEX.NEXT · INDEX.PREV · ZOOM.BY · ZOOM.SET · ROTATE.BY · FLIP · TRANSFORM.RESET · IMAGE.LOAD · IMAGE.ERROR · PAN.MOVE · POINTERS.DOWN · POINTERS.CHANGE · POINTERS.END · PAN.END · CONTROLLED.OPEN · CONTROLLED.CLOSE · PRESS.START · PRESS.END

判据isOpenControlled · canPress

connect API

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
openboolean
indexnumber当前下标,恒在 [0, count - 1] 内;清单为空时为 0。
countnumber
currentItemImageViewerItem | null当前图片;清单为空时为 null。
transformImageViewerTransform
panningboolean正在拖拽平移。
imageStatusImageViewerImageStatus当前大图的加载相位;切换图片与重新打开都回到 loading。
canPrevboolean向前仍可翻页(loop 且多于一张时恒为 true)。
canNextboolean
setOpen(next: boolean) => void
setIndex(next: number) => void直接跳到某一张;越界会被夹回 [0, count - 1]。切换图片时变换归零。
next() => void
prev() => void
zoomIn() => void
zoomOut() => void
setScale(scale: number) => void
rotateLeft() => void
rotateRight() => void
flipHorizontal() => void
flipVertical() => void
reset() => void变换整体归零(缩放 / 旋转 / 翻转 / 平移)。
getTriggerProps() => T['button']
getBackdropProps() => T['element']
getPositionerProps() => T['element']
getContentProps() => T['element']
getViewportProps() => T['element']
getImageProps() => T['img']
getToolbarProps() => T['element']底部的控件带,放置缩放、旋转、翻转与归零按钮。 它报告 role=group:一组有名字的控件,每个按钮各占一个 Tab 位。 不报告 role=toolbar:该角色承诺条内依靠方向键移动,而左右方向键与 Home/End 在这里是翻页;需要该移动方式时在这条带中放置一个 Toolbar 组件。
getZoomInTriggerProps() => T['button']
getZoomOutTriggerProps() => T['button']
getRotateLeftTriggerProps() => T['button']
getRotateRightTriggerProps() => T['button']
getFlipHorizontalTriggerProps() => T['button']
getFlipVerticalTriggerProps() => T['button']
getResetTriggerProps() => T['button']
getPrevTriggerProps() => T['button']
getNextTriggerProps() => T['button']
getCounterProps() => T['element']
getCloseTriggerProps() => T['button']

无障碍

键盘

规格出处:W3C APG

按键生效条件行为
Enter / Spacefocus in trigger打开看片浮层并把焦点移入 content
Escapeopen关闭并把焦点还给 trigger(closeOnEscape=false 时不关)
Tabopen在 content 内向后循环焦点
Shift+Tabopen在 content 内向前循环焦点
ArrowLeftopen上一张
ArrowRightopen下一张
Homeopen跳到第一张
Endopen跳到最后一张
+ / =open放大一档(zoomStep),到 maxScale 停住
-open缩小一档,到 minScale 停住
0open缩放、旋转、翻转与平移一并复位
Enter / Spaceopen, held on close-trigger / 工具条七颗 / prev-trigger / next-trigger, 该按钮未禁用按住期间该按钮投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦、浮层收起或按住途中转禁用(贴住缩放端点、翻到边界)撤下。缩放、旋转、翻转、复位、翻页与关闭照旧由这一次按键的原生激活承担

ARIA

以下属性由 connect 生成。

部件属性
triggeraria-controlscontent 部件的 id
triggeraria-expanded'true' | 'false'
triggeraria-haspopup'dialog'
backdroparia-hidden'true'
contentaria-hidden!open || undefined
contentaria-labelcurrentItem?.alt
contentaria-modal'true'
contentrole'dialog'
viewportaria-busyimageStatus === 'loading' || undefined
toolbararia-labellabel.toolbar
toolbarrole'group'
counteraria-live'polite'

样式参考

皮肤

@xihan-ui/styles/image-viewer.css 使用 [data-scope="image-viewer"][data-part="trigger"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides

数据属性

connect 生成;条件不成立时不输出无值属性。

部件属性
triggerdata-state'open' | 'closed'
backdropdata-state'open' | 'closed'
backdropdata-variantprops.variant
positionerdata-positioned''
positionerdata-state'open' | 'closed'
contentdata-state'open' | 'closed'
viewportdata-dragging''(条件成立时才出现)
viewportdata-loading''(条件成立时才出现)
viewportdata-state'open' | 'closed'
imagedata-dragging''(条件成立时才出现)
imagedata-loading''(条件成立时才出现)
imagedata-state'open' | 'closed'
toolbardata-state'open' | 'closed'
zoom-in-triggerdata-pressed''(条件成立时才出现)
zoom-in-triggerdata-xh-action-control''
zoom-in-triggerdata-xh-action-display'always'
zoom-in-triggerdata-xh-action-profile'icon'
zoom-in-triggerdata-xh-action-size'xs'
zoom-out-triggerdata-pressed''(条件成立时才出现)
zoom-out-triggerdata-xh-action-control''
zoom-out-triggerdata-xh-action-display'always'
zoom-out-triggerdata-xh-action-profile'icon'
zoom-out-triggerdata-xh-action-size'xs'
rotate-left-triggerdata-pressed''(条件成立时才出现)
rotate-left-triggerdata-xh-action-control''
rotate-left-triggerdata-xh-action-display'always'
rotate-left-triggerdata-xh-action-profile'icon'
rotate-left-triggerdata-xh-action-size'xs'
rotate-right-triggerdata-pressed''(条件成立时才出现)
rotate-right-triggerdata-xh-action-control''
rotate-right-triggerdata-xh-action-display'always'
rotate-right-triggerdata-xh-action-profile'icon'
rotate-right-triggerdata-xh-action-size'xs'
flip-horizontal-triggerdata-pressed''(条件成立时才出现)
flip-horizontal-triggerdata-xh-action-control''
flip-horizontal-triggerdata-xh-action-display'always'
flip-horizontal-triggerdata-xh-action-profile'icon'
flip-horizontal-triggerdata-xh-action-size'xs'
flip-vertical-triggerdata-pressed''(条件成立时才出现)
flip-vertical-triggerdata-xh-action-control''
flip-vertical-triggerdata-xh-action-display'always'
flip-vertical-triggerdata-xh-action-profile'icon'
flip-vertical-triggerdata-xh-action-size'xs'
reset-triggerdata-pressed''(条件成立时才出现)
reset-triggerdata-xh-action-control''
reset-triggerdata-xh-action-display'always'
reset-triggerdata-xh-action-profile'icon'
reset-triggerdata-xh-action-size'xs'
prev-triggerdata-pressed''(条件成立时才出现)
prev-triggerdata-xh-action-control''
prev-triggerdata-xh-action-display'always'
prev-triggerdata-xh-action-profile'floating'
prev-triggerdata-xh-action-size'md'
next-triggerdata-pressed''(条件成立时才出现)
next-triggerdata-xh-action-control''
next-triggerdata-xh-action-display'always'
next-triggerdata-xh-action-profile'floating'
next-triggerdata-xh-action-size'md'
counterdata-countString(count)
counterdata-indexString(index + 1)
counterdata-state'open' | 'closed'
close-triggerdata-pressed''(条件成立时才出现)
close-triggerdata-xh-action-control''
close-triggerdata-xh-action-display'always'
close-triggerdata-xh-action-profile'icon'
close-triggerdata-xh-action-size'lg'

CSS 变量

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

变量部件CSS 属性状态默认来源说明
--xh-image-viewer-action-bg-activeclose-trigger
content
next-trigger
prev-trigger
toolbar
background-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-color-neutral-950image-viewer 的 close-trigger、content、next-trigger、prev-trigger、toolbar 部件 background-color 覆盖槽。
--xh-image-viewer-action-bg-hoverclose-trigger
content
next-trigger
prev-trigger
toolbar
background-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-color-neutral-950image-viewer 的 close-trigger、content、next-trigger、prev-trigger、toolbar 部件 background-color 覆盖槽。
--xh-image-viewer-backdrop-bgbackdropbackgrounddefault--xh-color-neutral-950image-viewer 的 backdrop 部件 background 覆盖槽。
--xh-image-viewer-backdrop-blurbackdrop-webkit-backdrop-filter
backdrop-filter
variant=blur--xh-overlay-backdrop-blurimage-viewer 的 backdrop 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。
--xh-image-viewer-backdrop-layerbackdropz-indexdefault--xh-_layerimage-viewer 的 backdrop 部件 z-index 覆盖槽。
--xh-image-viewer-chrome-bgclose-trigger
content
counter
next-trigger
prev-trigger
toolbar
background
background-color
default
disabled
focus-visible
--xh-color-neutral-950image-viewer 的 close-trigger、content、counter、next-trigger、prev-trigger、toolbar 部件 background、background-color 覆盖槽。
--xh-image-viewer-close-bg-activeclose-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_image-viewer-chrome-bg-activeimage-viewer 的 close-trigger 部件 background-color 覆盖槽。
--xh-image-viewer-close-bg-hoverclose-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_image-viewer-chrome-bg-hoverimage-viewer 的 close-trigger 部件 background-color 覆盖槽。
--xh-image-viewer-close-fgclose-triggercolordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
currentColorimage-viewer 的 close-trigger 部件 color 覆盖槽。
--xh-image-viewer-close-radiusclose-triggerborder-radiusdefault--xh-shape-controlimage-viewer 的 close-trigger 部件 border-radius 覆盖槽。
--xh-image-viewer-close-sizeclose-triggerblock-size
inline-size
default
xh-action-profile=floating
xh-action-profile=icon
--xh-_action-profile-visual-sizeimage-viewer 的 close-trigger 部件 block-size、inline-size 覆盖槽。
--xh-image-viewer-counter-paddingcounterpaddingdefault--xh-space-1image-viewer 的 counter 部件 padding 覆盖槽。
--xh-image-viewer-counter-radiuscounterborder-radiusdefault--xh-image-viewer-overlay-radiusimage-viewer 的 counter 部件 border-radius 覆盖槽。
--xh-image-viewer-fgcontentcolordefault--xh-color-neutral-0image-viewer 的 content 部件 color 覆盖槽。
--xh-image-viewer-icon-sizeclose-trigger
content
next-trigger
prev-trigger
toolbar
--xh-icon-sizedefault--xh-_action-profile-glyph-size
--xh-glyph-size-md
image-viewer 的 close-trigger、content、next-trigger、prev-trigger、toolbar 部件 --xh-icon-size 覆盖槽。
--xh-image-viewer-layerpositionerz-indexdefault--xh-_layerimage-viewer 的 positioner 部件 z-index 覆盖槽。
--xh-image-viewer-loading-bgviewportbackgroundloading--xh-bg-surface-raisedimage-viewer 的 viewport 部件 background 覆盖槽。
--xh-image-viewer-loading-radiusviewportborder-radiusloading--xh-shape-surfaceimage-viewer 的 viewport 部件 border-radius 覆盖槽。
--xh-image-viewer-loading-sizeviewportblock-size
inline-size
loading--xh-control-h-lgimage-viewer 的 viewport 部件 block-size、inline-size 覆盖槽。
--xh-image-viewer-overlay-radiuscounter
next-trigger
prev-trigger
toolbar
border-radiusdefault--xh-_action-profile-radius
--xh-shape-control
--xh-shape-surface
image-viewer 的 counter、next-trigger、prev-trigger、toolbar 部件 border-radius 覆盖槽。
--xh-image-viewer-toolbar-gaptoolbargapdefault--xh-space-1image-viewer 的 toolbar 部件 gap 覆盖槽。
--xh-image-viewer-toolbar-paddingtoolbarpaddingdefault--xh-space-1_5image-viewer 的 toolbar 部件 padding 覆盖槽。
--xh-image-viewer-toolbar-radiustoolbarborder-radiusdefault--xh-_action-profile-radiusimage-viewer 的 toolbar 部件 border-radius 覆盖槽。
--xh-image-viewer-toolbar-radius-outertoolbarborder-radiusdefault--xh-image-viewer-overlay-radiusimage-viewer 的 toolbar 部件 border-radius 覆盖槽。

动效

共享关键帧 xh-fade-in · xh-fade-outfamily/motion.css 提供,皮肤 @import 它,单独引入仍成立;transformtransition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。

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

RTL

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

Released under The MIT License