主要介绍了 Epub.js 库的基本使用,渲染 epub 格式的电子书,展示了元数据、目录、章节切换、分页切换等功能。
Epub.js:基于 JavaScript 的电子书渲染库,专为在浏览器中解析和渲染 EPUB 格式文档设计。
提供开箱即用的电子书功能(文档渲染、持久化存储和分页显示),无需依赖原生应用或插件。
Github 地址:github.com/futurepress…
Demo 地址:github.com/futurepress…
- EPUB 解析
- 解压 EPUB 文件(本质为 ZIP 压缩包)
- 解析 OPF(内容清单)、NCX(目录结构)、HTML/CSS/图片资源等元数据。
- DOM 动态构建
- 将 EPUB 章节内容(XHTML)转换为标准 HTML 元素
- 按书籍结构生成层级化 DOM 树,支持复杂排版(如诗歌、表格)
- 样式渲染
- 注入 EPUB 内嵌 CSS 样式,保留原书视觉设计(字体、布局、响应式适配)
- 支持覆盖默认样式以实现自定义主题
- 交互层实现:通过 API 提供功能,如翻页、章节跳转、全文搜索、书签/高亮持久化等等
初始化 epub(滚动模式)
import Epub, { type NavItem, type Book, type Rendition } from "epubjs";
export default function EpubReader({ url = "/epub/moby-dick.epub" }: iProps) {
const viewerRef = useRef<HTMLDivElement>(null);
const [book, setBook] = useState<Book | null>(null);
const [rendition, setRendition] = useState<Rendition | null>(null);
const [metaData, setMetaData] = useState<any>(null);
const [menu, setMenu] = useState<NavItem[] | null>(null);
useEffect(() => {
if (!url || !viewerRef.current) return;
// 1. 创建EPUB实例
const newBook = Epub(url, {
openAs: "epub", // 明确指定为EPUB格式
});
// 2. 配置渲染参数
const newRendition = newBook.renderTo(viewerRef.current, {
width: "100%",
height: "100%", // 高度自适应容器
spread: "none", // none-禁用跨页视图;always-启用跨页时图
flow: "scrolled", // 启用滚动模式
});
// 3. 初始化渲染
newRendition.display();
// 4. 加载元数据
newBook.loaded.metadata.then((meta) => {
console.log("metadata", meta);
setMetaData(meta);
});
// 5. 加载目录导航
newBook.loaded.navigation.then((nav) => {
console.log("navigation", nav);
setMenu(nav.toc);
});
setBook(newBook);
setRendition(newRendition);
return () => {
newRendition?.destroy();
newBook?.destroy();
};
}, [url]);
return (
<Container className="epub-page">
<div className="epub-left">
{/* 基础信息 */}
<div className="info">
<div>标题:{metaData?.title}</div>
<div>作者:{metaData?.creator}</div>
<div>出版社:{metaData?.publisher}</div>
<div>出版时间:{metaData?.pubdate}</div>
</div>
{/* 目录 */}
{/* 翻页控制按钮 */}
</div>
<div className="epub-right">
<div ref={viewerRef} className="epub-viewer" />
</div>
</Container>
);
}
整体效果如下图所示:
元数据信息通过newBook.loaded.metadata
获取,具体如下图所示:
创建目录
目录信息通过newBook.loaded.navigation
获取
如下图所示,为目录信息:
跳转章节,使用rendition.display()
// 当前章节索引
const [curIdx, setCurIdx] = useState(0);
// 切换章节事件
const handleChapter = (value: string) => {
if (!menu?.length) return;
// 查找选中章节
const chapter = menu.find((item) => item.id === value);
if (!chapter || !rendition) return;
// 更新索引并跳转
const index = menu.findIndex((item) => item.id === value);
setCurIdx(index);
rendition.display(chapter.href);
};
/* 目录 */
{
menu && (
<Select
options={menu.map((o) => ({ value: o.id, label: o.label }))}
onChange={handleChapter}
/>
);
}
章节控制器
// 章节控制器(上一章/下一章)
const handlePageTurn = (direction: number) => {
// 无数据处理
if (!menu?.length) return;
// 边界处理
const newIndex = curIdx + direction;
if (newIndex < 0 || newIndex >= menu.length) return;
// 跳转处理
const chapter = menu[newIndex];
setCurIdx(newIndex);
rendition?.display(chapter.href);
};
/* 章节控制按钮 */
<div className="tools">
<Button
type="primary"
disabled={curIdx === 0}
onClick={() => handlePageTurn(-1)}
>
上一章
</Button>
<Button
type="primary"
disabled={!menu || curIdx === menu.length - 1}
onClick={() => handlePageTurn(1)}
>
下一章
</Button>
</div>;
翻页控制器(分页模式)
上面实现的是一次展示一章的内容,滚动渲染(spread: "none", flow: "scrolled"
),切换时,也是章节切换。
现在是双页并排
显示内容(spread: "always"
),使用分页模式
(flow: "paginated"
),通过上一页/下一页实现翻页。
修改渲染配置:使用分页模式,启用双页布局:
const newRendition = newBook.renderTo(viewerRef.current, {
width: "100%",
height: "100%",
spread: "always", // 启用双页布局
flow: "paginated", // 使用分页模式
});
增加翻页控制代码:
// 单页翻页功能
const handlePage = (direction: "prev" | "next") => {
if (!rendition) return;
if (direction === "prev") {
// 上一页
rendition.prev();
} else {
// 下一页
rendition.next();
}
};
<div className="tools">
<Button type="primary" onClick={() => handlePage("prev")}>
上一页
</Button>
<Button type="primary" onClick={() => handlePage("next")}>
下一页
</Button>
</div>;