文档
入门

安装

React 项目目录运行以下命令:

pnpm:

pnpm i react-image-previewer

yarn:

yarn add react-image-previewer

或者用 npm:

npm install react-image-previewer

快速开始

在需要使用的地方引入 PhotoProviderPhotoView 组件,基本用法:

import { PhotoProvider, PhotoView } from 'react-image-previewer';
 
export default function MyComponent() {
  return (
    <PhotoProvider>
      <div>
        {images.map((item, index) => (
          <PhotoView key={index} src={item}>
            <img src={item} alt="" />
          </PhotoView>
        ))}
      </div>
    </PhotoProvider>
  );
}

PhotoProvider为界限,里面所有的 PhotoView 图片会按照运行顺序提取为一组图片预览画廊。当某个 <img /> 被点击,则会定位到指定的图片并打开预览:

如果需要整个页面的图片形成一组进行预览,则可以在页面的入口处嵌套 PhotoProvider

💡

注意: react-image-previewer 一次只在 DOM 中加载保留三张图片,因为每张图片都是一个合成层,这会消耗相当多的内存。

PhotoProvider 参数

自定义覆盖物

<PhotoProvider /> 上添加 overlayRender 函数,可以实现自定义节点以及工具栏的渲染。

overlayRender 函数提供的参数有:

  • loading - 当前预览图片是否加载中 boolean
  • mode - 当前模式 drag | slide
  • images - 图片列表 DataType[]
  • index - 当前索引 number
  • onIndexChange - 索引改变回调 (index: number) => void
  • visible - 是否可见 boolean
  • onClose - 关闭事件回调 () => void
  • overlayVisible - 覆盖物是否可见 boolean
  • overlay - 图片覆盖物
  • rotate - 当前旋转角度 number
  • onRotate - 旋转事件回调 (rotate: number) => void
  • scale - 当前缩放 number
  • onScale - 缩放事件回调 (scale: number) => void

拖拽模式

默认使用 slide 模式,使用拖拽模式需要在 <PhotoProvider /> 上添加 mode="drag"

拖拽模式可以在当前预览视图内自由拖动图片,左右拖动不再自动切换图片。

缩放比例 scale 将按图片实际大小计算,最小缩放比例放开到 0.1(图片实际大小的 10%),最大缩放比例为 6 倍。

💡
若设备支持触摸,将强制使用 slide 模式。

循环预览

<PhotoProvider /> 上添加参数 loop 可以更改循环预览数量。设为 boolean 启用/关闭,设为 number 类型则超过具体数量以开启循环预览,默认值为 3 张:

<PhotoProvider loop={4} />

蒙层透明度

预设为不透明,上拉过程中会慢慢减少透明度,可在 <PhotoProvider /> 添加 maskOpacity 调整默认透明度,可设置为 0-1 之间的数字:

<PhotoProvider maskOpacity={0.5} />

蒙层事件

pullClosable(默认 true) 是否可以通过下拉关闭画廊,maskClosable(默认 true) 是否可以点击蒙版关闭画廊:

<PhotoProvider pullClosable={false} maskClosable={false} />

过渡效果

react-image-previewer 为保证更流畅体验,使用了相当多的 CSS3 动画,在滚动时使用弹簧动画贴近原生滚动效果。CSS3 动画默认持续时间为 400ms,使用 cubic-bezier(0.25, 0.8, 0.25, 1) 动画函数。

可通过设置 speedeasing 来自定义动画时间或函数:

<PhotoProvider
  speed={() => 800}
  easing={(type) => (type === 2 ? 'cubic-bezier(0.36, 0, 0.66, -0.56)' : 'cubic-bezier(0.34, 1.56, 0.64, 1)')}
/>

speedeasing为函数,可通过参数 type 判断当前动画的类型,type 的值有:

  • 1 - 打开中
  • 2 - 关闭中
  • 3 - 切换 index
💡
若子节点不是 img 节点将额外添加 opacity 淡入淡出效果。

关闭鼠标滚轮缩放

