通过动态路由动态的向EbookReader组件传入电子书路径
掌握了动态路由的配置方式
在EbookReader组建中实现了阅读器的解析和渲染
// 阅读器解析
initEpub () {...}
了解了Epubjs的解析方式
通过 vue-cli 3.0 新建.env.development文件,管理引入环境变量
.env.development
VUE_APP_RES_URL=http://192.168.0.156:9000/
掌握了vue-cli 3.0 的环境变量配置
通过手势操作实现了电子书的翻页,主要通过rendition.on方法绑定了touchstart和touchend事件对手势进行识别
this.rendition.on('touchstart', event => {
...
})
this.rendition.on('touchend', event => {
...
})
掌握了手势操作的识别
集成了阅读器的标题和菜单组件,集成了mixin和vuex机制对组件进行解耦
mixin.js
book.js
actions.js
getters.js
index.js
了解了与之前不同的架构方式,正在运用中... 待掌握
实现了字体风格和字号设置,运用了localstorage缓存阅读器的配置,引入了vue-i18n国际化插件实现了中英文两种语言的切换
了解了vue-i18n国际化的运用和Epubjs的字体设置
实现了主题设置功能,分为两部分:
-
阅读器的主题:阅读器的主题主要是通过Epubjs实现。核心方法是通过theme对象的register方法注册主题,select方法切换主题。
-
App界面的主题:App界面的主题主要是通过动态添加删除css实现的
了解了Epubjs的主题样式设置
阅读进度的实现,使用了html5的range控件,通过Epubjs的locations对象实现了定位和阅读百分比的显示,还实 现了上一章下一章以及获取当前名称还有阅读时间统计的功能
// 拖动进度条过程
onProgressInput (progress) {
this.setProgress(progress).then(() => {
this.updateProgressBackground()
})
}
// 进度条拖动结束松手
onProgressChange (progress) {
this.setProgress(progress).then(() => {
this.displayProgress()
this.updateProgressBackground()
})
}
// 展示当前进度所在的页面
displayProgress () {
const cfi = this.currentBook.locations.cfiFromPercentage(this.progress / 100)
this.display(cfi)
}
// 进度条左键头点击翻页
prevSection () {
if (this.section > 0 && this.bookAvailable) {
this.setSection(this.section - 1).then(() => {
this.displaySection()
})
}
}
// 进度条右键头点击翻页
nextSection () {
if (this.section < this.currentBook.spine.length - 1 && this.bookAvailable) {
this.setSection(this.section + 1).then(() => {
this.displaySection()
})
}
}
运用了HTML5的range控件,了解了Epubjs的locations对象
实现了阅读器的目录功能,值得一提的是封装了一个flatten方法,flatten方法运用了ES6的新特性,将一个树状结构转变为了一维数组,并运用find方法判断出了每一个对象所属的层级,实现了多级目录的缩进展示。
// flatten方法
export function flatten (arr) {
return [].concat(...arr.map(item => [].concat(item, ...flatten(item.subitems))))
}
// flatten方法结合find方法实现目录缩进
this.book.loaded.navigation.then(nav => {
const navItem = flatten(nav.toc)
function find(item, level = 0) {
return !item.parent ? level : find(navItem.filter(parentItem =>
parentItem.id === item.parent)[0], ++level)
}
navItem.forEach(item => {
item.level = find(item)
})
})
了解了如何解析电子书的内容,获取了电子书封面、作者、标题等信息。
学习到了将树状结构转变成一维数组,实现出多级目录缩进显示
通过官方搜索算法实现了全文搜索功能,搜索关键字的高亮显示,以及精确跳转关键字的位置和阅读器中的文字高亮显示,重点研究了如何通过ES6将二维数组转变为一维数组。
//全局搜索功能
search () {
if (this.searchText && this.searchText.length > 0) {
this.doSearch(this.searchText).then(list => {
this.searchList = list
// 二维数组转一维数组
this.searchList.map(item => {
item.excerpt = item.excerpt = item.excerpt.replace(this.searchText, `<span class="content-search-text">${this.searchText}</span>`)
return item
})
})
}
}
了解了官方搜索算法
重点学习到了如何将二维数组转成一维数组
实现了书签功能,主要是通过touchmove和touchend事件实现了复杂的手势交互
手势操作的要点:
-
通过绝对定位改变top值的方法实现了整个界面的下拉效果
// 下拉设置top值 move (v) { this.$refs.ebook.style.top = v + 'px' }
-
通过动态加载transition实现了界面的回弹动画
// 回弹动画设置 restore () { ... }
-
通过css绘制了一个底部透明三角的书签
.bookmark { width: 0; height: 0; border-width: px2rem(36) px2rem(10) px2rem(10) px2rem(10); border-style: solid; border-color: white white transparent white; }
-
设置了不同的状态来区分手势操作的不同阶段,触发不同的操作
// 状态1:未超过书签的高度 beforeHeight () { ... } // 状态2:未到达临界状态 beforeThreshold (v) { ... } // 状态3: 超越临界状态 afterThreshold (v) { ... } // 状态4:书签归位 restore () { ... }
学习到了如何通过绝对行为改变top值实现整个界面的下拉,通过css绘制一个书签
研究了利用不同的四种状态来区分手势操作的不同阶段而触发不同的操作
实现了添加书签的功能,比较重点的是如何获取当前书签所在页的文本内容。之后实现了书签删除的功能,主要是运用了ES6的filter方法做数组删除操作
// 添加书签
addBookmark () { ... }
// 删除书签
removeBookmark () { ... }
了解了如何获取当前书签所在页的文本内容
重温了ES6的 filter 方法
添加了页眉和页脚,如何通过滚动模式阅读电子书,对微信端做了一些适配,同时兼容了PC端。兼容PC端的时候对css进行了一些优化,添加了对鼠标事件的支持,通过mousedown、mousemove、mouseup完成对书签下拉动画的支持。鼠标事件比手势事件更加复杂一点,通过四种状态来解决开发鼠标事件中的问题。
// 鼠标事件的四种状态
// 1. 鼠标进入
// 2. 鼠标进入后的移动
// 3. 鼠标从移动状态松手
// 4. 鼠标还原
// 鼠标左键点击
onMouseEnter (e) {
this.mouseState = 1
this.mouseStartTime = e.timeStamp
e.preventDefault()
e.stopPropagation()
}
// 鼠标左键下拉
onMouseMove (e) {
if (this.mouseState === 1) {
this.mouseState = 2
} else if (this.mouseState === 2) {
let offsetY = 0
if (this.firstOffsetY) {
offsetY = e.clientY - this.firstOffsetY
this.setOffsetY(offsetY)
} else {
this.firstOffsetY = e.clientY
}
}
e.preventDefault()
e.stopPropagation()
}
// 鼠标左键下拉松开还原
onMouseEnd (e) {
if (this.mouseState === 2) {
this.setOffsetY(0)
this.firstOffsetY = null
this.mouseState = 3
e.preventDefault()
e.stopPropagation()
} else {
this.mouseState = 4
}
const time = e.timeStamp - this.mouseStartTime
if (time < 100) {
this.mouseState = 4
}
e.preventDefault()
e.stopPropagation()
}
重点学习了如何兼容PC端的鼠标事件
实现了阅读器的分页算法
// 阅读器的分页
this.book.ready.then(() => { ... })
了解了Epubjs的阅读器分页算法
标题 + 搜索 → 随机推荐 → 猜你喜欢 → 热门推荐 → 精选 → 分类推荐 → 全部分类 → 分类列表 → 搜索列表 → 详情页
标题搜索页和随机推荐的交互实现
标题搜索框滑动的交互难点:搜索框向上移动的过程中逐渐变窄,左边的返回按钮在搜索框上移的时候会有一个轻微的移动动画
- 向下滑动时标题文本和右侧的图标有一个向下的渐影并且消失的过程
- 搜索框向上移动到标题的位置
- 搜索框向上移动的过程中会逐渐的变窄 (难点)
- 左侧的返回按钮会向下微微的移动
- 没有移动之前下方没有阴影,移动之后下方会产生阴影,复原之后阴影消失
- 弹出卡片
- 卡片翻转动画 (难点)
- 烟花动画
- 弹出推荐图书
flex布局
input绑定keyup事件,回车触发搜索
组件基本架子、基础样式
主要逻辑:点击编辑按钮出现取消按钮,标题下的文本随着书籍的数量改变,如果不在编辑状态标题下的文本隐藏,取消按钮切换成编辑按钮
运用了基础的flex布局
主要逻辑:当进入书架的时候滑动页面,搜索框会跟着图书列表一起移动,点击搜索框之后出现tab,搜索框会向上移动并且有过度动画,当tab出现之后滑动页面之后,搜索框和tab固定住,列表进行滑动
优化了过度动画不流畅,添加了滚动阴影
使用了axios的常用api获取到了电子书的全部数据
获取数据之后对数据进行了展示
根据屏幕的不同大小动态计算了图书封面的高度,使用了基本的flex布局
基础样式开发
主要逻辑:点击添加图标的时候跳转到首页,选择图书添加到书架
主要逻辑:点击编辑按钮的时候,封面右下角出现多选图标,页面底部出现footer组件并且含有过渡动画。当没有选中多选图标的时候,footer组件的透明度为0.5,当选中多选图标的时候,标题下的文本会根据书籍的数量而改变字段,footer组件透明度为1
通过vue-create-api插件实现
主要逻辑:在选中图书后,点击footer中的按钮的时候,根据不同的状态显示不同的内容并且具有不同的功能
主要知识点:IndexedDB数据库
IndexedDB数据库特点:存储容量大一般不少于250M,甚至没有上限 、异步 、支持二进制存储等等,不仅可以存储字符串,还可以存储二进制文件(ArrayBuffer对象和blob对象),这里电子书就是blob对象。
通过npm下载 localforage库
localforage库是开源的可以对indexedDB数据库操作的库
基于localforage的简单封装
// 全部都是异步,通过回调操作
// 因为是本地数据库,所以提供了key,data cb:成功的回调 cb2:失败的回调
export function setLocalFroage (key, data, cb, cb2) {
// setItem设置完成之后返回一个promise对象
localForage.setItem(key, data).then((value) => {
if (cb) cb(value)
}).catch(function(err) {
if (cb2) cb2(err)
})
}
// 与set类似
export function getLocalForage (key, cb) {
localForage.getItem(key, (err, value) => {
cb(err, value)
})
}
// 根据key删除指定数据
export function removeLocalForage (key, cb, cb2) {
localForage.removeItem(key).then(function() {
cb()
}).catch(function(err) {
cb2(err)
})
}
// 清空所有数据
export function clearLocalForage (cb, cb2) {
localForage.clear().then(function() {
if (cb) cb()
}).catch(function(err) {
if (cb2) cb2(err)
})
}
// 获取indexedDB中一共有多少key
export function lengthLocalForage (cb) {
localForage.length().then(
numberOfKeys => {
if(cb) cb(numberOfKeys)
}).catch(function(err) {
console.log(err)
})
}
// 遍历key、value
export function iteratorLocalForage () {
localForage.iterate(function(value, key, iterationNumber) {
console.log([key, value])
}).then(function() {
console.log('Iteration has completed')
}).catch(function(err) {
console.log(err)
})
}
// 判断当前浏览器是否支持indexedDB
export function support () {
const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || null
if (indexedDB) {
return true
} else {
return fasle
}
}
主要逻辑:弹出对话框,选择书籍移动到某个分组。
弹出框实现思路:定义一个dialog,根据dialog定义里面不同的样式(把dialog做成一个通用组件),通过 vue-create-api 来做显示隐藏。
加入分组实现思路:获取到选中书籍,根据dialog弹框的按钮选中某个分组,然后把书籍添加到选中分组中。添加完毕之后会有一个布局变动的动画。
主要逻辑:点击某个分组进入当前分组页,选中一本或多本书籍可以移除分类、创建分类、删除分类,左上角有修改按钮,点击之后可以编辑修改当前分类名称。
基本没有什么难点,主要是audio的三个事件,canplay事件timeupdate事件和ended事件
获取语音合成的API
重点需要注意两个主要状态,一个是当前是否播放,一个是当期播放的状态
学习到了vue-cli 3.0的搭建,以及一些基本的配置
学习到了与之前开发不同的动态路由配置
学习到了Nginx的使用
重温了vuex的运用
学习到了mixin、vuex机制的架构方式
重温了ES6的一些方法,能在各种场景更加灵活的运用
重温了flex布局和touch事件
了解了Epubjs阅读器基本API的使用方法
了解了vue-i18n国际化和vue-create-api的使用
学习到了数组降维,树状结构转变一维数组,实现多级目录缩进显示
学习到了移动端touch事件如何兼容PC端的mouse事件
学习到了浏览器indexedDB数据库的使用
了解了基于indexedDB的操作库 localForage
学习了H5的audio控件的常用方法和状态控制
了解了科大讯飞在线语音合成API
学习了Node.je + express 编写API
【非常深刻的体会到这种架构方式的优势:大大降低组件之间的耦合度,大幅度提高代码的可维护性可扩展性】