打造web版epub閱讀器(閱讀設計)

寫在前面的話

實現(xiàn)本閱讀器需要進行以下幾個步驟:

  1. 設計書架。(可添加圖書,刪除圖書等)
  2. 打開并閱讀epub圖書。(可做高亮、筆記、書簽,可顯示目錄并通過目錄跳轉)

上一篇文章中,我們實現(xiàn)了書架設計,本篇將實現(xiàn)epub閱讀器的閱讀部分。
作者在實現(xiàn)時采用了vue + vue-loader來進行編碼,直接使用js實現(xiàn)時原理都是一樣的。

實現(xiàn)效果圖如下:
<img src="http://upload-images.jianshu.io/upload_images/6376000-25dce08b67f1ea2e.gif?imageMogr2/auto-orient/strip" width="40" height="40" alt="123"/>

在此主要有以下幾個問題:

  1. 如何打開圖書,并實現(xiàn)閱讀功能。
  2. 如何生成目錄。
  3. 如何給書籍添加筆記。

打開圖書、閱讀圖書

我們使用epub.js開源庫來實現(xiàn)epub圖書的閱讀,如果要自行實現(xiàn)epub的解析,請到參考epub規(guī)范。詳細的使用方式請到此處查閱。在此只貼出作者使用時的相關代碼:

//參考上篇文章,使用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框架,用樹形控件來實現(xiàn)目錄的展示。

使用如下代碼:

<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所生成的目錄格式)
//會發(fā)現(xiàn)其與elementui樹形控件所需數(shù)據(jù)格式并不相同,所以需要使用translitionToc函數(shù)對格式進行轉換。
//遞歸
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;
        //若當前用戶不在選中狀態(tài),并且選中文字不為空
        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;
}

用戶操作框目前實現(xiàn)的包括高亮和添加筆記,因為添加筆記的同時也會高亮,下面將重點講添加筆記部分。

用戶筆記輸入框

外觀部分代碼

輸入筆記
//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操作請參考此篇文章。

  1. 對用戶選中的文字和范圍進行保存,以便實現(xiàn)用戶筆記的記錄。
    文字可以使用this.selection.toString();來生成。
    范圍可以用var epubcfi = new EPUBJS.EpubCFI();來生成。
  2. 高亮用戶所選信息
    包括兩部分,一是用戶選中后的高亮,二是從用戶記錄的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來實現(xiàn)。
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閱讀器的閱讀設計效果就如文章開頭的效果圖,作者開發(fā)時使用的是elementui框架,如果未使用框架或使用的其他框架,將代碼稍作修改即可,因為作者文筆有限,很多東西想要寫出來但下筆時頓覺靈感被抽空。。。。有什么問題可以在下方留言交流。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容