1、书签手势实现
书签是有复杂手势交互的功能,在阅读器中将标题和菜单隐藏后才能操作。是个难点。
我们将整个功能拆分为以下几个部分,
第一步,是在阅读器部分监听手势或者鼠标的移动事件。通过监听touchmove事件,获取我们手势移动的高度。
第二步,当我们监听到事件后需要对最外层我们定义的index.vue进行一个移动,需要将index.vue做一个绝对定位,当我们向下的时候,整个界面向下即改变它的Top值进行移动。
第三步,我们需要在顶部位置增加一个书签组件,当我们向下移动的时候书签组件就出现。在书签组件中还要做一些判断,也就是当我们到临界值的时候,书签组件要能够自动固定在上面不再移动,同时能做各种逻辑判断,最后再下拉到某值后,在书签组件中,最右侧有一个书签图标,我们可以通过css来实现让它固定在右上角。
接下来我们就来实现
如何去绑定touchmove事件,我们回到EbookReader组件中,我们之前定义了一个initGesture方法,如下
然后我们再给rendition绑定一个touchmove事件,如下,但是我们在阅读器上触摸移动却不打印出event,因为rendition对象没有提供touchmove事件的绑定,所以我们这里不能采用下面这个方法实现,
也就意味着如果要实现书签功能,这个initGesture就不可行了
那怎么办,我们通过蒙版方式实现书签手势功能。
定个蒙版
我们之前刚开始做左中右点击翻页展示标题菜单栏的时候做过,现在这个手势下拉仍然需要蒙版实现,所以如下我们在EbookReader中增加一个蒙版,然后把背景颜色改为透明background: transparent;即可
然后我们给蒙版添加点击翻页展示菜单栏事件
打印出来的event中有offsetX,就和我们第一次做的那个初始版一样,只不过之前是分左中右三个DOM来实现,这个只是不要了上面的initGesture方法改成了蒙版来实现翻页和展示菜单栏
然后我们来实现移动事件即下拉
你触摸移动过程中会不断触发move事件,所以打印很多move的event,然后最后松手的时候才会触发end事件,所以end事件的event只打印了一次。然后我们来看看move的event中那个changedTouch [0]表示用一个手指,[1]表示两个手指,这个我们就没做这么复杂,然后[0]中会有clientY,它是表示当前move这个位置的Y值位置,所以其实第一个move的clientY是刚开始的Y轴位置,最后那个move的clientY是最后的Y轴位置,那你移动的位置就是用最后那个clientY-最开始那个clientY即可得到你move过程中的Y轴偏移量啦
原理:这里的偏移量主要是Y轴的偏移量,就是最开始的Y轴位置和最后的Y轴位置,我们需要拿到这个偏移量,我们将它存入到vuex中,然后由最外层即index.vue去接收这个偏移量,然后对整个index.vue界面进行下拉。
我们不是直接拿的最后move的clientY,因为你也不知道哪次的move是最后一个move呀,所以是每次move都去计算这个Y轴偏移量。第一次的move时就是我们要的刚开始的Y轴位置,如下第一次move就把刚开始的Y轴位置给了this.firstOffsetY;然后第二次往后的move,有this.firstOffsetY了,所以计算当前move的Y轴位置距离this.firstOffsetY的距离即offsetY。因为this.firstOffsetY是不变的了,而你每次move的Y轴位置都在变,到了最后一次move,即可算出了最终位置与初始位置的偏移量了
moveEnd时应该让偏移量还原,因为屏幕下拉是跟着偏移量一起下拉的,下拉结束后屏幕应该还原了,而是否屏幕是跟着偏移量变的,所以偏移量得还原,屏幕才能还原不再下拉,并且this.firstOffsetY也要还原
然后我们到index.vue中实现整个屏幕跟着偏移量下移
在index.vue中首先要把其变成绝对定位,因为下拉的时候是通过绝对定位改变top值来将整个页面下拉的,然后给它增加个ref,以便来获取它的DOM来改变它的top值
那index.vue中如何监听到offsetY的值呢?offsetY一变化它也跟着变化嘛,通过watch来监听,如下监听offsetY的值,传入v是新的offsetY,如果v>0则去调用move(),move()中通过去获取整个DOM,然后改变DOM的样式中的top值即可实现整个屏幕下拉的值等于offsetY的值
然后我们发现我们这个下拉后松开手了它不会还原,松开手后即end了此时offsetY已经被置为0了,但是我们只监听了v>0没有监听到=0的情况,所以我们增加如下
回去的时候我们加个过渡动画
接下来我们实现书签组件以及手势动画
这个灰色的背景颜色在App.vue中设置全局的背景色就能实现
创建EbookBookmark.vue,这个书签组件应该引入到index.vue中并且以绝对定位的形式放在屏幕画面的上方
书签组件中分为左右两侧来做,左侧是下拉图标和文字,右侧是书签图标,为什么不合成一个整体来做,因为右侧那个下拉图标它在书签组件回到顶部上面的时候这个图标是要固定在右上角这个位置,如果三个作为一个整体布局,左侧文字箭头位置不好计算。
左侧箭头是通过旋转180度来实现的,所以我们给了它一个过渡动画
我们创建书签组件叫Bookmark.vue
我们来做书签图标那个三角形,通过css来做,我们让宽高都为0,然后通过调节边距大小颜色来实现,如下
我们border-width其中第一个即上的px2rem(10)变成px2rem(35)即可拉高如下
我们把书签图标单独做成一个组件叫Bookmark.vue,然后引入到书签组件EbookBookmark.vue中
我们让Bookmark组件接收这三个参数,然后对样式进行改变
我们先来搞color,如下
下面来实现宽高,下面第二个第四个忘记加rem单位了,记得加上去
接下来我们设计书签的算法
监听offsetY,因为我们下拉书签的移动完全依赖与offsetY来判断,所以需要监听offsetY。
要实现这个算法需要更深入的分析整个书签添加的过程,整个书签添加的过程分为3个阶段,
第一个阶段,自然下拉阶段,下拉过程中,上面书签组件跟随屏幕移动而移动
第二个阶段,是吸顶阶段,这个书签组件我们高是35px,所以我们定它下拉刚好到35px的时候,书签就被吸顶,此时还没有到临界值
第三个阶段,可添加状态,此时下拉的长度已经超过临界值,此时箭头翻转为向上,文字内容改变,书签会变色,此时书签已添加,此时松手书签会被固定在右上角
第一个阶段已经实现,我们下拉的时候之前就已经使整个index跟着offsetY下拉了。
接下来我们实现第二个阶段,如下定义两个计算属性定义那两个临界值,然后去watch监听offsetY,划分出两个阶段
怎么实现让书签吸在顶部
其实是整个书签组件被吸在顶部,我们向下拖动的时候,是改变的最外层的top即index的top(index.vue是包含这个阅读器和书签组件的),但是书签组件本身没有改变top值,书签组件是跟着index移动而移动的,那么我们可以单独给书签组件一个反方向的相对位移,到了第二阶段的临界值后,整个index是向下移动的top值是正值越来越大的,此时我们让书签组件向上移动负值即反方向越来越大,这个值是等于offset的值的,只不过是取反。其实就是你继续下拉,它跟着反方向同速度向上,这样子看起来它就像不动一样即被吸在顶部一样。如下
接下来我们实现下拉文字和图标颜色的变化
接下来我们实现下拉图标箭头的旋转

