电子书阅读器一

引言

此系列为 Vue 实战商业级读书Web APP 全面提升技能 开发过程中的整理归纳,作为提纲方便重现。需要有一些前端的先验知识:javascript,vuejs,epubjs,nodejs 等。

阅读器知识

1.电子书的格式目前主要有 mobi 和 epub 两种,其中 mobi 是亚马逊独有的格式,epub是支持的通用格式。这次阅读器的目标是解析 epub 格式电子书。
2.使用解压命令解压一本电子书看下有什么:
------META-INF
 ------container.xml
------OEBPS
------mimetype
解压后目录内容如上所示,打开 container.xml 看到有

<?xml version="1.0"?>
-<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
    -<rootfiles>
        <rootfile media-type="application/oebps-package+xml" full-path="OEBPS/content.opf"/>
    </rootfiles>
</container>

这个 xml 文件指定了 rootfile 路径,我们需要的电子书内容其实都在 OEBPS 文件夹内,在 content.pdf 引导了电子书的内容,打开后发现也是一个 xml 类型文件,

<metadata>出版信息</metadata>
<manifest>
    图片,文件,章节,目录信息
</manifest>
<spine>
    电子书的阅读顺序
</spine>

有了对 epub 格式的基本了解就可以开始进行下一步开发了。

准备工作

1.字体图标:
在 Iconfont 下载 svg 格式的字体图标后导入 icomoon 转换后下载。
2.使用 vue-cli 3.0 搭建 vue 项目:
记得选择 Vuex 和 Router, Router 选择hash(not history mode)模式。
配置模式选择不集成(In dedicated config files),否则会全部集成到 package.json 里。
完成后发现文件结构比 2.0 的简洁许多,原来的 conf 和 build 文件夹没有了。
配置项目: vue.config.js,配置原来 build 里的内容。
配置 baseUrl:

module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
  }

vue cli 3.0 提供了图形化界面,可在命令行中通过

vue ui

启动图形化管理界面。
3.web字体:
在 CSS3 之前,web 设计师必须使用已在用户计算机上安装好的字体。通过 CSS3,web 设计师可以使用他们喜欢的任意字体。
准备好 web 字体后进行测试,通过后继续下一步。

4.viewport配置和rem设置:
该项目的主要使用场景是移动端,因此需要对 viewport 进行设置,禁止缩放,打开
public/index.html, 设置 viewport:

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximun-scala=1.0,minimum-scale=1.0,user-scale=no">

同时,打开 app.vue 加入以下代码:

<script>
  export default {}
  // DOM加载完毕后设置字体
  document.addEventListener('DOMContentLoaded', () => {
    const html = document.querySelector('html')
    let fontSize = window.innerWidth / 10
    // 移动端设置字体最大50px
    fontSize = fontSize > 50 ? 50 : fontSize
    html.style.fontSize = fontSize + 'px'
  })
</script>

我们对网页添加监听,加载完后根据屏幕尺寸设置 font-size,并限定字体最大尺寸为 50px。

解析电子书

先准备一本电子书放到 public 文件夹下