PhotoProvider 提供 enableMouseZoom 属性来控制是否响应鼠标滚轮事件。默认 true

<PhotoProvider enableMouseZoom={false} />

自定义反馈

<PhotoView /> 如果加载失败,默认渲染空节点,你很可能需要根据当前 UI 设置属于自己的错误反馈节点。

PhotoProvider 提供以下属性自定义反馈节点渲染:

  • brokenElement - 设置加载失败图案 React.ReactElement
  • loadingElement - 设置加载中图案 React.ReactElement
Click

根节点挂载

PhotoProvider 提供 portalContainer 属性设置挂载的根节点,默认挂载到 document.body

<PhotoProvider portalContainer={document.body} />

Ref

PhotoProvider 提供 ref 属性获取预览器的 DOM 节点

const ref = useRef()
 
<PhotoProvider ref={ref} />

裁切缩略图

<PhotoView /> 组件会自动识别子节点类型,如果子节点为 <img />,并且使用 CSS 设置了 object-fit 属性,则在缩放动画中附带裁切效果:

<PhotoView src={imageURL}>
  <img src={imageURL} style={{ objectFit: 'cover' }} alt="" />
</PhotoView>

放大/缩小

  • modedrag 模式下,缩放最小可达 0.1(图片原始尺寸的 10%),1 为图片原始尺寸大小

  • modeslide 模式下,缩放最小可达 1(当前视图的大小)

  • 最大缩放比例可达 6

添加对 scale 的控制即可实现放大/缩小按钮:

import { ZoomInIcon, ZoomOutIcon } from 'react-image-previewer/ui'
<PhotoProvider
  overlayRender={({ onScale, scale }) => {
    return (
      <div style={{zIndex: 9999, position: 'absolute', bottom: '100px', left: '50%'}}>
        <button onClick={() => onScale(scale + 1)}><ZoomOutIcon /></button>
        <button onClick={() => onScale(scale - 1)}><ZoomOutIcon /></button>
      </div>
    );
  }}
/>

旋转

react-image-previewer 默认不内置旋转控制按钮,但提供了旋转 API。我们只需简单添加一个 SVG 图片即可实现旋转效果:

import { RotateLeftIcon, RotateRightIcon } from 'react-image-previewer/ui'
<PhotoProvider
  overlayRender={({ rotate, onRotate }) => {
    return (
      <div style={{zIndex: 9999, position: 'absolute', bottom: '100px', left: '50%'}}>
      <button onClick={() => onRotate(rotate + 90)}><RotateLeftIcon /></button>
      <button onClick={() => onRotate(rotate - 90)}><RotateRightIcon /></button>
      </div>)
  }}
/>

以下示例实现了旋转/全屏/放大/缩小工具栏:

长图模式

移动设备加载长宽比例超过 3 倍的图片,默认会进入长图模式。即开始预览在图片顶部,图片宽度填充移动设备宽度:

触发节点

<PhotoView> 子组件可以是常规 React.HTMLAttributes 类型,如 <img /><div /> 等。

如果是自定义组件则需要保证 onClick 事件能正常触发,转发 refHTMLElement 上保证打开/关闭动画源的正确性。

<PhotoView src={imageURL}>
  <Button primary>Click</Button>
</PhotoView>
Click

PhotoSlider

一般情况,使用 <PhotoProvider /> 配合 <PhotoView /> 已经满足大部分需求。如果有更高级的自定义控制,可以使用 <PhotoSlider />

<PhotoSlider /> 的参数继承自 <PhotoProvider />。会额外暴露几个 API:

  • mode - 当前模式 drag | slide
  • images 图片列表,DataType[]
  • visible 是否可见受控属性 boolean
  • onClose 关闭事件回调 () => void
  • afterClose 关闭动画结束后回调 () => void
  • index 当前索引受控属性 number
  • onIndexChange 索引改变回调 (index: number) => void

visibleonClose 搭配形成受控,index 搭配 onIndexChange 形成受控。

