npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

right-image-preview

v0.2.0

Published

A dependency-free React image preview component with Lightroom-style discrete zoom stops, multi-group navigation, flip/rotate, auto-fade controls, and full keyboard support.

Readme

right-image-preview

English · 中文

无 UI 库依赖的 React 图片预览组件,原生支持固定档位缩放(Lightroom 式)、多图/多组导航、翻转旋转、键盘快捷键与自动渐隐控件。


✨ 特性

| 能力 | 说明 | |------|------| | Fit / Native 双模式 | fit 以 contain 语义完整显示图片;native 以原始像素为 100% 基准 | | 固定档位缩放 | 放大/缩小只在离散档位间跳转(默认 10 %–200 %);可用 stops 自定义 | | 缩放输入 | 可输入正整数 %;工具栏提交值会限制在最大档位ref 调用不限制) | | 多图 / 多组导航 | 支持单组图片列表,也支持按文件夹/分组组织的多组图片 | | 翻转 & 旋转 | 水平/垂直翻转,90° 顺/逆时针旋转,带 CSS 动画 | | 缩放锁定 | 切换图片时可选择保留或重置缩放状态 | | 智能侧边箭头 | 不可导航时箭头完全隐藏;组边界自动变为跳组按钮(双箭头) | | 控件自动渐隐 | 3 秒无操作后控件渐隐至约 10% 透明度,任意活动立即恢复 | | 导航小地图 | 主图溢出视口时右下角缩略图 + 可拖视口框;可通过 showMinimap 关闭 | | 小地图独立图源 | 每条 ImageItem(及单图 src 模式)可设 minimapSrc / minimap,用小缩略图或自定义节点;默认仍用主图 src | | 界面语言 | language 内置 英文简体中文enzhzh-CN 等) | | 丰富的键盘快捷键 | Esc / ±方向键 / Space / PageUp-Down / Ctrl+方向键 | | 可访问性 | role="dialog" + aria-modal,所有按钮带 aria-label,焦点管理 | | TypeScript 一等类型 | 完整类型导出,forwardRef 支持命令式 ref API | | 零生产依赖 | 仅依赖 React,无任何第三方 UI 库 | | 兼容性:React 17+ | 兼容 react / react-dom ≥ 17,推荐 18+(滚轮多档时原生 flushSync 体验最佳) |


快速开始

在业务项目中使用

npm install right-image-preview

克隆本仓库(演示页、测试、参与开发)

npm install
npm run dev       # 浏览器打开 http://localhost:5173
npm test
npm run build     # 演示站的 Vite 生产构建

浏览器访问 http://localhost:5173,页面右上角可切换 EN / 中文

  • Demo 1:单组相册,点击遮罩关闭,无翻转按钮
  • Demo 2:多文件夹分组,侧边箭头,含翻转按钮
  • Demo 3:本地高分辨率样张(滚轮/平移体验)

基本用法

import { ImagePreview } from 'right-image-preview';

// 单张图片
<ImagePreview
  src="/photo.jpg"
  visible={open}
  onClose={() => setOpen(false)}
/>

// 多图列表
<ImagePreview
  images={[
    { src: '/a.jpg', name: 'a.jpg' },
    { src: '/b.jpg', name: 'b.jpg' },
  ]}
  visible={open}
  onClose={() => setOpen(false)}
  wheelEnabled
  doubleClickEnabled
  closeOnMaskClick
/>

// 多组(文件夹)图片
<ImagePreview
  groupedImages={[
    {
      name: '旅行/',
      images: [
        { id: 'travel/a', src: '/a.jpg', name: 'a.jpg' },
        { src: '/b.jpg', name: 'b.jpg' },
        { src: '/c.jpg', name: 'c.jpg' },
      ],
    },
    {
      name: '活动/',
      images: [
        { src: '/d.jpg', name: 'd.jpg' },
        { src: '/e.jpg', name: 'e.jpg' },
        { src: '/f.jpg', name: 'f.jpg' },
      ],
    },
  ]}
  visible={open}
  onClose={() => setOpen(false)}
  arrows="side"
  showFlip
