引言
此系列为 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)
可以看到:
电子书解析成功。
阅读器开发难点
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
阅读器需求分析
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
阅读器部分的菜单栏单独抽象出来为 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 文件从而实现全局主题样式的切换。
阅读器第一部分完,阅读器第二部分将会实现阅,目录功能和全文搜索功能。