打造web版epub閱讀器(閱讀設計)
寫在前面的話
實現本閱讀器需要進行以下幾個步驟:
- 設計書架。(可新增圖書,刪除圖書等)
- 開啟並閱讀epub圖書。(可做高亮、筆記、書籤,可顯示目錄並通過目錄跳轉)
在上一篇文章中,我們實現了書架設計,本篇將實現epub閱讀器的閱讀部分。
作者在實現時採用了vue + vue-loader來進行編碼,直接使用js實現時原理都是一樣的。
實現效果圖如下:
<img src="http://upload-images.jianshu.io/upload_images/6376000-25dce08b67f1ea2e.gif?imageMogr2/auto-orient/strip" width="40" height="40" alt="123"/>
在此主要有以下幾個問題:
- 如何開啟圖書,並實現閱讀功能。
- 如何生成目錄。
- 如何給書籍新增筆記。
開啟圖書、閱讀圖書
我們使用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框架,用樹形控制元件來實現目錄的展示。
使用如下程式碼:
<el-tree :data="toc" :props="tocprop" @node-click="selectChapter" class="toc"></el-tree>
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
<el-card v-if="selected" class="box-card colorcontent">
<div slot="header" class="clearfix">
<div class="colorBs" id="theme1" style="background-color: #A4B401"></div>
<div class="colorBs" id="theme2" style="background-color: #D32802"></div>
<div class="colorBs" id="theme3" style="background-color: #0383B3"></div>
<div class="colorBs" id="theme4" style="background-color: #04B91E"></div>
<div class="colorBs" id="theme5" style="background-color: #F634F8"></div>
</div>
<div class="addnote">
<div class="colorb" v-bind:style="colorB"></div>新增筆記
</div>
<div class="selectitem">
翻譯
</div>
<div class="selectitem">
網路搜尋
</div>
<div class="selectitem">
複製內容
</div>
</el-card>
//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
<div v-if="noteselected" class="noteinput">
<div class="noteheader2">
┆┆ 新增筆記<i class="fa fa-close noteinput-close" @click="noteselected=!noteselected"></i>
</div>
<div contenteditable="true" class="notecontainer" placeholder="請輸入筆記">
</div>
<div class="notefooter">
<div class="savebut" @click="savenote">儲存</div>
<div class="giveupbut" @click="noteselected=!noteselected">放棄</div>
</div>
</div>
//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框架,如果未使用框架或使用的其他框架,將程式碼稍作修改即可,因為作者文筆有限,很多東西想要寫出來但下筆時頓覺靈感被抽空。。。。有什麼問題可以在下方留言交流。
相關文章
- 專業ePub閱讀神器:GM EPUB Reader Pro for MacMac
- 電子書閱讀器:GM EPUB Reader Pro for mac 免啟用版Mac
- 電子書閱讀器:GM EPUB Reader Pro for mac v2.5.2免啟用版Mac
- 工具推薦:完全免費的電腦 Epub 閱讀器軟體 Jane Reader
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- Web閱讀器開發系列教程(入門篇)Web
- 【閱讀筆記】REST設計風格筆記REST
- 遊戲特效設計的延伸閱讀遊戲特效
- Leaf for Mac RSS閱讀器Mac
- Web閱讀器開發系列教程(Vue環境篇)WebVue
- 閱讀YYModel
- 深度閱讀
- 閱讀-MTCNNCNN
- 如何更改Safari瀏覽器的閱讀樣式,使其更易於閱讀?瀏覽器
- PDF閱讀器不只閱讀註釋,還有轉換與編輯
- 從SpringBoot啟動,閱讀原始碼設計Spring Boot原始碼
- UI設計:螢幕閱讀用字法則UI
- Julia程式設計基礎 閱讀筆記程式設計筆記
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- JDK原始碼閱讀:String類閱讀筆記JDK原始碼筆記
- 如何閱讀一本書——分析閱讀Pre
- 全能閱讀器:OmniReader Pro for macMac
- Android CHM檔案閱讀器Android
- C++:小說閱讀器C++
- iCHM Reader for Mac(chm閱讀器)Mac
- SpringBoot文件之Web的閱讀筆記Spring BootWeb筆記
- 京東閱讀(web)體驗優化Web優化
- The Open Web Interface for .NET (OWIN) 原始碼閱讀Web原始碼
- 16 – 統計文章閱讀量
- PDF Reader Pro for mac(pdf閱讀器) 3.0.1.0啟用版Mac
- CHM Viewer Star for mac(CHM閱讀器)6.3.2啟用版ViewMac
- PDF Reader Pro for mac(pdf閱讀器)2.9.9.0啟用版Mac
- Reeder for Mac(rss新聞閱讀器)5.4啟用版Mac
- JDK原始碼閱讀(4):HashMap類閱讀筆記JDK原始碼HashMap筆記
- JDK原始碼閱讀(5):HashTable類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀(7):ConcurrentHashMap類閱讀筆記JDK原始碼HashMap筆記
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 《JavaScript設計模式》閱讀筆記_part2JavaScript設計模式筆記