You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 02:09:18