import Epub from 'epubjs'
globao.ePub = Epub
this.book = new Epub(‘/2016_Book_NewHorizonsForAData-DrivenEcon.epub')
console.log(this.book)

可以看到:

解析出电子书.png

电子书解析成功。

阅读器开发难点

1.阅读器开发:分页算法,全文搜索算法,引入 Web 字体,主题设计。
2.离线存储机制: LocalStorage + IndexedDB
3.复杂手势+交互动画,兼容手势 + 鼠标
4.利用 vuex + minin 实现组件解耦
5.利用 es6 优雅地实现数据结构变化
6.科大讯飞 Web 在线语音合成 API

global.scss 和 reset.scss 设置

重置样式是前端常用的 css 预设方法,预先设置了一些基本的样式,如 0 边距等,从网上搜一份 reset.css 文件,放置在
assets/style/ 下,重命名为 reset.scss。打开后添加

html,body {
  width: 100%;
  height: 100%;
  user-select: none;
  overflow: hidden;
}

把 user-select 置为 none, 阻止用户选中文字,避免和长按操作冲突。
在 global.scss 里引入 reset.scss ,添加 px2rem($px) 函数, 这样能在 vue 文件里对 css 样式进行像素控制。具体如下:

@import "./reset";

$ratio: 375 / 10;
@function px2rem($px) {
  @return $px / $ratio + rem;
}

这里设置了 1rem 对应的 px 为 375 / 10,因为 375 是 4.7 寸 iphone 的宽,移动端适配常用的一个数值。

引入vuex + vue-devtools

vuex介绍移步官网,使用 vuex 对组件进行解耦,对方法进行复用, chrome 安装 vue-devtools 可视化监控 state 变量和组件。

配置 nginx 静态资源服务器

将开发所需的大文件等放到 nginx 配置路径下。
nginx 配置:

 server {
        listen  9001;
        server_name resource;
        root D:\\fe\\resource;
        autoindex on;
        location / {
            add_header Access-Control-Allow-Origin *;
        }
        add_header Cache-Control "no-cache, must-revalidate";
    }

禁止缓存,每次必须重新获取资源。
启动 nginx: start nginx

阅读器需求分析

阅读器需求分析.png

1.阅读器部分先要实现一个动态路由实现这种地址的解析:

http://localhost:8081/#/ebook/ComputerScience|2016_Book_NewHorizonsForAData-DrivenEcon

做法是在 router 里配置动态路由:

  {
      path: '/ebook',
      component: () => import('./views/ebook/index.vue'),
      children: [
        {
          path: ':fileName',
          component: () => import('./components/ebook/EbookReader.vue')
        }
      ]
    }

2.然后在 EbookReader.vue 里解析:

mounted() {
    this.$route.params.fileName.split('|').join('\')
}

另外注意还要加上 '.epub' 才能下载电子书:

const url = process.env.VUE_APP_RES_URL + '/epub/' + this.fileName + '.epub'

注意到 VUE_APP_RES_URL 使我们在 .env.development 里预先定于好的,这里 VUE_APP_RES_URL = http://localhost:9001。指向的是 nginx 静态资源服务器。

将电子书渲染到指定 div 的方法:

this.rendition = this.book.renderTo('divID', {
          width: innerWidth,
          height: innerHeight,
          // method: 'scrolled'
          // 阅读器阅读模式
          method: 'default'
        })
// 渲染
this.rendition.display()

3.实现翻页
打开页面发现电子书是嵌入到一个 iframe 里展示的,使用 epubjs 自带的监听方法 this.rendition.on 来对 ‘touchStart' 和 ’touchEnd' 进行监听,通过 event.timeStamp 判断是否翻页,然后调用this.rendition.prev()/next() 函数即可处理翻页。event.changedTouches 对象是指触碰的手指,这里判断一只手指的动作。

this.rendition.on('touchStart', event => {
    this.touchStartX = event.changedTouches[0].clientX;
    this.touchStartTime = event.timeStamp;
}
this.rendition.on('touchEnd', event => {
    this.touchEndX = event.changedTouches[0].clientX;
    this.touchEndTime = event.timeStamp;
    const offsetX = event.changedTouches[0].clientX - this.touchStartX;
    if (this.touchEndTime - this.touchStartTime < 500){
        if (offset > 40) {
            // 上一页
            this.prevPage();
        } else if (offsetX < -40) {
            // 下一页
            this.nextPage();
        }
    } else {
        // 否则显示标题和菜单栏
        this.toggleMenuAndTitle();
    }
    event.preventDefault();
    event.stopPropagation();
}

4.标题和菜单栏
这一部分内容实现上比较容易,重点的内容有以下几点:
4.1.HTML 语义化:
在页面布局上使用合理的称谓和嵌套关系,易于理解。标签的命名遵照一定的格式,比如在 EbookReader 组件里:

<template>
  <transition name="slide-down">
    <div class="title-wrapper" v-show="menuVisible">
      <div class="left">
        <span class="icon-back" @click="back"></span>
      </div>
      <div class="right">
        <div class="icon-wrapper">
          <span class="icon-shelf"></span>
        </div>
        <div class="icon-wrapper">
          <span class="icon-cart"></span>
        </div>
        <div class="icon-wrapper">
          <span class="icon-more"></span>
        </div>
      </div>
    </div>
  </transition>
</template>

标签的 class 命名均采用小写,逻辑断点用 '-' 号,字体图标均用 icon 前缀。
方法的命名,均采用驼峰命名,布尔类型的形容词用 ‘Visible' 类的形容词。
div 的嵌套规则上, 需要外层 div 的用 '-wrapper' 来标识,等等。平常开发过程中需要注意这些变量的命名要有统一的规则,DOM 嵌套上也要有合理的顺序,整个项目要保持高度的一致性。

4.2.动画相关:
vue 其实定义好了一套自己的 css 动画规则,我们只需要套用即可,项目中把常用动画封装到了 transition.scss 里,在 global.scss 里进行引入。

4.3.混入机制
之前是通过直接在 EbookReader.vue 文件里利用扩展运算符把 state 添加到 this 中去,
做法如下:

import { mappGetters } from 'vuex'
computed: {
    ...mapGetters(['menuVisible'])
}

但是这种方式的弊端显而易见,需要逐个 vue 文件里去手动引入定义的 state, 所有 vue 文件的一致性不高,那么有没有一种方式可以让所有 vue 文件用同样的方式引入 state 呢?Vue 提供了 mixin 混入机制,具体做法是在所需的 vue 文件里用以下方式写入:

import { ebookMixin } from '../../utils/mixin'
mixin: [ebookMixin]

在 src/utils 下新建 mixin.js 文件,写入

import { mapGetters, mapActions } from 'vuex'

export const ebookMixin = {
    computed: {
        ...mapGetters([
        'menuVisible',
        ])
    },
    methods: {
        ...mapActions([
            'menuVisible',
        ]),
        // 其他方法
        ···
    }
    // 其他
    ...
}

这样的做法既能将公有的方法抽象出来,然后通过引入 mixin 一次性导入即可,十分方便。

5.字号,字体设置UI

字号设置UI.png
字体设置UI.png

阅读器部分的菜单栏单独抽象出来为 EbookMenu.vue 文件,在 EbookMenu 里又把字体,目录,进度,模式 设置单独作为组件:

<ebook-slide></ebook-slide>
<ebook-setting-font></ebook-setting-font>
<ebook-setting-font-popup v-show="fontFamilyVisible"></ebook-setting-font-popup>
<ebook-setting-theme></ebook-setting-theme>
<ebook-setting-progress></ebook-setting-progress>

6.字号,字体设置
通过 epubjs 自带的函数对字体进行设置:

this.currentBook.rendition.themes.fontSize(fontSize)
this.currentBook.rendition.themes.font('Times New Roman')

由于 epubjs 渲染的内容在 iframe 里, 而 web 字体的引用为 css 文件引入的方式,此时需要 epubjs 提供的钩子函数将 css 文件加载到 iframe 里去:

 // epubjs勾子函数为电子书显示的iframe注入内容
        this.rendition.hooks.content.register(contents => {
          // console.log(process.env.VUE_APP_RES_URL)
          Promise.all([
            // 电子书显示添加字体
            contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/cabin.css`),
            contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/daysOne.css`),
            contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/montserrat.css`),
            contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/tangerine.css`)
          ]).then(() => {
          })
        })

7.字体设置弹窗
css 实现进度条,如上图,左右是最小和最大字号的 A 字母预览,中间是 css 实现的进度条,这种效果如何实现要有合理的布局,清晰的结构,EbookSettingFont.vue 里进行了实现,涉及到了 flex 布局,利用高度为 0 设置 border-top 和 border-left 来模拟进度条, 点击改变 vuex 里的 defaultFontSize, 并利用以上提到的方法进行电子书字号的更改。
弹窗的动画需要注意的是,需要注意设置同样的高度才不会不一致的情况。

8.字号和字体的离线存储
使用 localStorage 进对电子书设置的字体,字号等信息进行缓存,utils/localStorage.js 里预先定义了一系列 localStorage 的操作方法。

9.字体设置国际化
vue-i18n 的使用, 预先定义所有需要的字体内容, 通过 vue.$t('name') 来使用国际化内容。

10.主题设置
分为 4 种主题,在 utils/book.js 里定义好了这 4 中主题的 css 样式,然后在 EbookSettingTheme.vue 里调用 epubjs 的 rendition.theme.select(theme) 来改变主题。

11.全局主题样式
全局主题根据 4 种不同样式抽象出 4 个 css 文件放置到 nginx 服务文件夹下, 在 utils/ book.js 下创建动态添加/删除 link 标签的方法,通过这种方式向页面注入 css 文件从而实现全局主题样式的切换。

阅读器第一部分完,阅读器第二部分将会实现阅,目录功能和全文搜索功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350