写在前面的话
实现本阅读器需要进行以下几个步骤:
- 设计书架。(可添加图书,删除图书等)
- 打开并阅读epub图书。(可做高亮、笔记、书签,可显示目录并通过目录跳转)
在上一篇文章中,我们实现了书架设计,本篇将实现epub阅读器的阅读部分。
作者在实现时采用了vue + vue-loader来进行编码,直接使用js实现时原理都是一样的。
实现效果图如下:
在此主要有以下几个问题:
- 如何打开图书,并实现阅读功能。
- 如何生成目录。
- 如何给书籍添加笔记。
打开图书、阅读图书
我们使用epub.js开源库来实现epub图书的阅读,如果要自行实现epub的解析,请到参考epub规范。详细的使用方式请到此处查阅。在此只贴出作者使用时的相关代码:
//参考上篇文章,使用localForage加载图书。
this.store.getItem(this.editFile.dname, function(err, file) {
if (file) {
//读取图书
var reader = new FileReader();
reader.onload = function() {
var arrayBuffer = reader.result;
//参考epub.js读取epub图书
self.Book = ePub(arrayBuffer, {
restore: true,
gap: 80
});
//检测是否保存上次读取页
if (self.editFile.lastreadurl) {
self.Book.spinePos = self.editFile.lastreadurl;
}
//将图书渲染到html中。
self.Book.renderTo("viewer");
self.Book.setStyle("font-family", "微软雅黑,宋体");
self.Book.setStyle("color", self.mainStyle.color);
self.Book.on('renderer:locationChanged', function(locationCfi) {
self.editFile.lastreadurl = locationCfi;
if (self.onLine == '0') {
localStorage.setItem("filesInfo", JSON.stringify(self.files));
}
});
}
reader.readAsArrayBuffer(file);
}
});
生成目录
我在制作此项目时,采用的是elementui框架,用树形控件来实现目录的展示。
使用如下代码:
self.Book.getToc().then(function(toc) {
//遍历目录树,并修改部分内容
self.transitionToc(toc);
self.toc = toc;
});
//真正编写代码时,可调式查看toc(epub.js所生成的目录格式)
//会发现其与elementui树形控件所需数据格式并不相同,所以需要使用translitionToc函数对格式进行转换。
//递归
transitionToc: function(toc) {
var self = this;
$.each(toc, function(index, val) {
if (val.nodes.length == 0) {
//val.nodes = null;
} else {
self.transitionToc(val.nodes);
}
});
},
添加笔记
添加笔记需要注意以下几点:
- 检测用户选中文字
- 弹出用户操作框
- 用户笔记输入框
- 保存用户选中文字及范围
- 高亮
检测用户选中文字
当用户选中文字时,会使得slef.selected=true
,之后下面界面部分将显示。
//epub.js能捕获用户在书籍上的鼠标释放事件,使用self.selected是为了防止用户重复选中。
self.Book.on('renderer:mouseup', function(event) {
//释放后检测用户选中的文字
var render = self.Book.renderer.render;
var selectedContent = render.window.getSelection();
self.selection = selectedContent;
//若当前用户不在选中状态,并且选中文字不为空
if (self.selected == false) {
if (selectedContent.toString() && (selectedContent.toString() != "")) {
self.selected = true;
}
}
});
弹出用户操作框
用户操作框界面设计
//html
添加笔记
翻译
网络搜索
复制内容
//css
.colorcontent {
width: 200px;
height: 205px;
position: absolute;
font-size: 17px;
color: #7E7E7E;
}
.colorBs {
float: left;
margin-top: 0px;
margin-left: 15px;
width: 20px;
height: 20px;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
border: 1px solid #D4D0D0;
cursor: pointer;
}
.colorBs:hover {
border: 1px solid #ffffff;
}
.selectitem {
height: 40px;
line-height: 40px;
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
cursor: pointer;
}
用户操作框目前实现的包括高亮和添加笔记,因为添加笔记的同时也会高亮,下面将重点讲添加笔记部分。
用户笔记输入框
外观部分代码
//html
┆┆ 添加笔记
//css
.noteinput {
position: absolute;
left: 20px;
top: 20px;
width: 350px;
height: 230px;
background-color: #ffffff;
z-index: 1000;
}
.noteheader2 {
padding-left: 10px;
color: #ffffff;
line-height: 30px;
font-size: 10px;
background-color: #858585;
height: 30px;
cursor: move;
//不被选中
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.noteheader2:hover {
background-color: #707070;
}
.noteinput-close {
font-size: 14px;
margin-left: 250px;
cursor: pointer;
}
.noteinput-close:hover {
color: #A7A7A7;
}
.notecontainer {
border: 1px solid #A0A0A0;
margin-top: 10px;
margin-left: 15px;
margin-right: 15px;
height: 130px;
padding: 10px;
font-size: 13px;
line-height: 20px;
overflow-y: auto;
}
.notecontainer:empty:before {
content: attr(placeholder);
color: #989898;
}
.notecontainer:focus {
content: none;
color: #464646;
}
/**
* 自定义滚动条
* @type {[type]}
*/
.notecontainer::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
background-color: #ffffff;
}
.notecontainer::-webkit-scrollbar {
width: 4px;
background-color: #ffffff;
}
.notecontainer::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
background-color: #8F8E8E;
}
.notefooter {
margin-top: 8px;
height: 30px;
line-height: 30px;
}
.savebut {
float: left;
border: 1px solid #00C4BE;
color: #00C4BE;
margin-left: 240px;
text-align: center;
width: 50px;
height: 23px;
line-height: 23px;
cursor: pointer;
}
.giveupbut {
float: left;
text-align: center;
width: 50px;
height: 23px;
line-height: 23px;
cursor: pointer;
}
用户操作部分代码
$('.addnote').click(function(event) {
//记录标记,使得用户在编写笔记时,无法再次选中其他文字。
self.noteselected = true;
self.highlightAndSaveSelected(); ////高亮(下面将贴出代码)
selectedContent.empty(); //清空选中文字
self.$nextTick(function() {/////一下代码使得用户笔记部分为可拖动的。
var pageW = $(window).width();
var pageH = $(window).height();
var dialogW = $('.noteinput').width();
var dialogH = $('.noteinput').height();
var maxX = pageW - dialogW; //X轴可拖动最大值
var maxY = pageH - dialogH; //Y轴可拖动最大值
var moveX = event.pageX - 50;
var moveY = event.pageY;
moveX = Math.min(Math.max(0, moveX), maxX); //X轴可拖动范围
moveY = Math.min(Math.max(0, moveY), maxY); //Y轴可拖动范围
$('.noteinput').css({
top: moveY,
left: moveX
});
var mx, my, dx, dy, isDraging;
//鼠标按下
$(".noteheader2").mousedown(function(e) {
e = e || window.event;
mx = e.pageX; //点击时鼠标X坐标
my = e.pageY; //点击时鼠标Y坐标
dx = $('.noteinput').offset().left;
dy = $('.noteinput').offset().top;
isDraging = true; //标记对话框可拖动
});
var moveNote = function(e) {
var e = e || window.event;
var x = e.pageX; //移动时鼠标X坐标
var y = e.pageY; //移动时鼠标Y坐标
if (isDraging) { //判断对话框能否拖动
var moveX = dx + x - mx; //移动后对话框新的left值
var moveY = dy + y - my; //移动后对话框新的top值
//设置拖动范围
var pageW = $(window).width();
var pageH = $(window).height();
var dialogW = $('.noteinput').width();
var dialogH = $('.noteinput').height();
var maxX = pageW - dialogW; //X轴可拖动最大值
var maxY = pageH - dialogH; //Y轴可拖动最大值
moveX = Math.min(Math.max(0, moveX), maxX); //X轴可拖动范围
moveY = Math.min(Math.max(0, moveY), maxY); //Y轴可拖动范围
//重新设置对话框的left、top
$('.noteinput').css({
"left": moveX + 'px',
"top": moveY + 'px'
});
};
};
//鼠标移动更新窗口位置
$(self.Book.renderer.render.document).mousemove(function(e) {
moveNote(e);
});
$(document).mousemove(function(e) {
moveNote(e);
});
$(document).mouseup(function() {
isDraging = false;
});
});
});
保存用户操作信息以及高亮
有关Range操作请参考此篇文章。
- 对用户选中的文字和范围进行保存,以便实现用户笔记的记录。
文字可以使用this.selection.toString();
来生成。
范围可以用var epubcfi = new EPUBJS.EpubCFI();
来生成。 - 高亮用户所选信息
包括两部分,一是用户选中后的高亮,二是从用户记录的epubcfi信息来高亮。
两者都是要先转换成Range对象,转换方式分别为:
var range = this.selection.getRangeAt(0);//从用户选中的selection对象来转换
var doc = self.Book.renderer.doc;
var range = epubcfi.generateRangeFromCfi(cfi, doc);//从epubcfi转换
高亮Range使用github库dom-highlight-range来实现。
highlightAndSaveSelected: function() {
this.selected = false;
var range = this.selection.getRangeAt(0);
var epubcfi = new EPUBJS.EpubCFI();
var chapter = this.Book.currentChapter;
var cfiBase = chapter.cfiBase;
var cfi = epubcfi.generateCfiFromRange(range, cfiBase);
//对CFI进行存储
var note = new Object();
note.color = this.colorB['background-color'];
note.cfi = cfi;
note.text = this.selection.toString();
note.tagtime = Date.parse(new Date());
note.tagtimedis = ''; //标记标签时间
note.dishead = false; //显示标签跳转图标
note.note = ""; //标记注释
this.editFile.notes.push(note);
this.editNote = note;
localStorage.setItem("filesInfo", JSON.stringify(this.files));
this.selection.empty();
highlightRange(range, this.editNote.color);
},
总结
epub阅读器的阅读设计效果就如文章开头的效果图,作者开发时使用的是elementui框架,如果未使用框架或使用的其他框架,将代码稍作修改即可,因为作者文笔有限,很多东西想要写出来但下笔时顿觉灵感被抽空。。。。有什么问题可以在下方留言交流。