4.阅读器–阅读进度、目录、全文搜索功能开发

4.12. 全文搜索功能实现(搜索算法+数组降维)

该方法 中,q为输入的关键词,在全篇电子书中,查找关键词,返回关键词所在的位置。
 doSearch(q){return Promise.all(this.currentBook.spine.spineItems.map(section => section.load(this.currentBook.load.bind(this.currentBook)).then(section.find.bind(section,q)).finally(section.unload.bind(section)))).then(results => {// return Promise.resolve([].concat.apply([],results))console.log(results);})},

试一下调用该方法

if (this.currentBook) {this.currentBook.ready.then(()=>{this.doSearch('added').then(results=>{console.log(results);})})}

得到的是一组多维数组

因为得到的是二维数组,所以需要像 目录那样降维。

实际上这句就已经采用了降维的方法。
想要实现搜索功能,需要在我们搜索的时候,把目录列表隐藏掉,然后把搜索列表展示出来

再添加搜索列表

试一下 执行

看,成功打印出来了

4-13 全文搜索功能实现 (搜索关键字高亮+搜索结果高亮显示)

把返回的text 中的关键字 replace成 用span包裹起来,让span中的文字高亮

    search(){if (this.currentBook&&this.searchText.length>0&&this.searchText){this.currentBook.ready.then(()=>{this.doSearch(this.searchText).then((results)=>{this.searchList = resultsthis.searchList.map(item=>{item.excerpt = item.excerpt.replace(this.searchText,`<span class="content-search-text">${this.searchText}</span>`);return item;})});})}},

当在输入框按下回车键的时候执行该方法

成功完成任务

接下来,要实现的是 点击对应的搜索列表项 跳转到对应的搜索结果页面,

只需要添加这个点击事件即可实现,当然,跳转后应该将菜单栏消失
所以我们把点击方法改成

displaySearch(target){this.display(target,()=>{this.hideMenuVisible();})},

还没完成,跳转到对应的页面之后,也应该将关键词高亮显示
电子书提供了一个方法

同时把上面方法改一下

 displaySearch(target,highlight = false){this.display(target,()=>{this.hideMenuVisible();if (highlight){this.currentBook.rendition.annotations.highlight(target);}})},


可见搜索结果高亮显示也完成了。

4-14 目录加载动画实现

效果如图

组件代码

<template><div class="ebook-loading"><div class="ebook-loading-wrapper"><div class="ebook-loading-item" v-for="(item, index) in data" :key="index"><div class="ebook-loading-line-wrapper" v-for="(subItem, subIndex) in item" :key="subIndex"><div class="ebook-loading-line" ref="line"></div><div class="ebook-loading-mask" ref="mask"></div></div></div><div class="ebook-loading-center"></div></div></div>
</template><script>import { px2rem } from './../../utils/utils'export default {data() {return {data: [[{}, {}, {}],[{}, {}, {}]],maskWidth: [{ value: 0 },{ value: 0 },{ value: 0 },{ value: 0 },{ value: 0 },{ value: 0 }],lineWidth: [{ value: 16 },{ value: 16 },{ value: 16 },{ value: 16 },{ value: 16 },{ value: 16 }],add: true,end: false}},methods: {},mounted() {this.task = setInterval(() => {this.$refs.mask.forEach((item, index) => {const mask = this.$refs.mask[index]const line = this.$refs.line[index]let maskWidth = this.maskWidth[index]let lineWidth = this.lineWidth[index]if (index === 0) {if (this.add && maskWidth.value < 16) {maskWidth.value++lineWidth.value--} else if (!this.add && lineWidth.value < 16) {maskWidth.value--lineWidth.value++}} else {if (this.add && maskWidth.value < 16) {let preMaskWidth = this.maskWidth[index - 1]if (preMaskWidth.value >= 8) {maskWidth.value++lineWidth.value--}} else if (!this.add && lineWidth.value < 16) {let preLineWidth = this.lineWidth[index - 1]if (preLineWidth.value >= 8) {maskWidth.value--lineWidth.value++}}}mask.style.width = `${px2rem(maskWidth.value)}rem`mask.style.flex = `0 0 ${px2rem(maskWidth.value)}rem`line.style.width = `${px2rem(lineWidth.value)}rem`line.style.flex = `0 0 ${px2rem(lineWidth.value)}rem`if (index === this.maskWidth.length - 1) {if (this.add) {if (maskWidth.value === 16) {this.end = true}} else {if (maskWidth.value === 0) {this.end = true}}}if (this.end) {this.add = !this.addthis.end = false}})}, 20)},beforeDestroy() {if (this.task) {clearInterval(this.task)}}}
</script><style lang="scss" rel="stylesheet/scss" scoped>@import "../../assets/styles/global";.ebook-loading {position: relative;z-index: 500;width: px2rem(63);height: px2rem(40);background: transparent;border: px2rem(1.5) solid #d7d7d7;border-radius: px2rem(3);.ebook-loading-wrapper {display: flex;width: 100%;height: 100%;.ebook-loading-item {display: flex;flex-direction: column;flex: 1;padding: px2rem(7) 0;box-sizing: border-box;.ebook-loading-line-wrapper {flex: 1;padding: 0 px2rem(7);box-sizing: border-box;@include left;.ebook-loading-mask {flex: 0 0 0;width: 0;height: px2rem(1.5);}.ebook-loading-line {flex: 0 0 px2rem(16);width: px2rem(16);height: px2rem(2);background: #d7d7d7;}}}.ebook-loading-center {position: absolute;left: 50%;top: 0;width: px2rem(1.5);height: 100%;background: #d7d7d7;}}}
</style>

5.阅读器–书签功能、页眉页脚及兼容性优化

5-1 书签手势实现(页面下拉)

epub电子书是没有touchmove这个监听事件的,要想要这个效果,就必须添加个蒙层


当然要把它背景弄成透明
这样,监听事件就放在 这个蒙层上就好,通过蒙层来改变 offsetY的值,再通过offsetY的值来改变ebook的位置

蒙层上的监听事件

             move(e){let offsetY = 0;if (this.firstOffsetY){offsetY = e.changedTouches[0].clientY - this.firstOffsetYthis.setOffsetY(offsetY)console.log(this.offsetY);} else{this.firstOffsetY = e.changedTouches[0].clientY;}e.preventDefault();e.stopPropagation();},moveEnd(e){this.setOffsetY(0);this.firstOffsetY = null;},onMaskClick(e){const offsetX = e.offsetX;const width = window.innerWidth;if (offsetX>0 && offsetX<width * 0.3){this.prevPage();} else if(offsetX>0&&offsetX>width*0.7){this.nextPage();}else {this.toggleTitleAndMenu();}},

可见,已经改变了offsetY的值
接下来 index.vue中监听offsetY的改变来改变reader的top

 watch:{offsetY(v){if(v>0){this.move(v)}else if(v===0){this.restore()}}},methods:{restore(){this.$refs.ebook.style.top = 0+'px';this.$refs.ebook.style.transition = 'all 0.2s linear';setTimeout(()=>{this.$refs.ebook.style.transition = '';},200)},move(v){this.$refs.ebook.style.top = v+'px';},},

这样就实现了 下拉功能。
这里需要解释一下里面的

 setTimeout(()=>{this.$refs.ebook.style.transition = '';},200)

这是因为,那里设置的
this.$refs.ebook.style.transition = ‘all 0.2s linear’; 刚好为两秒,当松手后,动画结束,事件刚好200ms

5-2 书签手势实现(书签组件)

先创建这个大组件

<template><div class="ebook-bookmark"><div class="ebook-bookmark-text-wrapper"><div class="ebook-bookmark-down-wrapper"><span class="icon-down"></span></div><div class="ebook-bookmark-text">{{text}}</div></div><div class="ebook-bookmark-icon-wrapper"><Bookmark :color="'red'" :width="15" :height="35"></Bookmark><!--<div class="icon"></div>--></div></div>
</template><script>import Bookmark from "./../../components/common/Bookmark"const BLUE = "#346cbc";const WHITE = "#fff";export default {name: "EbookBookmark",data(){return{text:this.$t('book.pulldownAddMark')}},components:{Bookmark}}
</script><style scoped lang="scss">@import "./../../assets/styles/global";.ebook-bookmark{position: absolute;top: px2rem(-35);left: 0;z-index: 200;width: 100%;height: px2rem(35);background-color: black;.ebook-bookmark-text-wrapper{position: absolute;right: px2rem(45);bottom: 0;display: flex;.ebook-bookmark-down-wrapper{font-size: px2rem(14);color: white;transition:all 0.2s linear;@include center;.icon-down{}}.ebook-bookmark-text{font-size: px2rem(14);color: white;}}.ebook-bookmark-icon-wrapper{position: absolute;right: 0;bottom: 0;margin-right:px2rem(15) ;.icon{/*width: 0;*//*height: 0;*//*border-width: px2rem(50) px2rem(10) px2rem(10) px2rem(10);*//*border-style: solid;*//*border-color: white white transparent white;*/}}}
</style>


这也是一个组件

<template><div class="bookmark" :style="style" ref="bookmark"></div>
</template><script>import {px2rem} from "../../utils/utils";export default {name: "Bookmark",props:{width:Number,height:Number,color:String},computed:{style(){if (this.color){console.log(this.color);return {borderColor:`${this.color} ${this.color} transparent ${this.color}`}} else{return{ borderColor:``}}}},methods:{refresh(){if (this.height && this.width){this.$refs.bookmark.style.borderWidth = `${px2rem(this.height - 5)}rem ${px2rem(this.width / 2)}rem ${px2rem(5)}rem ${px2rem(this.width / 2)}rem`}},},mounted(){this.refresh();}}
</script><style scoped lang="scss">@import "./../../assets/styles/global";.bookmark{width: 0;height: 0;border-width: px2rem(50) px2rem(10) px2rem(10) px2rem(10);border-style: solid;border-color: white white transparent white;}
</style>

5-3 书签手势实现(下拉状态管理)

下拉阶段分为三个阶段
第一阶段:不变。
第二阶段:
第三阶段:

代码实现,通过监听offsetY:

 watch:{offsetY(v){if(v>=this.height && v<=this.threshold){console.log("到达第二阶段");this.$refs.bookmark.style.top = `${-v}px` //使书签吸顶this.text = this.$t('book.pulldownAddMark'); //文字改为添加书签this.color = WHITE;}else if(v>=this.threshold){console.log("到达第三阶段");this.$refs.bookmark.style.top = `${-v}px`  //使书签吸顶this.text = this.$t('book.pulldownAddMark'); //文字改为释放书签this.color = BLUE;}}},

5-4 书签手势实现(书签添加删除交互)

先改变 箭头方向,在第三阶段的时候,箭头向上

第二阶段的时候,箭头向下

在上面代码中添加这个即可实现

添加功能完成 ,我们也应该想想一下,删除书签该怎么操作,
所以下拉的时候,要判断,是已经有了标签,还是没有,有了就是要进行的是删除操作

添加一阶段,和释放阶段
释放的时候,判断 是否 当前为书签页,是则 删除,否则添加,

watch:{offsetY(v){if (!this.bookAvailable || this.menuVisible){return;}if(v>=this.height && v<=this.threshold){//console.log("到达第二阶段");this.beforeThreshold(v);}else if(v>=this.threshold){// console.log("到达第三阶段");this.afterThreshold(v);}else if(v>0&&v<this.height){//第一阶段this.beforeHeight(v);}else if (v ===0 ){//状态为0时this.restore()}},isFixed(v){// console.log(v);}},
methods:{addBookmark(){},removeBookmark(){},//下拉的第一阶段beforeHeight(){if (this.isBookmark){this.text = this.$t('book.pulldownDeleteMark'); //文字改为释放书签this.color = BLUE;this.isFixed = true;} else{this.text = this.$t('book.pulldownAddMark'); //文字改为释放书签this.color = WHITE;this.isFixed = false;}},//下拉的第二阶段beforeThreshold(v){const iconDown = this.$refs.iconDown;this.$refs.bookmark.style.top = `${-v}px`; //使书签吸顶if (this.isBookmark){//当页是标签页this.text = this.$t('book.pulldownDeleteMark'); //文字改为添加书签this.color = BLUE;} else {this.text = this.$t('book.pulldownAddMark'); //文字改为添加书签this.color = WHITE;}if(iconDown.style.transform === 'rotate(180deg)'){iconDown.style.transform = ''}this.isFixed = false;},//下拉的第三阶段afterThreshold(v){const iconDown = this.$refs.iconDown;this.$refs.bookmark.style.top = `${-v}px`  //使书签吸顶if (this.isBookmark){this.text = this.$t('book.releaseDeleteMark'); //文字改为释放书签this.color = WHITE;this.isFixed = false;} else{this.text = this.$t('book.releaseAddMark'); //文字改为释放书签this.color = BLUE;this.isFixed = true;}if(iconDown.style.transform === ''){iconDown.style.transform = 'rotate(180deg)'}},//归为restore(){setTimeout(()=>{this.$refs.bookmark.style.top = `${-this.height}px`;this.$refs.bookmark.style.transform = 'rotate(0deg)';},200);if (this.isFixed){console.log("isFixed");this.setIsBookmark(true);this.addBookmark();}else {this.setIsBookmark(false);console.log("nofixed");this.removeBookmark();}}},

5-6 书签功能实现

添加书签

            addBookmark(){this.bookmark = getBookmark(this.fileName);if (!this.bookmark){this.bookmark = [];}const currentLocation = this.currentBook.rendition.currentLocation();const cfibase = currentLocation.start.cfi.replace(/!.*/,'');const cfistart = currentLocation.start.cfi.replace(/.*!/,'').replace(/\)$/,'');const cfiend = currentLocation.end.cfi.replace(/.*!/,'').replace(/\)$/,'');const cfirange = `${cfibase}!,${cfistart},${cfiend})`;this.currentBook.getRange(cfirange).then(range => {const text = range.toString().replace(/\s\s/g,'');this.bookmark.push({cfi:currentLocation.start.cfi,text:text})saveBookmark(this.fileName,this.bookmark);})/**@@@ 1.console.log(currentLocation)*  end: {index: 8, href: "A468350_1_En_5_Chapter.html", cfi: "epubcfi  (/6/18[A468350_1_En_5_Chapter]!/4/12/6[Sec3]/10[Par20]/3:426)", displayed: {…}, location: 235, …}* start: {index: 8, href: "A468350_1_En_5_Chapter.html", cfi: "epubcfi(/6/18[A468350_1_En_5_Chapter]!/4/12/6[Sec3]/8[Par19]/1:680)", displayed: {…}, location: 234, …}__proto__: Object2.console.log(cfibase)   -->   epubcfi(/6/18[A468350_1_En_5_Chapter]3.console.log(cfistart)  -->   /4/12/6[Sec3]/8[Par19]/1:6804.console.log(range);Range {startContainer: text, startOffset: 680, endContainer: text, endOffset: 680,…}5.console.log(cfiend)    -->   /4/12/6[Sec3]/10[Par20]/3:4266.console.log(range.toString());//得到的是本页的内容*/},

删除 书签

 removeBookmark(){const currentLocation = this.currentBook.rendition.currentLocation();const cfi = currentLocation.start.cfi;this.bookmark = getBookmark(this.fileName);if(this.bookmark){saveBookmark(this.fileName,this.bookmark.filter(item=>item.cfi!==cfi));console.log(this.bookmark);this.setIsBookmark(false);}},

接下来就要把书签渲染上去呢,过程和 目录一样

这是复制搜索列表的,稍微做了点修改

<template><div class="ebook-slide-marks"><scroll class="slide-search-list":top="66":bottom="48"><div class="slide-search-item" v-for="(item,index) in bookmark" :key="index" v-html="item.text"@click="displaySearch(item.cfi)"></div></scroll></div>
</template><script>import {ebookMixin} from "./../../utils/mixin"import {getBookmark} from "./../../utils/localStorage"import scroll from "./../../components/common/Scroll"export default {name: "EbookSlideMarks",mixins:[ebookMixin],components:{scroll},data(){return {bookmark:''}},mounted(){this.bookmark = getBookmark(this.fileName)},methods: {displaySearch(target) {this.display(target, () => {this.hideMenuVisible();})}}}
</script><style scoped lang="scss">@import "./../../assets/styles/global";.ebook-slide-marks{font-size: px2rem(12);.slide-search-list{width: 100%;padding: 0 px2rem(15);box-sizing: border-box;.slide-search-item{font-size: px2rem(14);line-height: px2rem(16);padding: px2rem(20) 0;}}}</style>

5-7 页眉和页脚功能实现


两个组件

<template><div class="ebook-header"><span class="ebook-header-text">{{getSectionName}}</span></div>
</template><script>import {ebookMixin} from "../../utils/mixin";export default {name: "EbookHeader",mixins:[ebookMixin],computed:{getSectionName() {console.log("草泥马");if (this.section) {if (this.currentBook){const section = this.currentBook.section(this.section)if (section && section.href && this.currentBook && this.currentBook.navigation) {// return this.currentBook.navigation.get(section.href).labelreturn this.navigation[this.section].label}}}else {console.log("草222泥马");}}}}
</script><style scoped lang="scss">@import "./../../assets/styles/global";.ebook-header{position: absolute;top: 0;left: 0;z-index: 100;width: 100%;height: px2rem(48);padding: 0 px2rem(15);box-sizing: border-box;overflow: hidden;@include left;.ebook-header-text{font-size: px2rem(12);color: #6d7178;}}</style>
<template><div class="ebook-footer"><span class="ebook-footer-text">{{progress}}%</span></div>
</template><script>import {ebookMixin} from "../../utils/mixin";export default {name: "EbookFooter",mixins:[ebookMixin],}
</script><style scoped lang="scss">@import "./../../assets/styles/global";.ebook-footer{position: absolute;bottom: 0;left: 0;z-index: 100;width: 100%;height: px2rem(48);padding: 0 px2rem(15);box-sizing: border-box;@include right;.ebook-footer-text{font-size: px2rem(12);color: #6d7178;}}</style>

5-10 自适应布局优化(PC端布局优化)

主要修改这里


已经把这里改为弹性布局

5-11 自适应布局优化(书签支持鼠标事件)

touch那一套 对pc是不管用的
所以需要另外添加 mouse事件

           //1.鼠标进入// 2.鼠标进入后移动// 3.鼠标从移动转台松手// 4.鼠标还原onMouseEnter(e){this.mouseState = 1;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.firstOffsetYthis.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;}else{this.mouseState = 4;}e.preventDefault();e.stopPropagation();},onMaskClick(e){if (this.mouseState && (this.mouseState === 2 || this.mouseState ===3)){return;}const offsetX = e.offsetX;const width = window.innerWidth;if (offsetX>0 && offsetX<width * 0.3){this.prevPage();} else if(offsetX>0&&offsetX>width*0.7){this.nextPage();}else {this.toggleTitleAndMenu();}},

商业级web阅读器项目(下下)相关推荐

  1. 商业级web阅读器项目(上)

    1.技术难点分析 阅读器开发: 分页算法.全文搜索算法.引入web字体.主题设计 离线存储机制设计:LocalStorage+IndexDB 实现各种复杂手势+交互动画,如何兼容手势+鼠标操作 利用v ...

  2. 双语web阅读器+书城设计与实现

    背景: 前段日子心血来潮,突然想做个小说阅读器,带翻译功能的. 原因大概来之已久了,主要是我本人是一个超级大书虫,特别喜欢看网络小说.经典小说.名著.心理学等"有意思"的书. 从接 ...

  3. Web阅读器开发系列教程(入门篇)

    作者:Sam 前言 最近我在慕课网发布了两门关于Web阅读应用开发的课程,采用Vue全家桶开发.免费课是入门级课程,初步实现了一个阅读器.实战课是进阶课程,实现了一个高性能的互联网阅读应用.两个项目都 ...

  4. 开源的电子书阅读器项目

    论坛会员zhang_cn分享的开源的电子书阅读器项目供大家参考 iphone平台上开源的电子书阅读器项目: https://github.com/vfr/Reader https://github.c ...

  5. ios 开源的电子书阅读器项目

    iphone平台上开源的电子书阅读器项目: https://github.com/vfr/Reader https://github.com/brow/leaves https://github.co ...

  6. 嵌入式Linux系统的电子书阅读器项目1——Overview

    摘要:本项目是参考@韦东山的视频教程第三期嵌入式Linux jz2440平台的电子书项目框架,使用S5PV210 SOC芯片,Smart210开发板,基于Linux kernel 3.0.8做的移植与 ...

  7. 快速入门Web阅读器开发

    知识点: 阅读器工作原理 ePub 和 mobi ePub(Electronic Publication) 电子出版物 mobi 是 Amazon Kindle 的电子书格式 开发流程 当前Node版 ...

  8. Vue开发Web阅读器(一)

    本项目使用vue3 初始化项目后需配置vue.config.js,官方文档:https://cli.vuejs.org/zh/config/#vue-config-js 我的暂时配置: const p ...

  9. 嵌入式Linux系统的电子书阅读器项目3——Encode Font System

    1.字符编码(Encode)与字体(Font)显示概述 如图1,大家在阅读器界面看到的不同字体和尺寸的"好"字(左边宋体小二,右边楷体小二),在电子书txt格式文档中的原始数据是如 ...

  10. Android电子书阅读器开发笔记(1):创建电子书阅读器项目

    项目开发工具是Android Studio. 创建项目 选择菜单命令:File->New->New Project,填入项目信息. (2)设置项目运行的设备和最低Android SDK版本 ...

最新文章

  1. 窗口分析函数_16_找出最后一个元素
  2. 很现实、很暴力的面试法则 —— 来自招聘官的自述
  3. 移除VS解决方案中的TFS版本控制
  4. 图形学教程Lecture 13: RayTracing1(Whitted-Style Ray Tracing)知识点总结
  5. 使用 Arduino 和 LM35 温度传感器监测温度
  6. opencv_python使用cv2.imread()读取中文路径报错问题(转)
  7. Redhat 6.8部署oracle 12c
  8. intelRealsense D435 python开发环境搭建
  9. thinkphp 接收小程序json数组
  10. java gc 监控_JAVA网站full GC监控脚本
  11. java敌人发射子弹
  12. Python+Vue计算机毕业设计面向轨道交通的智能服务链系统jop1x(源码+程序+LW+部署)
  13. simulink电机仿真笔记一
  14. 阵列中条带(stripe)、stripe unit
  15. js中回调函数的理解 举例说明js回调函数
  16. 51单片机下载完程序后不亮_为什么要学习单片机?如何开始上手学习单片机?...
  17. 单元测试框架unittest和HtmlTestRunner报告
  18. vim 全局替换_有一说一,Intellij IDEA 自带的 Vim 插件真心不错!
  19. 字符串存储的可修改性
  20. 5.8日, 服务器青龙面板拉ksjsb脚本教程~

热门文章

  1. informatica joiner组件学习
  2. 2021-08-18 HarmonyOS实战 CommonDialog的使用
  3. SE3可能是苹果最失败的手机,销量不达预期致上市半月降价促销
  4. Win10系统如何设置开机启动项
  5. sqlserver备份还原数据库操作流程
  6. GTK+的编译还真麻烦
  7. c语言向量乘法,运用C语言实现向量积
  8. 有一个做饭好吃的妈妈是一种什么体验?
  9. html关联程序怎么设置,Win10预览版10102系统中如何设置IE浏览器的关联程序【图文】...
  10. 悉尼大学计算机工程专业世界排名,悉尼大学世界排名及专业排名汇总(QS世界大学排名版)...