export default function MyComponent() {
  const [visible, setVisible] = useState(false);
  const [index, setIndex] = useState(0);
 
  return (
    <>
      <Button onClick={() => setIndex(2)}>setIndex(2)</Button>
      <Button onClick={() => setIndex(4)}>setIndex(4)</Button>
      <Button onClick={() => setVisible(true)} primary>
        Click
      </Button>
 
      <PhotoSlider
        images={images.map((item) => ({ src: item, key: item }))}
        visible={visible}
        onClose={() => setVisible(false)}
        index={index}
        onIndexChange={setIndex}
      />
    </>
  );
}
setIndex(2)
setIndex(4)
Click

预览更多

<PhotoView /> 无子组件的情况下也能加入到预览队列,只是无法直接触发当前图片的预览。通过这个特性,我们可以实现经过一个入口,预览更多图片的功能:

<PhotoProvider>
  {images.map((item, index) => (
    <PhotoView key={index} src={item}>
      {index < 2 ? <img src={item} alt="" /> : undefined}
    </PhotoView>
  ))}
</PhotoProvider>

自定义渲染节点

<PhotoView /> 默认内置支持图片的预览,只需传递 src 即可,组件会自动识别图片的宽高。

我们也可以通过 render 函数实现自定义节点的预览,以支持 videopdf 的需求。

const elementSize = 400;
 
function MyComponent() {
  return (
    <PhotoView
      width={elementSize}
      height={elementSize}
      render={({ scale, attrs }) => {
        const width = attrs.style.width;
        const offset = (width - elementSize) / elementSize;
        const childScale = scale === 1 ? scale + offset : 1 + offset;
 
        return (
          <div {...attrs}>
            <div style={{ transform: `scale(${childScale})`, width: elementSize, transformOrigin: '0 0' }}>
              <div>Hello world</div>
              <Button>button</Button>
              <input onMouseDown={(e) => e.stopPropagation()} />
            </div>
          </div>
        );
      }}
    >
      <Button primary>Click</Button>
    </PhotoView>
  );
}
💡

注意:画廊在缩放之后,先会用 scale 进行缩放,在动画结束后则调整为当前 scale 所对应的宽高,并重置 scale1

Click

UI

图标

react-image-previewer/ui 提供了一些工具栏图标

import {
  ArrowLeftIcon,
  ArrowRightIcon,
  RotateLeftIcon,
  RotateRightIcon,
  ZoomInIcon,
  ZoomOutIcon,
  OneToOneIcon,
  DownloadIcon,
  LoadingIcon,
  CloseIcon,
} from 'react-image-previewer/ui'
 

关闭按钮

react-image-previewer/ui 提供了一个关闭按钮 CloseButton,它的默认位置在右上角

import { CloseButton } from 'react-image-previewer/ui'
 
<PhotoProvider
  overlayRender={({ onClose }) => {
    return <CloseButton onClick={onClose} />
  }}
/>

适合滑动模式的工具栏

react-image-previewer/ui 提供了一个适合滑动模式的工具栏 SlideToolbar

提供了四个按钮,内置了他们的行为和位置,并且优化了移动设备

  • arrowLeft 左箭头,点击切换到上一张图片
  • arrowRight 右箭头,点击切换到下一张图片
  • countText 用于显示当前图片的索引 2/5
  • download 下载按钮,点击下载当前图片
import { SlideToolbar, CloseButton } from 'react-image-previewer/ui'
 
<PhotoProvider
  overlayRender={props => {
    return (
      <>
        <SlideToolbar {...props} />
        <CloseButton onClick={props.onClose} />
      </>
    )
  }}
/>

可选项:

  • items 设置工具栏按钮,默认为 [ 'arrowLeft', 'countText', 'arrowRight', 'download']
import { SlideToolbar } from 'react-image-previewer/ui'
 
<SlideToolbar {...props} items={[ 'arrowLeft', 'countText', 'arrowRight', 'download']} />

除了通过 key 来使用他们外,您可以使用单独的按钮。

import { SlideArrowLeft, SlideArrowRight, SlideCountText, SlideDownload } from 'react-image-previewer/ui'
 
