使用 HTML + epub.js 实现简易电子书阅读器

576 阅读2分钟

在数字出版领域,EPUB 是一种广泛使用的电子书格式,支持多种设备和平台。为了方便地在网页中嵌入 EPUB 电子书阅读功能,我们可以使用开源库 epub.js 来快速构建一个功能齐全的阅读器。本文将介绍如何使用 HTML、CSS 和 JavaScript 搭建一个基础的 EPUB 阅读器。

效果演示

image-20250526224244063

image-20250526224527907

项目概述

本项目主要包含以下主要功能:

  • 加载并展示 EPUB 文件
  • 支持上一页 / 下一页翻页操作
  • 显示书籍目录(TOC)
  • 记录阅读进度

准备工作

准备一本 EPUB 格式的电子书文件,并将其放置在项目根目录下供加载使用。

引入必要的依赖文件,其中 jszip 是处理 EPUB 文件(本质上是 ZIP 压缩包)所必需的,epub.js 是主库文件,提供了完整的 EPUB 解析与渲染能力。

epub.js下载地址:github.com/futurepress…

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
<script src="./epub.js"></script>

页面结构与样式设计

创建 HTML 结构

<div class="controls">
    <button onclick="toggleTOC()">目录</button>
    <button onclick="prevPage()">上一页</button>
    <button onclick="nextPage()">下一页</button>
</div><div id="toc-panel"></div>
<div id="viewer"></div>
<div class="progress" id="progress"></div>

设计 CSS 样式

body {
    margin: 0;
    padding: 20px;
    font-family: Arial, sans-serif;
    background-color: #f5f5f5;
    transition: background-color 0.3s;
}
​
#viewer {
    width: 90%;
    height: 80vh;
    margin: 20px auto;
    box-shadow: 0 0 10px rgba(0,0,0,0.2);
    background: white;
}
​
.controls {
    display: flex;
    gap: 10px;
    justify-content: center;
    margin: 20px 0;
}
​
button {
    padding: 8px 16px;
    cursor: pointer;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    transition: background 0.3s;
}
​
button:hover {
    background: #0056b3;
}
​
.progress {
    text-align: center;
    color: #666;
}
​
#toc-panel {
    position: fixed;
    left: -300px;
    top: 0;
    width: 280px;
    height: 100vh;
    background: white;
    box-shadow: 2px 0 5px rgba(0,0,0,0.1);
    transition: left 0.3s;
    padding: 20px;
    overflow-y: auto;
    z-index: 10;
}
​
.toc-visible #toc-panel {
    left: 0;
}

核心功能实现

初始化阅读器

let book;
let rendition;
let currentLocation;
​
function initReader(epubFile) {
    if (rendition) rendition.destroy();
​
    book = ePub(epubFile);
    rendition = book.renderTo("viewer", {
        width: "100%",
        height: "100%",
        spread: "auto"
    });
    book.ready
        .then(() => book.locations.generate(1600))
        .then(() => {
        // 加载进度
        const savedPosition = localStorage.getItem('epubProgress');
        rendition.display(savedPosition || 0);
        updateTOC();
    })
        .catch(error => console.error("EPUB初始化失败:", error));
    // 初始化设置
    rendition.themes.default({
        body: { color: "inherit", background: "inherit" }
    });
​
    // 注册事件监听
    rendition.on("locationChanged", updateProgress);
    rendition.on("keyup", e => (e.key === "ArrowLeft") && prevPage());
    rendition.on("keyup", e => (e.key === "ArrowRight") && nextPage());
}

更新阅读进度

function updateProgress(location) {
    currentLocation = location.start;
    const percentage = (location.percentage * 100).toFixed(1);
    document.getElementById("progress").textContent = `进度:${percentage}%`;
    localStorage.setItem('epubProgress', currentLocation);
}

翻页控制

function prevPage() {
    rendition.prev();
}
​
function nextPage() {
    rendition.next();
}

目录控制

function toggleTOC() {
    document.body.classList.toggle("toc-visible");
}

生成目录

