小组项目 “基于JavaScript的读书平台” 正在开发中,预计年底上线。
由于之前有Flask框架的web开发经验,产品的第一个版本我负责了web平台搭建,技术选型:
服务器端:nodejs-express
前端:React、Ant Design
数据库:Mysql
之前Flask框架下的前端技术使用的是基础的HTML+CSS,和一小部分JQuery效果,因为一直对前端比较感兴趣,这次大胆的用上学的不那么扎实(或者说更流行)的React,实践指导理论。
“读书平台”打造的是一个:能够在线读书、即时分享、方便批注笔记的高度定制化、个性化书库。
回到题目上,epub是最流行的电子书规范之一,网络上对于Java Web有不少合适的方法来解析和呈现epub文件,但在JavaScript特别是浏览器端渲染的中间件则只有epub.js一家。
网络上关于epub.js的介绍比较少(尽管github上已经2K星了),更多的是概念性的内容,如:
epub.js是支持跨多个设备的,在浏览器端渲染EPUB文件的JavaScript库,提供通用的电子书功能界面(如渲染、持久和分页)不需要开发专用的应用程序或插件。
具体用法因人而异,在此我介绍结合React使用的案例:
如图是使用React和AntD构建的后台管理系统,对于文件的上传下载删除功能已经做好,epub.js解决的是对于epub规格的文件如何解析和呈现。
点击“阅读本书”操作:
这就是epub.js结合React提供的基础单页阅览页面了,epub.js中间件封装了解析epub文件的过程。左右两侧的翻页键都可以按需定制,甚至用AntD的组件来替代也不是问题。
简单用法在官方文档写的很容易理解:https://github.com/futurepress/epub.js
有多种解析模式可以选择,单页阅览是默认模式。
但对于一个专业的读书平台来说,简单呈现图书内容是远远不够的,epub.js提供了更多的接口:
https://github.com/futurepress/epub.js/blob/master/documentation/README.md
方法:
ePub(bookPath, options)
创建EPUB图书实例:
bookPath可选参数,指定epub文件路径,可以是网络资源,也可以是本地资源。
var Book = ePub("url/to/book/"); // With default options var Book = ePub({ restore: true }); Book.open("url/to/book/"); // Books can be opened later
Options可选参数,配置解析参数
{ bookPath : null, version: 1, // Changing will cause stored Book information to be reloaded restore: false, // Skips parsing epub contents, loading from localstorage instead storage: false, // true (auto) or false (none) | override: ‘ram‘, ‘websqldatabase‘, ‘indexeddb‘, ‘filesystem‘ spreads: true, // Displays two columns fixedLayout : false, //-- Will turn off pagination styles : {}, // Styles to be applied to epub width : false, //width和height可以设置图书内容的宽和高,默认值是不设置,此时会填满父窗口。 height: false, }
解析图书的更多方法:
Book.open(bookPath) 打开指定路径的图书,和构造函数的作用类似,只是将配置和解析的过程分开
var Book = ePub({ restore: true }); Book.open("url/to/book/"); // Books can be opened later
也可以带扩展名:
Book.open("url/to/book.epub");
Book.renderTo(element) 将解析好的图书追加到某个HTML结点并渲染,此时epub图书内容在HTML中呈现。(使用的是iframe标签)
var Book = ePub("url/to/book/", { restore: true }); var $el = document.getElementById("div-id"); Book.renderTo($el);
参数也可以是字符串表示:
var Book = ePub("url/to/book/"); Book.renderTo("div-id");
Book.nextPage()
Book.prevPage()
在阅读页面中的翻页控制函数,添加到按钮事件函数:
<div onclick="Book.prevPage();">?</div> <div onclick="Book.nextPage();">?</div>
必须在渲染之后,才会响应翻页。
Book.displayChapter(chap, end) 显示指定章节:
加载指定的章节到页面上,加载的是这一章的第一页:
Book.displayChapter(‘/6/4[chap01ref]!/4[body01]/10‘);
第二个参数设置为true,将加载这一章的最后一页:
Book.displayChapter(3, true);
Book.goto(url)跳转到指定章节:
加载指定的章节(需要有pageMap)
var skip = Book.goto("chapter_001.xhtml"); skip.then(function(){ console.log("On Chapter 1"); })
通常用来显示一些带有指定章节链接的表格
Book.setStyle(style, val, prefixed)设置图书内容的样式:
Book.setStyle("font-size", "1.2em");
Book.removeStyle(style)移除设置的样式。
Book.destroy()删除iframe和事件函数。
Promises
Book.getMetadata()在回调函数中获取meta数据:
Book.getMetadata().then(function(meta){ document.title = meta.bookTitle+" – "+meta.creator; });
返回一个对象:
{ "bookTitle": "The title of the book", "creator": "Book Author", "description": "The description/synopsis of the book", "pubdate": "", "publisher": "The Publisher", "identifier": "The ISBN", "language": "en-US", "rights": "Copyright text", "modified_date": "", "layout": "", "orientation": "", "spread": "", "direction": null }
Book.getToc()在回调函数中获取toc,是一个包含目录结构的数组对象。
Book.getToc().then(function(toc){ console.log(toc); });
Book.generatePagination()进行分页,可选参数为页面大小:
book.generatePagination().then(function(toc){ console.log("Pagination generated"); }); book.generatePagination(pageWidth, pageHeight).then(function(toc){ console.log("Pagination generated"); });
可响应的事件:
book:ready
book:stored
book:online
book:offline
book:linkClicked
book.on(‘book:linkClicked‘, function(href) { console.log(href); });
book:pageChanged
{ anchorPage: 2 percentage: 0.5 pageRange: [1, 2, 3] }
pageChanged事件响应当前页数,前提是必须有一个页码表
book.on(‘book:pageChanged‘, function(location){ console.log(location.anchorPage, location.pageRange) });
renderer:resized
renderer:chapterDisplayed
renderer:chapterUnloaded
renderer:locationChanged
"epubcfi(...)"
locationChanged事件响应当前位置信息,可以用来做进度条等效果。
book.on(‘renderer:locationChanged‘, function(locationCfi){ console.log(locationCfi) });
renderer:visibleRangeChanged