/>

API

Props

| Prop | 类型 | 默认值 | 说明 | |------|------|--------|------| | src | string | — | 单张图片 URL(提供 images 或非空 groupedImages 时忽略) | | minimapSrc | string | — | 仅单图:小地图瓦片 URL(默认同主图 src);设 minimap 时忽略 | | minimap | React.ReactNode | — | 仅单图:自定义小地图(覆盖 minimapSrc) | | images | ImageItem[] | — | 扁平多图列表(高于 src);非空 groupedImages 时忽略(开发环境若同时传入可能 console.warn);每项可带 minimapSrc / minimap | | groupedImages | ImageGroup[] | — | 文件夹式分组;按顺序拼接各组的 images;优先级高于 imagessrc | | visible | boolean | true | 控制预览显示/隐藏 | | defaultGroupedSelection | { defaultGroupIndex, defaultIndexInGroup } | — | groupedImages 模式下的初始图(组下标只计非空组);优先于 defaultIndex | | defaultIndex | number | 0 | 扁平列表中的初始下标;与分组同时传入 defaultGroupedSelection 时忽略 | | stops | number[] | [10,25,50,75,100,150,200] | Native zoom 档位(%,升序);需要更高上限请传入更长列表 | | initialMode | 'fit' \| 'native' | 'fit' | 初始缩放模式 | | initialNativePercent | number | 第一档 | initialMode='native' 时的初始比例 | | firstZoomInStrategy | 'above-fit' \| 'first-stop' \| 'hundred' | 'above-fit' | 从 Fit 首次放大时的入档策略 | | zoomOutBelowMinBehaviour | 'fit' \| 'noop' | 'noop' | 缩小到最小档以下的行为 | | zoomInAtMaxBehaviour | 'noop' \| 'notify' | 'noop' | 放大到最大档时的行为 | | wheelEnabled | boolean | true | 是否启用滚轮缩放 | | doubleClickEnabled | boolean | true | 双击切换 Fit ↔ 100% | | switchImageResetZoom | boolean | true | 切图时是否重置缩放(锁定时被覆盖) | | switchImageResetTransform | boolean | false | 切图时是否重置翻转/旋转 | | fitResetPan | boolean | true | 切回 Fit 时是否归零平移 | | showFlip | boolean | false | 是否显示翻转按钮 | | arrows | 'both' \| 'side' \| 'toolbar' \| 'none' | 'both' | 仅控制两侧箭头;非空 groupedImages 时工具栏上一张/下一张始终显示 | | initialZoomLocked | boolean | false | 初始是否锁定缩放 | | closeOnMaskClick | boolean | false | 点击遮罩区域是否关闭预览 | | onClose | () => void | — | 关闭回调 | | onZoomChange | (state: ZoomState) => void | — | 缩放状态变化回调 | | onIndexChange | (index: number) => void | — | 图片索引变化回调 | | onMaxStopReached | () => void | — | 到达最大档位回调(需配合 'notify') |

arrows 取值说明

| 值 | 效果 | |----|------| | 'both' | 两侧箭头 + 扁平列表时工具栏上一张/下一张(默认) | | 'side' | 仅两侧箭头;扁平列表时工具栏仍有上一张/下一张,序号在中间 | | 'toolbar' | 仅工具栏上一张/下一张;无两侧箭头 | | 'none' | 无两侧箭头;键盘 ← → 仍可用;扁平列表时工具栏仍有上一张/下一张与序号 |

传入非空 groupedImages 时,工具栏上一张/下一张始终显示;本表只约束两侧箭头。

类型定义

interface ImageItem {
  id?: string;   // 稳定主键(如路径);身份识别请优先于 `name`
  src: string;
  alt?: string;
  name?: string; // 工具栏信息栏显示的文件名
  minimapSrc?: string;
  minimap?: React.ReactNode;
}