<>
  <Tooltip content="prev"><SlideArrowLeft {...props} /></Tooltip>
  <Tooltip content="next"><SlideArrowRight {...props} /></Tooltip>
  <SlideCountText {...props} className="count-text" />
  <SlideDownload {...props} className="download" />
<>

适合拖拽模式的工具栏

react-image-previewer/ui 提供了一个适合滑动模式的工具栏 DragToolbar,它的默认位置在正下方

提供了11个按钮类型

  • arrowLeft 左箭头,点击切换到上一张图片
  • countText 用于显示当前图片的索引 2/5
  • arrowRight 右箭头,点击切换到下一张图片
  • zoomOut 放大,点击执行放大操作
  • scaleCount 显示当前缩放比例 45%
  • zoomIn 缩小,点击执行缩小操作
  • oneToOne 缩放比例调整为图片原始大小 100%
  • download 下载按钮,点击下载当前图片
  • rotateLeft 左旋转,点击向左旋转 90
  • rotateRight 右旋转,点击向右旋转 90
  • split 分割线,用于分割按钮组合

放大/缩放设置了递增比例

  • scale < 0.5 时,每次放大 0.05
  • scale < 1.5 时,每次放大 0.1
  • scale < 3 时,每次放大 0.25
  • scale < 6 时,每次放大 0.5
import { DragToolbar, CloseButton } from 'react-image-previewer/ui'
 
<PhotoProvider
  overlayRender={props => {
    return (
      <>
        <DragToolbar {...props} />
        <CloseButton onClick={props.onClose} />
      </>
    )
  }}
/>

可选项:

  • items 设置工具栏按钮,默认为 [ 'arrowLeft', 'countText', 'arrowRight', 'split', 'zoomOut', 'scaleCount', 'zoomIn', 'oneToOne', 'split', 'download', 'split', 'rotateLeft', 'rotateRight']
  • children 可以追加子节点到工具栏根节点
  • className 设置额外的样式名称

除了使用 key 来设置按钮类型,还可以自由配置工具栏按钮对象,或者覆盖默认内置按钮组件

import { DragStyledItem, DragStyledLabel, DragDownload, DragScaleCount } from 'react-image-previewer/ui'
 
<DragToolbar {...props} items={
  [
    'arrowLeft', 'countText', 'arrowRight',
    'split',
    {
      key: 'download',
      component: props => {
        return <Tooltip><DragDownload {...props} /></Tooltip>
      }
    },
    {
      key: 'scaleText',
      component: props => {
        return <DragScaleCount {...props} />{ 100 * props.scale }</DragScaleCount>
      }
    },
    {
      key: 'dog',
      component: ({ index }) => {
        return (
          <DragStyledItem
            onClick={() => {
              setImages(prev => {
                const result = [...prev]
                result.splice(index, 1, dog.src)
                return result
              })
            }}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="1em"
              height="1em"
              viewBox="0 0 768 768"
              fill="currentColor"
            >
              <path d="M384 559.5c-75 0-138-45-163.5-111h327c-25.5 66-88.5 111-163.5 111zM271.5 352.5c-27 0-48-21-48-48s21-48 48-48 48 21 48 48-21 48-48 48zM496.5 352.5c-27 0-48-21-48-48s21-48 48-48 48 21 48 48-21 48-48 48zM384 640.5c141 0 256.5-115.5 256.5-256.5s-115.5-256.5-256.5-256.5-256.5 115.5-256.5 256.5 115.5 256.5 256.5 256.5zM384 64.5c177 0 319.5 142.5 319.5 319.5s-142.5 319.5-319.5 319.5-319.5-142.5-319.5-319.5 142.5-319.5 319.5-319.5z" />
            </svg>
          </DragStyledItem>
        )
      },
    },
    {
      key: 'label',
      component: ({ loading }) => {
        return (
          <DragStyledLabel>
            I`m {loading ? 'loading' : 'loaded'}
          </DragStyledLabel>
        )
      }
    }
  ]
} />
Last updated on 2023年1月1日