async function updateTOC() {
    const toc = await book.loaded.navigation;
    const tocPanel = document.getElementById("toc-panel");
    tocPanel.innerHTML = "<h3>目录</h3>";
​
    toc.forEach(chapter => {
        const item = document.createElement("div");
        item.style.padding = "5px 10px";
        item.style.cursor = "pointer";
        item.textContent = chapter.label;
        item.onclick = () => {
            rendition.display(chapter.href);
            toggleTOC();
        };
        tocPanel.appendChild(item);
    });
}

完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>EPUB电子书阅读器</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
            transition: background-color 0.3s;
        }
​
        #viewer {
            width: 90%;
            height: 80vh;
            margin: 20px auto;
            box-shadow: 0 0 10px rgba(0,0,0,0.2);
            background: white;
        }
​
        .controls {
            display: flex;
            gap: 10px;
            justify-content: center;
            margin: 20px 0;
        }
​
        button {
            padding: 8px 16px;
            cursor: pointer;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            transition: background 0.3s;
        }
​
        button:hover {
            background: #0056b3;
        }
​
        .progress {
            text-align: center;
            color: #666;
        }
​
        #toc-panel {
            position: fixed;
            left: -300px;
            top: 0;
            width: 280px;
            height: 100vh;
            background: white;
            box-shadow: 2px 0 5px rgba(0,0,0,0.1);
            transition: left 0.3s;
            padding: 20px;
            overflow-y: auto;
            z-index: 10;
        }
​
        .toc-visible #toc-panel {
            left: 0;
        }
    </style>
</head>
<body>
<div class="controls">
    <button onclick="toggleTOC()">目录</button>
    <button onclick="prevPage()">上一页</button>
    <button onclick="nextPage()">下一页</button>
</div><div id="toc-panel"></div>
<div id="viewer"></div>
<div class="progress" id="progress"></div><!-- 引入epub.js库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
<script src="./epub.js"></script>
<script>
    let book;
    let rendition;
    let currentLocation;
​
    // 初始化阅读器
    function initReader(epubFile) {
        if (rendition) rendition.destroy();
​
        book = ePub(epubFile);
        rendition = book.renderTo("viewer", {
            width: "100%",
            height: "100%",
            spread: "auto"
        });
        book.ready
            .then(() => book.locations.generate(1600))
            .then(() => {
                // 加载进度
                const savedPosition = localStorage.getItem('epubProgress');
                rendition.display(savedPosition || 0);
                updateTOC();
            })
            .catch(error => console.error("EPUB初始化失败:", error));
        // 初始化设置
        rendition.themes.default({
            body: { color: "inherit", background: "inherit" }
        });
​
        // 注册事件监听
        rendition.on("locationChanged", updateProgress);
        rendition.on("keyup", e => (e.key === "ArrowLeft") && prevPage());
        rendition.on("keyup", e => (e.key === "ArrowRight") && nextPage());
    }
​
    // 更新阅读进度
    function updateProgress(location) {
        currentLocation = location.start;
        const percentage = (location.percentage * 100).toFixed(1);
        document.getElementById("progress").textContent = `进度:${percentage}%`;
        localStorage.setItem('epubProgress', currentLocation);
    }
​
    // 翻页控制
    function prevPage() {
        rendition.prev();
    }
​
    function nextPage() {
        rendition.next();
    }
​
    // 目录控制
    function toggleTOC() {
        document.body.classList.toggle("toc-visible");
    }
​
    // 生成目录
    async function updateTOC() {
        const toc = await book.loaded.navigation;
        const tocPanel = document.getElementById("toc-panel");
        tocPanel.innerHTML = "<h3>目录</h3>";
​
        toc.forEach(chapter => {
            const item = document.createElement("div");
            item.style.padding = "5px 10px";
            item.style.cursor = "pointer";
            item.textContent = chapter.label;
            item.onclick = () => {
                rendition.display(chapter.href);
                toggleTOC();
            };
            tocPanel.appendChild(item);
        });
    }
​
    // 初始化示例书籍
    initReader("./demo.epub"); // 替换为实际EPUB路径
</script>
</body>
</html>