组件 API
本库导出三个预览组件:
| 组件 | 场景 | 渲染方式 |
|---|---|---|
FilePreviewModal | 全屏弹窗预览 | Portal 挂载到 document.body,带半透明遮罩 |
FilePreviewEmbed | 内联嵌入到任意 div 容器 | 普通 DOM,填充父容器 |
FilePreviewContent | 底层组件,自定义包装 | 由你决定包装方式 |
FilePreviewModal 和 FilePreviewEmbed 都是基于底层 FilePreviewContent 的薄包装,核心功能(工具栏、缩放、旋转、导航、渲染器分发)完全一致。
FilePreviewModal
主要的文件预览模态框组件。
Props
files
- 类型:
PreviewFileInput[] - 必需: 是
- 描述: 要预览的文件列表,支持三种输入格式:
File对象(原生浏览器 File)PreviewFileLink对象(包含 name, url, type 等属性)string(HTTP URL)
tsx
// 方式 1: URL 字符串
const files1 = ['https://example.com/image.jpg']
// 方式 2: 文件对象
const files2 = [
{
name: 'document.pdf',
url: '/files/doc.pdf',
type: 'application/pdf'
}
]
// 方式 3: File 对象
const files3 = [new File(['content'], 'text.txt')]
// 方式 4: 混合使用
const files4 = [
'https://example.com/image.jpg',
{ name: 'doc.pdf', url: '/doc.pdf', type: 'application/pdf' },
fileObject
]currentIndex
- 类型:
number - 必需: 是
- 描述: 当前显示的文件索引(从 0 开始)
tsx
<FilePreviewModal currentIndex={0} ... />isOpen
- 类型:
boolean - 必需: 是
- 描述: 控制模态框的显示/隐藏状态
tsx
<FilePreviewModal isOpen={true} ... />onClose
- 类型:
() => void - 必需: 是
- 描述: 关闭模态框时的回调函数
tsx
<FilePreviewModal onClose={() => setIsOpen(false)} ... />onNavigate
- 类型:
(index: number) => void - 必需: 否
- 描述: 文件切换时的回调函数,参数为新的文件索引
tsx
<FilePreviewModal
onNavigate={(index) => {
console.log('切换到文件:', index)
setCurrentIndex(index)
}}
...
/>customRenderers
- 类型:
CustomRenderer[] - 必需: 否
- 描述: 自定义渲染器数组,用于扩展或覆盖默认的文件渲染逻辑
每个 CustomRenderer 对象包含:
test: (file: PreviewFile) => boolean- 文件匹配函数,返回true表示使用此渲染器render: (file: PreviewFile) => React.ReactNode- 渲染函数,返回要显示的 React 组件
自定义渲染器会优先于内置渲染器执行。如果多个自定义渲染器匹配同一文件,将使用第一个匹配的渲染器。
tsx
import type { CustomRenderer } from '@eternalheart/react-file-preview'
const customRenderers: CustomRenderer[] = [
{
// 为 JSON 文件添加格式化显示
test: (file) => file.name.endsWith('.json'),
render: (file) => (
<div className="p-8">
<pre className="bg-gray-900 text-white p-4 rounded">
<JsonViewer url={file.url} />
</pre>
</div>
),
},
{
// 为特定 MIME 类型添加自定义渲染
test: (file) => file.type === 'application/x-custom',
render: (file) => <CustomFileViewer file={file} />,
},
]
<FilePreviewModal
customRenderers={customRenderers}
...
/>locale
- 类型:
Locale('zh-CN' | 'en-US' | string) - 必需: 否
- 默认值:
'zh-CN' - 描述: 界面语言。内置支持
'zh-CN'(中文)和'en-US'(英文),也可传入任意 locale 字符串并通过messagesprop 提供字典。
tsx
<FilePreviewModal locale="en-US" ... />messages
- 类型:
Partial<Record<Locale, Partial<Messages>>> - 必需: 否
- 描述: 用户自定义翻译字典。浅合并到对应语言的内置字典之上,可覆盖任何翻译值。详见 国际化指南。
tsx
<FilePreviewModal
locale="en-US"
messages={{ 'en-US': { 'toolbar.zoom_in': 'ZOOM++' } }}
...
/>完整示例
tsx
import { useState } from 'react'
import { FilePreviewModal } from '@eternalheart/react-file-preview'
import type { PreviewFileInput } from '@eternalheart/react-file-preview'
import '@eternalheart/react-file-preview/style.css'
function App() {
const [isOpen, setIsOpen] = useState(false)
const [currentIndex, setCurrentIndex] = useState(0)
const files: PreviewFileInput[] = [
'https://example.com/image.jpg',
{
name: 'document.pdf',
url: 'https://example.com/document.pdf',
type: 'application/pdf'
}
]
return (
<>
<button onClick={() => setIsOpen(true)}>
打开预览
</button>
<FilePreviewModal
files={files}
currentIndex={currentIndex}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onNavigate={setCurrentIndex}
/>
</>
)
}FilePreviewEmbed
嵌入式文件预览组件。与 FilePreviewModal 共用同一套渲染逻辑,但以内联方式渲染到你指定的容器内,而不是全屏弹出。
适用场景:
- 详情页中的文件预览面板
- 左右分栏的文件浏览器
- 仪表盘卡片里的文件预览
- 任何不希望遮挡页面其他内容的场景
基础用法
tsx
import { FilePreviewEmbed } from '@eternalheart/react-file-preview'
import '@eternalheart/react-file-preview/style.css'
function Panel() {
const files = [
'https://example.com/image.jpg',
{ name: 'doc.pdf', url: '/doc.pdf', type: 'application/pdf' }
]
return (
// 嵌入预览默认填充父容器,父容器需要有明确的高度
<div style={{ width: '100%', height: 520 }}>
<FilePreviewEmbed files={files} />
</div>
)
}Props
| 属性 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
files | PreviewFileInput[] | ✅ | - | 要预览的文件列表,格式与 FilePreviewModal.files 一致 |
currentIndex | number | ❌ | 0 | 当前显示的文件索引 |
onNavigate | (index: number) => void | ❌ | - | 切换文件时的回调 |
customRenderers | CustomRenderer[] | ❌ | - | 自定义渲染器数组 |
width | number | string | ❌ | '100%' | 容器宽度,填充父容器或显式指定 |
height | number | string | ❌ | '100%' | 容器高度 |
className | string | ❌ | - | 根节点额外 className |
style | CSSProperties | ❌ | - | 根节点额外内联样式 |
尺寸说明
FilePreviewEmbed 默认使用 width: 100%; height: 100% 填充父容器,因此 父容器必须具有明确的高度(如 height: 520px 或通过 flex/grid 布局给定高度),否则组件会塌陷为 0 高度。
也可以直接在组件上显式指定尺寸:
tsx
<FilePreviewEmbed files={files} width={800} height={500} />与 FilePreviewModal 的区别
| 特性 | FilePreviewModal | FilePreviewEmbed |
|---|---|---|
| 渲染位置 | Portal 到 document.body | 组件树内联 |
| 背景遮罩 | 半透明黑色全屏遮罩 | 无 |
isOpen / onClose | 必填 | 不存在,由父组件控制是否渲染 |
| 工具栏"关闭"按钮 | ✅ 显示 | ❌ 不显示 |
Esc 键关闭 | ✅ 支持(全局监听) | ❌ 不支持 |
| ← → 键导航 | ✅ 全局 window 监听 | ✅ 仅容器 focus 时响应 |
| body 滚动锁定 | ✅ 打开时锁定 | ❌ 不锁定 |
| z-index | 9999(最高层) | 跟随组件树,由外层决定 |
完整示例
tsx
import { useState } from 'react'
import { FilePreviewEmbed } from '@eternalheart/react-file-preview'
import type { PreviewFileInput } from '@eternalheart/react-file-preview'
import '@eternalheart/react-file-preview/style.css'
function DetailPanel() {
const [index, setIndex] = useState(0)
const files: PreviewFileInput[] = [
'https://example.com/image.jpg',
{ name: 'document.pdf', url: 'https://example.com/doc.pdf', type: 'application/pdf' },
{ name: 'data.xlsx', url: 'https://example.com/data.xlsx', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
]
return (
<div className="grid grid-cols-2 gap-4">
<aside>
{files.map((f, i) => (
<button key={i} onClick={() => setIndex(i)}>
文件 {i + 1}
</button>
))}
</aside>
<div style={{ height: 600 }}>
<FilePreviewEmbed
files={files}
currentIndex={index}
onNavigate={setIndex}
/>
</div>
</div>
)
}FilePreviewContent
底层文件预览组件。FilePreviewModal 和 FilePreviewEmbed 都是基于它的薄包装。
当你需要构建完全自定义的外壳(例如自定义抽屉、浮层、Tab 切换容器等)时,可以直接使用它。
Props
| 属性 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
files | PreviewFileInput[] | ✅ | - | 文件列表 |
currentIndex | number | ✅ | - | 当前文件索引 |
onNavigate | (index: number) => void | ❌ | - | 导航回调 |
customRenderers | CustomRenderer[] | ❌ | - | 自定义渲染器 |
mode | 'modal' | 'embed' | ❌ | 'modal' | 运行模式,控制细节差异 |
onClose | () => void | ❌ | - | 关闭回调,仅在 mode='modal' 时显示关闭按钮 |
mode 差异:
mode='modal': 显示工具栏"关闭"按钮、全局监听键盘(Esc 关闭、←/→ 导航)mode='embed': 不显示"关闭"按钮、键盘事件绑定到组件根节点(需 focus),不监听 Esc
使用示例
tsx
import { FilePreviewContent } from '@eternalheart/react-file-preview'
function CustomDrawer({ files, currentIndex, onNavigate, onClose }) {
return (
<div className="my-drawer-wrapper" style={{ width: 720, height: '100vh' }}>
<div className="my-drawer-header">
<button onClick={onClose}>自定义关闭按钮</button>
</div>
<div style={{ flex: 1 }}>
<FilePreviewContent
mode="embed"
files={files}
currentIndex={currentIndex}
onNavigate={onNavigate}
/>
</div>
</div>
)
}渲染机制
Portal 渲染
FilePreviewModal 使用 React Portal (createPortal) 将模态框渲染到 document.body,而不是在组件树的当前位置渲染。
优势:
最高层级: 模态框的
z-index设置为9999,确保始终显示在页面最上层样式隔离: 不受父元素的 CSS 样式影响(如
overflow: hidden、transform、filter等)定位准确: 模态框使用
fixed定位相对于视口,不受父元素定位上下文影响无需配置: 开箱即用,无需担心层级和定位问题
示例:
tsx
// 即使在复杂的嵌套结构中使用也没问题
<div style={{ position: 'relative', overflow: 'hidden', zIndex: 100 }}>
<div style={{ transform: 'scale(0.9)' }}>
<FilePreviewModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
files={files}
currentIndex={0}
/>
</div>
</div>模态框会自动渲染到 document.body,完全不受上述样式影响。
功能特性
工具栏控制
根据文件类型,组件提供不同的工具栏控制:
图片预览
- 缩放控制: 放大/缩小按钮(步进 10%,范围 0.01x - 10x),鼠标滚轮缩放(步进 0.05)
- 旋转控制: 顺时针/逆时针旋转(每次 90°)
- 拖拽移动: 缩放后可拖拽图片
- 重置按钮: 恢复到原始状态
PDF 预览
- 缩放控制: 放大/缩小按钮(范围 0.01x - 10x)
- 页面导航: 上一页/下一页按钮
- 页码显示: 当前页/总页数
- 连续滚动: 支持滚动浏览所有页面
Office 文档
- Word (DOCX): 通过 mammoth 库渲染为 HTML
- Excel (XLSX): 多工作表切换,表格渲染
- PowerPoint (PPT/PPTX): 平铺/幻灯片两种显示模式,16:9 宽高比
Outlook 邮件
- MSG: 解析邮件头信息(发件人、收件人、主题、日期)
- 邮件正文渲染
- 附件列表展示
视频
- 基于 Video.js 播放器
- 支持播放控制、音量调节、进度条、全屏播放
- 支持 MP4、WebM、OGG、MOV、AVI、MKV 等格式
音频
- 自定义播放器界面(紫粉渐变主题)
- 播放/暂停控制、进度条、音量调节
- 快进/快退按钮(±10 秒)
- 支持 MP3、WAV、OGG、M4A、AAC、FLAC 格式
Markdown
- 实时渲染 Markdown 内容
- 支持 GFM (GitHub Flavored Markdown)
- 支持表格、代码块、任务列表等
代码文件
- 语法高亮显示(40+ 种语言)
- 自动检测语言类型
- VS Code Dark+ 主题
通用功能
- 文件导航: 上一个/下一个文件按钮
- 下载功能: 下载当前文件
- 关闭操作: 关闭按钮、ESC 键、点击背景
键盘快捷键
| 按键 | 功能 |
|---|---|
Escape | 关闭预览 |
← | 上一个文件 |
→ | 下一个文件 |
响应式设计
- 桌面端: 完整工具栏和控制按钮
- 移动端: 优化的触摸操作和简化的 UI
- 平板: 介于两者之间的体验
触摸手势
- 左滑 (>50px): 切换到下一个文件
- 右滑 (>50px): 切换到上一个文件
动画效果
基于 Framer Motion 提供入场/退场动画,包括模态框、导航按钮和工具栏的过渡动画。
滚动锁定
预览打开时自动锁定页面滚动,关闭后恢复,并自动处理滚动条宽度补偿。