打造web版epub阅读器(阅读设计)

作者: 李亚楠0219 | 来源:发表于2017-06-14 09:36 被阅读6920次

    写在前面的话

    实现本阅读器需要进行以下几个步骤:

    1. 设计书架。(可添加图书,删除图书等)
    2. 打开并阅读epub图书。(可做高亮、笔记、书签,可显示目录并通过目录跳转)

    上一篇文章中,我们实现了书架设计,本篇将实现epub阅读器的阅读部分。
    作者在实现时采用了vue + vue-loader来进行编码,直接使用js实现时原理都是一样的。

    实现效果图如下:
    <img src="https://img.haomeiwen.com/i6376000/25dce08b67f1ea2e.gif?imageMogr2/auto-orient/strip" width="40" height="40" alt="123"/>

    在此主要有以下几个问题:

    1. 如何打开图书,并实现阅读功能。
    2. 如何生成目录。
    3. 如何给书籍添加笔记。

    打开图书、阅读图书

    我们使用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操作请参考此篇文章

    1. 对用户选中的文字和范围进行保存,以便实现用户笔记的记录。
      文字可以使用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来实现。
    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框架,如果未使用框架或使用的其他框架,将代码稍作修改即可,因为作者文笔有限,很多东西想要写出来但下笔时顿觉灵感被抽空。。。。有什么问题可以在下方留言交流。

    相关文章

      网友评论

        本文标题:打造web版epub阅读器(阅读设计)

        本文链接:https://www.haomeiwen.com/subject/fuslqxtx.html