EPub.js技术问题:无法设置电子书打开位置以恢复阅读进度
EPUB.js 阅读进度保存与恢复问题
我正在开发一个用HTML页面展示EPUB电子书的功能,核心需求是保存并恢复用户的阅读进度。目前已经实现了导航时(左右按钮或键盘)将当前位置存入localStorage,但调用setBookToSavedState()恢复时始终失败:部分电子书会直接跳转到首页,部分则跳转到末页且导航功能完全失效。
我查过EPub.js官方文档,尝试直接设置book.locations.currentLocation和rendition.currentLocation来恢复位置,没有效果。虽然想过用循环调用rendition.next()的临时方案,但还是希望找到正确的解决方式。
原代码如下:
<html> <head> <script src="https://futurepress.github.io/epubjs-reader/js/epub.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script> <style> #prev { left: 0; } #next { right: 0; } .arrow { position: fixed; top: 50%; margin-top: -32px; font-size: 64px; color: #E2E2E2; font-family: arial, sans-serif; font-weight: bold; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; user-select: none; } </style> </head> <body> <a id="prev" href="#prev" class="arrow" style="visibility: visible;"><<</a> <a id="next" href="#next" class="arrow" style="visibility: visible;">>></a> <div id="area"> </div> <script> // Load the opf var book = ePub("moby-dick.epub"); var rendition = book.renderTo("area", { width: "100%", height: 600, spread: "always" }); rendition.display(); book.ready.then(() => { var next = document.getElementById("next"); next.addEventListener("click", function(e){ book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next(); e.preventDefault(); saveViewerState(); }) }, false); var prev = document.getElementById("prev"); prev.addEventListener("click", function(e){ book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev(); e.preventDefault(); saveViewerState(); }, false); var keyListener = function(e){ // Left Key if ((e.keyCode || e.which) == 37) { book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev(); } // Right Key if ((e.keyCode || e.which) == 39) { book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next(); } }; rendition.on("keyup", keyListener); document.addEventListener("keyup", keyListener, false); function saveViewerState() { if (rendition && rendition.location && rendition.location.start) { // get the current location var currentLocation = rendition.currentLocation(); // save the current location in localStorage localStorage.setItem("currentLocation", JSON.stringify(currentLocation)); } } // Retrieve the saved state from local storage and set the book to the saved page function setBookToSavedState() { // get the saved location from localStorage var savedLocation = JSON.parse(localStorage.getItem("currentLocation")); // display the saved location rendition.display(savedLocation); } </script> </body> </html>
问题根源与修复方案
1. 核心问题
你直接保存并传递rendition.currentLocation()的完整对象到rendition.display(),但这个对象包含大量运行时动态生成的临时数据,EPub.js无法用它来正确定位。另外,恢复操作没有等待book.locations初始化完成——这个对象负责解析电子书的位置索引,没初始化的话定位必然失败。
2. 修复后的代码
<html> <head> <script src="https://futurepress.github.io/epubjs-reader/js/epub.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script> <style> #prev { left: 0; } #next { right: 0; } .arrow { position: fixed; top: 50%; margin-top: -32px; font-size: 64px; color: #E2E2E2; font-family: arial, sans-serif; font-weight: bold; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; user-select: none; } </style> </head> <body> <a id="prev" href="#prev" class="arrow" style="visibility: visible;"><<</a> <a id="next" href="#next" class="arrow" style="visibility: visible;">>></a> <div id="area"> </div> <script> // Load the opf var book = ePub("moby-dick.epub"); var rendition = book.renderTo("area", { width: "100%", height: 600, spread: "always" }); // 等待电子书和位置索引都初始化完成再执行后续逻辑 Promise.all([book.ready, book.locations.ready]).then(() => { // 优先恢复阅读进度 setBookToSavedState(); // 绑定下一页事件 var next = document.getElementById("next"); next.addEventListener("click", function(e){ book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next(); e.preventDefault(); saveViewerState(); }) // 绑定上一页事件 var prev = document.getElementById("prev"); prev.addEventListener("click", function(e){ book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev(); e.preventDefault(); saveViewerState(); }, false); }); var keyListener = function(e){ // 确保电子书已初始化完成 if (!book.package) return; // Left Key if ((e.keyCode || e.which) == 37) { book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev(); saveViewerState(); } // Right Key if ((e.keyCode || e.which) == 39) { book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next(); saveViewerState(); } }; rendition.on("keyup", keyListener); document.addEventListener("keyup", keyListener, false); function saveViewerState() { if (rendition && rendition.location && rendition.location.start) { // 只保存稳定的CFI定位标识,这是EPub.js认可的标准定位符 var savedCfi = rendition.currentLocation().start.cfi; localStorage.setItem("epubReadingProgress", savedCfi); } } function setBookToSavedState() { var savedCfi = localStorage.getItem("epubReadingProgress"); if (savedCfi) { // 直接用CFI调用display方法定位 rendition.display(savedCfi); } else { // 无保存进度时显示首页 rendition.display(); } } </script> </body> </html>
3. 关键修改点
- 等待初始化完成:用
Promise.all([book.ready, book.locations.ready])确保电子书内容和位置索引都加载完成,再执行恢复和导航绑定逻辑 - 保存稳定标识:只保存
start.cfi——这是EPub.js的标准定位字符串,不会随运行时变化 - 正确恢复逻辑:直接将CFI字符串传给
rendition.display(),而不是整个currentLocation对象 - 补全键盘事件保存:原代码中键盘导航后没有调用
saveViewerState(),现在补上,确保键盘导航也能保存进度
内容的提问来源于stack exchange,提问作者schizoid04
相关产品推荐
相关产品推荐