interface ImageGroup {
  id?: string;   // 可选稳定主键(如目录路径)
  name: string;  // 组名,显示在文件名下方
  images: ImageItem[];
}

interface DefaultGroupedSelection {
  defaultGroupIndex: number;   // 仅统计非空组,顺序同 `groupedImages`
  defaultIndexInGroup: number; // 在该组 `images` 内的 0-based 下标
}

interface ZoomState {
  mode: 'fit' | 'native';
  nativePercent: number;
  fitEquivalentNativePercent?: number; // 供 UI 显示"适应 ≈ xx%"
}

包内还导出 resolvePreviewImagesflattenGroupedImagesresolveDefaultGroupedFlatIndexFlattenedGroupSliceDefaultGroupedSelection,便于在组件外复用相同的扁平列表与组内下标范围。

Ref API

const ref = useRef<ImagePreviewRef>(null);

interface ImagePreviewRef {
  // 缩放
  zoomIn(): void;
  zoomOut(): void;
  fit(): void;
  setNative(percent: number): void; // 任意正数(不截断;工具栏输入会限制在最大档位)

  // 变换
  rotateCW(): void;
  rotateCCW(): void;
  flipHorizontal(): void;
  flipVertical(): void;

  // 导航
  next(): void;
  prev(): void;
  nextGroup(): void;
  prevGroup(): void;

  // 状态读取
  getState(): ZoomState;
}

键盘快捷键

| 按键 | 行为 | |------|------| | Esc | 关闭预览 | | + / = / | 放大一档 | | - / | 缩小一档 | | 0 | 适应视口(Fit) | | 1 | 原图 100% | | Space | 切换 Fit ↔ 100% | | / | 上一张 / 下一张 | | Ctrl/⌘ + ← | 逆时针旋转 90° | | Ctrl/⌘ + → | 顺时针旋转 90° | | PageUp | 跳到上一组第一张(需非空 groupedImages) | | PageDown | 跳到下一组第一张(需非空 groupedImages) |

任意按键操作均会重置控件自动渐隐计时器。


项目结构

src/
  components/ImagePreview/
    types.ts              # TypeScript 类型定义
    flattenGroupedImages.ts  # resolvePreviewImages / flattenGroupedImages 辅助函数
    useZoomState.ts       # 缩放状态机 Hook(纯逻辑,无 DOM)
    useImageTransform.ts  # 尺寸测量 + CSS transform 计算 + 拖拽平移
    Toolbar.tsx           # 底部工具栏
    ImagePreview.tsx      # 主组件(遮罩/键盘/滚轮/双击/渐隐)
    index.ts              # 公开导出
  App.tsx                 # 演示页外壳
  demos/                  # 各 Demo 与演示站文案(不打进 npm 包)
docs/
  api.md / api.zh-CN.md          # Props & Ref API 参考
  keyboard.md / keyboard.zh-CN.md # 键盘快捷键说明
  requirements.md                 # 需求迭代记录
tests/
  setup.ts                # Vitest + jsdom 配置
  useZoomState.test.ts    # 状态机单元测试
  ImagePreview.test.tsx   # 组件集成测试

缩放算法

fitScale   = min(containerW / naturalW, containerH / naturalH)
nativeScale = nativePercent / 100

CSS transform scale(fit)    = fitScale
CSS transform scale(native) = nativeScale × (naturalW / layoutW)
                              ≈ nativePercent / 100

fitEquivalentNativePercent    = fitScale × 100(供 UI 显示"适应 ≈ xx%")

后续迭代方向

  • 触控双指捏合手势(Pinch-to-zoom)
  • 图片预加载策略(前后各预加载 N 张)
  • 旋转 90°/270° 时的严格 1:1 约束(宽高调换)
  • 弹簧物理动画(缩放/平移更自然的惯性)

License

MIT © ZhangJian