我们在vuex中定义了一个变量isBookmark表示当前页是否为书签页。如果当前页为书签页,那我们进入这页时,这页会有一个书签挂在右上角。
监听offsetY的时候,我们要判断当前页是否为书签页,如果为书签页那下拉的话文字就应该展示下拉删除书签这些。所以如下
我们封装以下简洁一些
还有状态1自然下拉的过程,我们主要是做一些归位操作
还有一个是达到状态3时书签会添加到当前页面中,我们可以用fixed固定定位。
我们就给书签图标组件绑定一个样式,一个变量isFixed(表示是否可固定书签图标到右上角),这个变量如果是true则书签图标组件应该以固定定位固定在右上角,为false则保持原样
然后我们在状态1,2中isFixed都应该为false,状态3的时候isFixed应该变成true,状态4即归位的时候要判断isFixed即是否有书签图标挂在右上角,如果书签图标挂在右上角即为true的话应该去修改vuex中的isBookmark(判断是否为书签页)为true,如下书签手势动画就实现了
先介绍一下EpubCFI,
我们添加一个书签后,到书签列表中是可以看到这个书签当前页内容,就是我们需要把当前内容截取,截取下来之后进行显示,然后我们切换别的页后,点击书签中的某个依然能回到那一页并且判断是书签页即有书签图标在右上角
这里就有一个很重要的概念,就是去获取当前页的内容,我们要获取这个当前页的内容就需要通过这个Epubcfi,epubcfi可以对应到电子书中每一个文字
如上,最后的4/10/4[Par5]/3:307就表示在文本中那个位置
接下来我们做添加书签的操作,就是获取当前页文本内容以及此页最开始的位置cfi保存到一个变量bookmark中
currentLocation是当前页的信息,有start的cfi表示当前页第一个字母的位置,有end的cfi表示当前页的最后一个字母的位置
我们前面知道cfi感叹号后面的就是指位置的东西,
end部分也是同样的,然后做拼接,通过epubjs中的range方法来获取这个范围内的内容
去掉空格
定义一个变量叫bookmark,主要用来放书签页的信息(有哪些书签页,这些书签页的位置即cfi,这些书签页的文本内容,所以bookmark是[{xx},{xx}]这种形式),然后如果bookmark还没有就去初始化给一个空数组,如果本地缓存中有就从本地缓存中获取,然后把获取到的文本和cfi做成一个对象追加到bookmark中。这样bookmark中就存着这些书签页的信息,然后把bookmark存到本地存储中。这就是添加书签,就是获取书签文本内容以及此页最开始的位置即cfi保存到bookmark中。
然后我们书签列表的渲染就可以直接获取这个bookmark的text,点击书签列表中某一页可以通过往display中传cfi即可完成渲染。
接下来我们做删除书签的操作,就是获取当前页开始位置cfi,然后到bookmark中找到这个cfi把它过滤掉即可
每一页都应该判断是否是书签页
此时我们发现我们给某页添加书签后,然后翻页发现每一页都是书签页,这是因为我们缺失对当前页判断是否为书签页的判断,这个我们在刷新的时候加,如下,利用some()方法,如果当前页存在于bookmark中那么就把vuex中isbookmark置为true
然后还应该在EbookBookmark组件即书签组件中watch监听isBookmark的变化,因为上面你只是改变了isBookmark的值还没做什么操作
下面我们实现书签的列表功能
我们建一个书签展示组件叫EbookSlideBookmark,到EbookSlide中导入
到本地存储中获取bookmark赋值给这里的bookmark;
书签列表也是用的滚动组件Scroll,遍历bookmark,把bookmark里面的每页截取的文本展示出来,然后绑定个点击事件,点击就把当前页的cfi传入display()中渲染处页面