@eternalheart/react-file-preview
v1.6.7
Published
A modern, feature-rich file preview component for React with support for images, videos, audio, PDFs, Office documents (Word, Excel, PowerPoint), Markdown, and code files.
Maintainers
Readme
React File Preview 


English | 简体中文
一个现代化、功能丰富的 React 文件预览组件,支持图片、视频、音频、PDF、Office 文档(Word、Excel、PowerPoint)、Markdown 和代码文件预览。
特性
现代化 UI - 简洁现代的界面设计,流畅动画 多格式支持 - 支持 20+ 种文件格式 两种展示模式 - 全屏弹窗 或 嵌入式内联预览 强大的图片查看器 - 缩放、旋转、拖拽、滚轮缩放 自定义视频播放器 - 基于 Video.js,支持多种视频格式 自定义音频播放器 - 精美的音频控制界面 PDF 查看器 - 支持分页浏览 Office 文档支持 - Word、Excel、PowerPoint 文件预览 Markdown 渲染 - 支持 GitHub Flavored Markdown 代码高亮 - 支持 40+ 种编程语言 流畅动画 - 基于 Framer Motion 响应式设计 - 适配各种屏幕尺寸 键盘导航 - 支持方向键、Home、End 和 ESC 键
安装
# 使用 npm
npm install @eternalheart/react-file-preview
# 使用 yarn
yarn add @eternalheart/react-file-preview
# 使用 pnpm
pnpm add @eternalheart/react-file-preview重要提示: 你还需要导入 CSS 文件:
import '@eternalheart/react-file-preview/style.css';说明: PDF 预览所需的
pdfjs-dist依赖会自动安装,无需额外操作。
PDF.js 配置(可选)
如果你需要预览 PDF 文件,建议配置 PDF.js 使用本地静态文件以提高性能和稳定性:
方式 1: 使用 CDN(默认)
默认情况下,组件会自动使用 unpkg CDN 加载 PDF.js,无需额外配置。
方式 2: 使用本地静态文件(推荐用于生产环境)
- 将 PDF.js 文件复制到你的 public 目录:
# 从 node_modules 复制 PDF.js 文件到 public 目录
cp -r node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdfjs/
cp -r node_modules/pdfjs-dist/cmaps public/pdfjs/- 在应用入口配置 PDF.js:
import { configurePdfjs } from '@eternalheart/react-file-preview';
// 配置使用本地静态文件
configurePdfjs({
workerSrc: '/pdfjs/pdf.worker.min.mjs',
cMapUrl: '/pdfjs/cmaps/',
cMapPacked: true
});使用 Vite 自动复制(推荐)
在 vite.config.ts 中配置自动复制:
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [
{
src: 'node_modules/pdfjs-dist/build/pdf.worker.min.mjs',
dest: 'pdfjs'
},
{
src: 'node_modules/pdfjs-dist/cmaps',
dest: 'pdfjs'
}
]
})
]
});然后在应用入口配置:
import { configurePdfjs } from '@eternalheart/react-file-preview';
configurePdfjs({
workerSrc: '/pdfjs/pdf.worker.min.mjs',
cMapUrl: '/pdfjs/cmaps/',
cMapPacked: true
});Vite 打包提示(AVIF 解码器)
如果你的项目使用 Vite 打包,且项目里安装了 @jsquash/avif(直接安装或被其它依赖间接引入),生产构建时可能报错:
[commonjs--resolver] Invalid value "iife" for option "worker.format"
- UMD and IIFE output formats are not supported for code-splitting builds.
file: .../@jsquash/avif/codec/enc/avif_enc_mt.js原因:@jsquash/avif 内部包含一个使用代码分割的多线程 worker,而 Vite 默认的 worker.format 是 'iife',不支持多 chunk 拆分。
解决方法 —— 在你的 vite.config.ts 中加入:
export default defineConfig({
// ... 原有配置
worker: {
format: 'es',
},
});'es' 会产生 module worker(type: 'module'),现代浏览器全部支持,且兼容代码分割。
说明:
@jsquash/avif仅在浏览器不原生支持 AVIF 时作为兜底使用(Chrome 85+、Firefox 93+、Safari 16+ 均已原生支持)。如果你的目标浏览器都覆盖原生支持范围,也可以直接从依赖中移除@jsquash/avif。
快速开始
📖 第一次使用? 查看 快速开始指南 获取 5 分钟入门教程!
基础用法
import { FilePreviewModal } from 'react-file-preview';
import 'react-file-preview/style.css';
import { useState } from 'react';
function App() {
const [files, setFiles] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const handleFileSelect = (file: File) => {
// 方法 1: 直接传入 File 对象(推荐)
setFiles([file]);
setCurrentIndex(0);
setIsOpen(true);
};
return (
<>
<input
type="file"
onChange={(e) => e.target.files?.[0] && handleFileSelect(e.target.files[0])}
/>
<FilePreviewModal
files={files}
currentIndex={currentIndex}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onNavigate={setCurrentIndex}
/>
</>
);
}多种输入类型
组件支持三种类型的文件输入:
import { FilePreviewModal, PreviewFileInput } from '@eternalheart/react-file-preview';
import '@eternalheart/react-file-preview/style.css';
function App() {
// 假设 file1 来自 File API:<input type="file">、拖拽、
// 剪贴板粘贴,或 fetch().then(r => r.blob())
const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });
const files: PreviewFileInput[] = [
// 1. 原生 File 对象(组件卸载时自动释放)
file1,
// 2. HTTP URL 字符串(按需加载)
'https://example.com/image.jpg',
// 3. 带元数据的文件对象(推荐用于远程资源)
{
name: 'document.pdf',
type: 'application/pdf',
url: '/path/to/document.pdf',
size: 1024,
},
];
// 内存提示: 如果你通过 URL.createObjectURL() 生成 URL,
// 文件移除时请调用 URL.revokeObjectURL() 避免内存泄漏。
return (
<FilePreviewModal
files={files}
currentIndex={0}
isOpen={true}
onClose={() => {}}
/>
);
}嵌入模式 (FilePreviewEmbed)
除了全屏弹窗,组件库还提供了嵌入式变体,可以将预览内联渲染到任意 div 容器中,适合详情面板、左右分栏布局、仪表盘等场景。
import { FilePreviewEmbed } from '@eternalheart/react-file-preview';
import '@eternalheart/react-file-preview/style.css';
import { useState } from 'react';
function InlinePreview() {
const [index, setIndex] = useState(0);
const files = [
'https://example.com/image.jpg',
{ name: 'document.pdf', type: 'application/pdf', url: '/doc.pdf' },
];
return (
// 嵌入式预览默认填充父容器
<div style={{ width: '100%', height: 520 }}>
<FilePreviewEmbed
files={files}
currentIndex={index}
onNavigate={setIndex}
/>
</div>
);
}与 FilePreviewModal 的区别:
- 不使用 Portal、无全屏遮罩、没有
isOpen/onClose - 不显示关闭按钮
- 键盘导航 (←/→/Home/End) 作用域限定在嵌入容器内 (基于 focus)
- 尺寸默认
width: 100%; height: 100%,可通过width/heightprops 覆盖
// 显式指定尺寸
<FilePreviewEmbed files={files} width={800} height={500} />使用示例
预览 PowerPoint 文件
import { FilePreviewModal } from '@eternalheart/react-file-preview';
import { useState } from 'react';
function PptPreview() {
const [isOpen, setIsOpen] = useState(false);
const pptFile = {
name: 'presentation.pptx',
type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
url: '/path/to/your/presentation.pptx',
};
return (
<>
<button onClick={() => setIsOpen(true)}>
预览 PPT
</button>
<FilePreviewModal
files={[pptFile]}
currentIndex={0}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
/>
</>
);
}预览多个文件
const files = [
{ name: 'image.jpg', type: 'image/jpeg', url: '/path/to/image.jpg' },
{ name: 'document.pdf', type: 'application/pdf', url: '/path/to/document.pdf' },
{ name: 'presentation.pptx', type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', url: '/path/to/presentation.pptx' },
{ name: 'spreadsheet.xlsx', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', url: '/path/to/spreadsheet.xlsx' },
];
<FilePreviewModal
files={files}
currentIndex={0}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onNavigate={setCurrentIndex}
/>支持的文件格式
图片
- 格式: JPG, PNG, GIF, WebP, SVG, BMP, ICO, HEIC/HEIF, AVIF, TIFF, RAW, PSD, JPEG 2000
- 功能: 缩放 (0.01x - 10x)、旋转、拖拽、滚轮缩放、双击重置、多页 TIFF
- 解码: HEIC/RAW/PSD 优先通过 Worker 解码,失败时回退主线程;其他高级格式按需加载
视频
- 格式: MP4, WebM, OGG, MOV, AVI, MKV, M4V, 3GP, FLV
- 功能: 自定义播放器、进度控制、音量调节、全屏播放
音频
- 格式: MP3, WAV, OGG, M4A, AAC, FLAC
- 功能: 自定义播放器、进度条、音量控制、快进/快退
文档
- PDF: 分页浏览、缩放
- Word: DOCX 与旧版 DOC(97–2003)支持
- Excel: XLSX 与旧版 XLS(97–2003)支持
- PowerPoint: PPTX 与旧版 PPT(97–2003)幻灯片预览
字体
- 格式: TTF, OTF, WOFF, WOFF2
- 功能: 字体元数据(字体家族、设计师、版本)、字符集预览、自定义文本输入、多字号展示
CAD / 3D 模型
- 格式: DXF, STL, OBJ, GLTF, GLB
- 功能: 交互式 3D 查看器(旋转/缩放/平移)、线框/实体切换、网格与坐标轴显示、自动居中
代码 & 文本
- Markdown: GitHub Flavored Markdown,代码高亮
- 代码文件: Vue、Svelte、Astro、JS/TS、Dart、GraphQL、Protobuf、Prisma、Terraform、PowerShell、Scala 等 40+ 种语言
- 配置 / 日志: YAML, TOML, INI, ENV, PROPERTIES, BAT, CMD, TEX, MAP, LOG, DIFF, PATCH 等
结构化数据
- JSON: 自动格式化 + 语法高亮
- CSV / TSV: 零依赖解析,表格视图 + 行列统计
- XML:
DOMParser校验 + 自动缩进 + 语法高亮
字幕 / 歌词
- SRT / WebVTT: 零依赖解析,结构化 cue 列表(索引、时间区间、文本)
- LRC / Enhanced LRC: 歌词文件,
[mm:ss.xx]行时间戳(ELRC 额外支持行内<mm:ss.xx>逐字时间戳),自动解析[ti:][ar:][al:]等元数据 - ASS / SSA: Advanced SubStation Alpha,提取 Dialogue 事件,自动剥离
\N\h与{...}样式覆盖码,展示 Style 标签 - TTML / DFXP: W3C / Apple Music 使用的 XML 字幕,支持
begin/end/dur与<br/>
压缩包
- ZIP: 树形目录 + 内嵌预览文本/代码/图片,其他类型可下载导出
Outlook 邮件
- MSG: 邮件头、正文、附件列表
电子书
- EPUB: 章节导航、翻页
⚠️ 功能限制与性能说明
支持等级
✅ 完全支持(生产可用)
- 图片(JPG, PNG, GIF, WebP, SVG, BMP, ICO)
- 视频(MP4, WebM, OGG)
- 音频(MP3, WAV, OGG)
- Markdown
- 代码文件(通过 Shiki 支持 40+ 种语言,按需加载)
- JSON, CSV, XML
⚠️ 部分支持(仅供预览)
- Office(DOCX, XLSX, PPTX): 基础布局和文本渲染。复杂格式(图表、宏、嵌入对象)可能无法准确渲染。
- ZIP: 目录树浏览 + 文本/代码/图片内联预览。大型压缩包(>100MB)可能导致性能问题。
- 字体(TTF, OTF, WOFF): 元数据 + 字符预览。不支持完整字体特性测试。
🧪 实验性支持
- MSG(Outlook 邮件): 邮件头和纯文本正文。复杂 HTML 正文可能无法正确渲染。
- EPUB: 基础章节导航。CSS 样式可能与原生阅读器有差异。不支持 DRM 保护文件。
- 字幕格式(SRT, ASS, TTML, LRC): 仅文本显示。不支持视频同步或高级样式。
性能边界
| 文件大小 | 状态 | 说明 | |---------|------|------| | < 50MB | ✅ 推荐 | 流畅的预览体验 | | 50-100MB | ⚠️ 可能卡顿 | 加载时 UI 可能无响应 | | > 100MB | ❌ 不推荐 | 可能超出浏览器内存限制 |
特殊情况:
- ZIP 压缩包: 性能取决于文件数量,而非仅体积
- Office 文档: 复杂文件(>200 页、大量图片)可能超时
- 代码高亮: >5MB 的文件可能需要 3-5 秒高亮时间
浏览器兼容性
最低要求:
- Chrome 90+ / Edge 90+
- Firefox 88+
- Safari 14+
已知限制:
- Safari iOS: 视频自动播放需要用户交互
- Firefox: AVIF 支持需要 Firefox 93+(已包含降级解码器)
- Office 格式: 不同浏览器渲染质量有差异
- EPUB: 旧版浏览器可能不支持某些 CSS 特性
API 参考
FilePreviewModal Props
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| files | PreviewFileInput[] | ✅ | 文件列表(支持 File 对象、文件对象或 URL 字符串) |
| currentIndex | number | ✅ | 当前文件索引 |
| isOpen | boolean | ✅ | 是否打开预览 |
| onClose | () => void | ✅ | 关闭回调 |
| onNavigate | (index: number) => void | ❌ | 导航回调 |
| customRenderers | CustomRenderer[] | ❌ | 自定义渲染器 |
| locale | Locale | ❌ | 界面语言(默认 'zh-CN',内置 'en-US') |
| messages | Partial<Record<Locale, Partial<Messages>>> | ❌ | 自定义翻译覆盖 |
| headless | boolean | ❌ | 无头模式,隐藏工具栏和导航箭头 |
| theme | Theme | ❌ | 主题模式: 'auto' \| 'dark' \| 'light'(默认 'dark') |
| showDownload | boolean | ❌ | 是否显示下载按钮(默认 true) |
| showClose | boolean | ❌ | 是否显示关闭按钮(modal 模式默认 true) |
| showNavigation | boolean | ❌ | 是否显示文件导航箭头(默认 true) |
| loopNavigation | boolean | ❌ | 是否循环导航文件(默认 false) |
FilePreviewEmbed Props
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| files | PreviewFileInput[] | ✅ | - | 文件列表 |
| currentIndex | number | ❌ | 0 | 当前文件索引 |
| onNavigate | (index: number) => void | ❌ | - | 导航回调 |
| customRenderers | CustomRenderer[] | ❌ | - | 自定义渲染器 |
| width | number \| string | ❌ | '100%' | 容器宽度 |
| height | number \| string | ❌ | '100%' | 容器高度 |
| className | string | ❌ | - | 根节点额外 className |
| style | CSSProperties | ❌ | - | 根节点额外内联样式 |
| locale | Locale | ❌ | 'zh-CN' | 界面语言('zh-CN' 或 'en-US') |
| messages | Partial<Record<Locale, Partial<Messages>>> | ❌ | - | 自定义翻译覆盖 |
| headless | boolean | ❌ | false | 无头模式,隐藏工具栏和导航箭头 |
| theme | Theme | ❌ | 'dark' | 主题模式: 'auto' \| 'dark' \| 'light' |
| showDownload | boolean | ❌ | true | 是否显示下载按钮 |
| showClose | boolean | ❌ | false | 是否显示关闭按钮(embed 模式默认 false) |
| showNavigation | boolean | ❌ | true | 是否显示文件导航箭头 |
| loopNavigation | boolean | ❌ | false | 是否循环导航文件 |
FilePreviewEmbed没有isOpen/onClose,若要显示/隐藏,请在父组件中条件渲染。关闭按钮默认隐藏,但可通过showClose启用。
FilePreviewContent(高级用法)
FilePreviewModal 和 FilePreviewEmbed 都是基于底层 FilePreviewContent 组件的薄包装。当你需要构建完全自定义的容器时,可以直接使用它:
import { FilePreviewContent } from '@eternalheart/react-file-preview';
<FilePreviewContent
mode="embed" // 或 "modal"
files={files}
currentIndex={index}
onNavigate={setIndex}
/>文件类型定义
// 支持三种文件输入类型
type PreviewFileInput = File | PreviewFileLink | string;
// 1. 原生 File 对象(浏览器 File API)
const file: File = ...;
// 2. 文件对象
interface PreviewFileLink {
id?: string; // 可选的唯一标识符
name: string; // 文件名
type: string; // MIME 类型
url: string; // 文件 URL (支持 blob URL 和 HTTP URL)
size?: number; // 文件大小(字节)
}
// 3. HTTP URL 字符串
const url: string = 'https://example.com/file.pdf';使用示例
// 方式 1: 使用原生 File 对象
const files = [file1, file2]; // File 对象数组
// 方式 2: 使用 HTTP URL 字符串
const files = [
'https://example.com/image.jpg',
'https://example.com/document.pdf',
];
// 方式 3: 使用文件对象
const files = [
{
name: 'presentation.pptx',
type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
url: '/path/to/presentation.pptx',
},
];
// 方式 4: 混合使用
const files = [
file1, // File 对象
'https://example.com/image.jpg', // URL 字符串
{ name: 'doc.pdf', type: 'application/pdf', url: '/doc.pdf' }, // 文件对象
];支持的 MIME 类型
Office 文档
- Word:
application/vnd.openxmlformats-officedocument.wordprocessingml.document(.docx) - Word (旧版):
application/msword(.doc) - Excel:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(.xlsx) - Excel (旧版):
application/vnd.ms-excel(.xls) - PowerPoint:
application/vnd.openxmlformats-officedocument.presentationml.presentation(.pptx) - PowerPoint (旧版):
application/vnd.ms-powerpoint(.ppt)
其他文档
- PDF:
application/pdf
字体
- TrueType:
application/x-font-ttf,font/ttf(.ttf) - OpenType:
application/x-font-otf,font/otf(.otf) - WOFF:
application/font-woff,font/woff(.woff) - WOFF2:
application/font-woff2,font/woff2(.woff2)
CAD / 3D 模型
- DXF:
application/dxf(.dxf) - STL:
model/stl,application/vnd.ms-pki.stl(.stl) - OBJ:
model/obj(.obj) - GLTF:
model/gltf+json(.gltf) - GLB:
model/gltf-binary(.glb)
媒体文件
- 图片:
image/jpeg,image/png,image/gif,image/webp,image/svg+xml, 等 - 视频:
video/mp4,video/webm,video/ogg, 等 - 音频:
audio/mpeg,audio/wav,audio/ogg, 等
文本文件
- Markdown: 文件扩展名
.md或.markdown - 代码: 根据文件扩展名自动识别 (
.js,.ts,.py,.java, 等) - 配置 / 日志:
.yaml,.yml,.toml,.ini,.conf,.env,.properties,.bat,.cmd,.tex,.map,.log,.diff,.patch - 纯文本:
text/plain
结构化数据
- JSON:
application/json(.json) - CSV / TSV:
text/csv(.csv),text/tab-separated-values(.tsv) - XML:
application/xml,text/xml(.xml)
字幕 / 歌词
- SRT:
application/x-subrip(.srt) - WebVTT:
text/vtt(.vtt) - LRC: (.lrc)
- Enhanced LRC: (.elrc)
- ASS / SSA: (.ass, .ssa)
- TTML / DFXP:
application/ttml+xml(.ttml, .dfxp)
压缩包
- ZIP:
application/zip,application/x-zip-compressed(.zip)
Outlook 邮件
- MSG:
application/vnd.ms-outlook(.msg)
电子书
- EPUB:
application/epub+zip(.epub)
国际化(i18n)
内置中文(默认)和英文,零外部依赖。
// 切换为英文
<FilePreviewModal files={files} locale="en-US" ... />
// 自定义覆盖某些翻译
<FilePreviewModal
files={files}
locale="en-US"
messages={{ 'en-US': { 'toolbar.zoom_in': 'Zoom ++' } }}
/>在自定义渲染器中可通过 useTranslator() hook 获取翻译函数。
自定义渲染器
本库支持自定义渲染器以处理内置不支持的文件类型。自定义渲染器可以可选地提供工具栏配置并集成到本库的架构中。
事件驱动的工具栏更新
自定义渲染器可以通过事件驱动机制实现实时工具栏更新:
优势:
- 实时更新:工具栏立即反映状态变化
- 更好的性能:无轮询开销或不必要的重新渲染
- 类型安全:完整的 TypeScript 接口支持
实现方式:
import { forwardRef, useImperativeHandle, useState, useEffect, useMemo, useCallback } from 'react';
import { ToolbarEventEmitter } from '@eternalheart/react-file-preview';
import type { RendererHandle, ToolbarGroup } from '@eternalheart/react-file-preview';
interface CustomRendererProps {
url: string;
onPageChange?: (current: number, total: number) => void;
}
export const CustomRenderer = forwardRef<RendererHandle, CustomRendererProps>((props, ref) => {
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(10);
const emitter = useMemo(() => new ToolbarEventEmitter(), []);
// 状态变化时通知工具栏
useEffect(() => {
emitter.notify();
props.onPageChange?.(currentPage, totalPages);
}, [currentPage, totalPages, emitter, props]);
const getToolbarGroups = useCallback((): ToolbarGroup[] => [
{
items: [
{
type: 'button',
icon: <ChevronLeft className="w-4 h-4" />,
tooltip: '上一页',
action: () => setCurrentPage(p => Math.max(1, p - 1)),
disabled: currentPage <= 1
},
{
type: 'text',
content: `${currentPage} / ${totalPages}`,
minWidth: '4rem'
},
{
type: 'button',
icon: <ChevronRight className="w-4 h-4" />,
tooltip: '下一页',
action: () => setCurrentPage(p => Math.min(totalPages, p + 1)),
disabled: currentPage >= totalPages
}
]
}
], [currentPage, totalPages]);
useImperativeHandle(ref, () => ({
getToolbarGroups,
onToolbarChange: (listener) => emitter.subscribe(listener)
}), [getToolbarGroups, emitter]);
return <div>你的自定义渲染器 UI</div>;
});主组件使用:
import { CustomRenderer } from './CustomRenderer';
<FilePreviewModal
files={files}
customRenderers={[
{
test: (file) => file.type === 'application/custom',
render: () => <CustomRenderer />
}
]}
/>自定义渲染器如需扩展顶层工具栏,应在 renderer 描述对象上实现 getToolbarGroups(file, ctx);onToolbarChange 是内置 RendererHandle 的内部机制。
Renderer 懒加载
所有内置渲染器通过 React.lazy 实现代码分割,以最小化主包体积并提升初始加载性能。
架构:
- 注册:渲染器在
src/renderers/lazy.tsx中注册,使用 named export 并通过React.lazy包装 - 加载:每个渲染器是独立的 chunk,按需加载
- 回退:
<Suspense>配合<RendererLoading />处理加载状态
每个渲染器都会输出为独立异步 chunk,并仅在需要时加载。
实现示例:
// src/renderers/lazy.tsx
import { lazy } from 'react';
import type { CustomRenderer as CustomRendererImpl } from './Custom';
export const CustomRenderer: Lazy<typeof CustomRendererImpl> = lazy(() =>
import('./Custom').then((m) => ({ default: m.CustomRenderer }))
);// src/FilePreviewContent.tsx
import { CustomRenderer } from './renderers/lazy'; // ✅ 懒加载导入
// 禁止: import { CustomRenderer } from './renderers/Custom'; // ❌ 直接导入会破坏代码分割
<Suspense fallback={<RendererLoading />}>
{fileType === 'custom' && <CustomRenderer ref={rendererRef} url={currentFile.url} />}
</Suspense>用于自定义渲染器:
如果你希望自定义渲染器也享受代码分割,可以使用相同的模式:
import { lazy, Suspense } from 'react';
const MyCustomRenderer = lazy(() => import('./MyCustomRenderer'));
<FilePreviewModal
files={files}
customRenderers={[
{
test: (file) => file.type === 'application/custom',
render: () => (
<Suspense fallback={<div>加载中...</div>}>
<MyCustomRenderer />
</Suspense>
)
}
]}
/>i18n 集成
自定义渲染器可以通过 useTranslator() hook 访问本库的 i18n 系统,实现一致的多语言支持。
架构:
- 字典源:
file-preview-core/src/i18n/messages/(zh-CN.ts、en-US.ts) - 禁止硬编码:所有用户可见文案必须使用翻译 key
- 自动切换语言:跟随
FilePreviewModal的localeprop
在自定义渲染器中使用:
import { useTranslator } from '@eternalheart/react-file-preview';
export const CustomRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
const t = useTranslator();
const [error, setError] = useState<string | null>(null);
if (error) {
return (
<div className="rfp-text-fg-primary">
{t('custom.load_failed')}: {error}
</div>
);
}
return (
<div>
<button>{t('common.download')}</button>
<span>{t('custom.loading')}</span>
</div>
);
});新增自定义翻译键:
对于自定义渲染器,通过 messages prop 扩展翻译(不要修改 node_modules 中的源文件):
<FilePreviewModal
files={files}
locale="en-US"
messages={{
'en-US': {
'custom.load_failed': 'Failed to load custom file',
'custom.file_size': 'File size: {size} KB'
},
'zh-CN': {
'custom.load_failed': '自定义文件加载失败',
'custom.file_size': '文件大小: {size} KB'
}
}}
customRenderers={[...]}
/>指南:
- 使用
<scope>.<snake_name>格式(如custom.load_failed、custom.parse_error) - 为所有启用的语言(
zh-CN和en-US)提供翻译 - 已有通用 key:
common.loading、common.download、common.close、toolbar.*
参数化翻译:
// 字典: 'custom.file_size': '文件大小: {size} KB'
t('custom.file_size', { size: 1024 }) // → "文件大小: 1024 KB"工具栏集成:
工具栏项也应使用翻译字符串:
const getToolbarGroups = useCallback((): ToolbarGroup[] => [
{
items: [
{
type: 'button',
icon: <Download className="rfp-w-4 rfp-h-4" />,
tooltip: t('common.download'), // ✅ 已翻译
action: handleDownload
}
]
}
], [t]);主题适配
自定义渲染器必须使用语义化颜色 token 以支持本库的 'auto' | 'dark' | 'light' 主题系统。
语义化 Token 系统:
所有颜色定义为 CSS 变量(--fp-*),通过 Tailwind 类暴露,前缀为 rfp-:
| 用途 | 类名 | 说明 |
|------|------|------|
| 文字(fg) | | |
| 主文本 | rfp-text-fg-primary | 最高对比度 |
| 正文 | rfp-text-fg-secondary | 默认文字 |
| 次要文本 | rfp-text-fg-tertiary | 副本、计数器 |
| 弱化文本 | rfp-text-fg-muted | 占位符 |
| 禁用文本 | rfp-text-fg-disabled | 禁用按钮 |
| 背景(surface) | | |
| 表面层 1 | rfp-bg-surface-1 | 卡片、最弱 |
| 表面层 2 | rfp-bg-surface-2 | hover 状态 |
| 表面层 3 | rfp-bg-surface-3 | 强调 |
| 工具栏 | rfp-bg-surface-toolbar | 顶部工具栏 |
| 边框 | | |
| 弱边框 | rfp-border-line-weak | 细线 |
| 标准边框 | rfp-border-line | 默认边框 |
| 强边框 | rfp-border-line-strong | 强调 |
| 代码 | | |
| 代码背景 | rfp-bg-code-bg | Dark:#1e1e1e / Light:#f6f8fa |
| 代码文字 | rfp-text-code-fg | 跟随主题 |
| 强调(accent) | | |
| 强调背景 | rfp-bg-accent | 主按钮 |
| 强调 hover | rfp-bg-accent-hover | hover 状态 |
✅ 正确用法:
export const CustomRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
return (
<div className="rfp-bg-surface-1 rfp-border rfp-border-line-weak rfp-rounded">
<h2 className="rfp-text-fg-primary rfp-text-lg">标题</h2>
<p className="rfp-text-fg-secondary">正文内容</p>
<button className="rfp-bg-surface-2 hover:rfp-bg-surface-3 rfp-text-fg-primary">
点击
</button>
<pre className="rfp-bg-code-bg rfp-text-code-fg">
{code}
</pre>
</div>
);
});❌ 错误用法(禁止使用):
// ❌ 字面色类 — 会破坏主题切换
<div className="rfp-text-white/90 rfp-bg-white/10 rfp-border-white/15">
<div className="rfp-text-gray-700 rfp-bg-gray-100">
// ❌ 内联字面色
<div style={{ color: '#ffffff', background: '#1f2937' }}>
// ❌ 硬编码暗色
<div style={{ background: '#1e1e1e' }}> // 应改用 rfp-bg-code-bg支持主题的三方库:
对于具有 theme prop 的库(如 react-syntax-highlighter),使用 useResolvedTheme():
import { useResolvedTheme } from '@eternalheart/react-file-preview';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
export const CodeRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
const resolvedTheme = useResolvedTheme(); // 'dark' | 'light'
return (
<SyntaxHighlighter
language="javascript"
style={resolvedTheme === 'light' ? vs : vscDarkPlus}
>
{code}
</SyntaxHighlighter>
);
});测试:
务必在 Light 和 Dark 两个主题下测试你的自定义渲染器:
<FilePreviewModal
files={files}
theme="light" // 在 'light'、'dark'、'auto' 间切换
customRenderers={[...]}
/>验证:
- 文字在两个主题下都可读(无白底白字或黑底黑字)
- 边框和分隔线清晰可见
- hover 状态有足够对比度
- 代码块跟随主题(不固定为暗色)
自定义样式
组件使用 Tailwind CSS 构建,您可以通过覆盖 CSS 变量来自定义样式:
/* 自定义主题色 */
:root {
--primary-color: #8b5cf6;
--secondary-color: #ec4899;
}键盘快捷键
ESC- 关闭预览←- 上一个文件→- 下一个文件Home- 第一个文件End- 最后一个文件滚轮- 缩放图片 (仅图片预览)
文档
- 在线演示 - 在线 Demo
Context7 支持
本项目支持 Context7 MCP Server。如果你正在使用 AI 编程助手(如 Claude Code、Cursor 等),可以配置 Context7 MCP Server 来获取 @eternalheart/react-file-preview 的最新文档和代码示例,从而获得更好的 AI 辅助开发体验。
如何使用
- 将 Context7 MCP Server 添加到你的 AI 工具配置中
- 在与 AI 交互时,Context7 会自动提供本库的最新 API 文档和使用示例
- 无需手动查阅文档,即可获得更精准的代码建议和解答
更多关于 Context7 的配置方式,请访问 Context7 官方文档。
开发
库开发
# 克隆仓库
git clone https://github.com/wh131462/file-preview.git
# 安装依赖
pnpm install
# 启动开发服务器(演示应用)
pnpm dev
# 构建库(用于 npm 发布)
pnpm build:lib
# 构建演示应用(用于 GitHub Pages)
pnpm build:demo项目结构
react-file-preview/
├── src/
│ ├── index.ts # 库入口文件
│ ├── FilePreviewModal.tsx # 主组件
│ ├── types.ts # 类型定义
│ ├── utils/ # 工具函数
│ ├── renderers/ # 文件类型渲染器
│ ├── App.tsx # 演示应用
│ └── main.tsx # 演示应用入口
├── lib/ # 构建后的库(npm 包)
├── dist/ # 构建后的演示应用(GitHub Pages)
└── vite.config.lib.ts # 库构建配置许可证
贡献
欢迎提交 Issue 和 Pull Request!
