Vue-eBookReader 学习笔记(目录)

3、目录

基本框架:目录板块的一个大的蒙板,左边是目录右边是背景部分(灰色),左边又有两个tab栏,一个是目录条目一个是书签,同时翻开还带有进入动画。

分为四个组件,整个板块一个大组件,里面有三个小组件,分别为:目录条目、书签、进入动画

3.1 目录浮出效果

  • 要做出的效果其实是,当目录向右滑动同时背景逐渐显现出来,但是一开始直接在最外层加上fade-slide-right之后发现,目录推出的过程加上背景渐显有点不太和谐,所以将动画分为两个部分,叠加起来
  • 在最外层加上整个过程opacity变大的过程,在里面内容加上向右推出的过程,这样的动画就比较和谐

3.2 tab切换和搜索效果

  • tab栏共有两个东西,书签和目录,点击可以切换,用动态绑定class来控制当前选中tab的样式
  • tab栏切换之后,当前所展示的组件也要相应的进行切换,用到了动态组件的知识,用component标签,is属性来绑定当前展示的是哪个标签
<component :is="currentTab === 1 ? content : bookmark"></component>
  • 上面的搜索框部分,点击input框会有取消按钮出现,能进行输入搜索,点击取消就消失,这里设置一个searchVisible的变量用来看当前的取消按钮是否是显示的(搜索框部分主要是css比较麻烦,用flex布局的话会很舒服)

3.3 图书内容信息布局

  • 目录条目上面是图书信息,分为三个部分 左中右,分别为 图书图片、图书信息、图书进度,水平布局(依然是先html再css),同时这里也用到了国际化(后面就不再重复了,反正有文字的地方都会用到);此处也要获得读书时间,所以直接将获取读书时间文本的函数放到mixin里,换成分钟的时间放到book.js里(静态方法?)

  • 布局仍然使用flex布局,注意这里用到了 设置font-size为0解决div自带内边距问题,详见:

font-size为0解决内边距问题

  • 这里在显示信息的时候,书名或者作者名字可能会很长,要用省略号来显示,但是直接引入ellipsis方法发现它将盒子撑开了,在使用ellipsis的时候必须设置盒子有自己的固定高度(计算的方法一定要掌握:375x0.85-15x2-10x2-45-70=153.75),详见解决方案:

让文字在两行显示 其余为省略号

3.4 目录条目开发

3.4.1 目录界面

  • 由于目录本身是有多级的,是树状结构,但是显示的时候是线性 平面的,这里要将树状结构拆分成一维数组
  • 其中用到了 es6的扩展运算符、concat拼接数组元素、递归
export function flatten (array) {
  return [].concat(...array.map(item => [].concat(item, ...flatten(item.subitems))))
}
  • 但是全部这样放完之后,所有都变成一维数组中的内容,所以还要计算出每个元素处于第几层(level),还是通过递归来计算,递归寻找father,下面这段代码执行完后,每个元素的level就都进行保存完毕了
const navItem = flatten(nav.toc)
	function find (item, level) {
		return !item.parent ? level : find(navItem.filter(parentItem => 					parentItem.id === item.parent)[0], ++level)
	}
	navItem.forEach(item => {
		item.level = find(item, 0)
})

3.4.2 滚动部分

自己封装一个scroll组件,里面有一些滚动条基本的部分,下面说一下注意的几个点,这里由于滚动条很可能会在多个组件里用到,所以新开一个目录专门存放common组件(components/common/Scroll.vue)

  • 这里封装的简单scroll组件,功能大概为:传入 top(距上方高度),bottom(距下方高度),ifNoScroll(是否能滚动,特殊化组件),initPosition(起始位置),这样组件就能自己计算出高度,中间是个slot插槽,用于添加用户自己的内容
  • 每次其他高度有任何改变后,调用refresh函数,作用是计算出新的滚动条高度
    • 这里有个realPx函数,作用是根据屏幕的高度不同,自动对当前传入高度进行缩放(默认是500px,大于则放大,小于则缩小),统一的标准来设置高度
refresh () {
	if (this.$refs.scrollWrapper) {
		this.$refs.scrollWrapper.style.height = window.innerHeight - 		realPx(this.top) - realPx(this.bottom) + 'px'
		this.$refs.scrollWrapper.addEventListener('scroll', this.handleScroll)
	}
}
  • scroll、touchmove、touchstart等事件被触发的时候,我们通常会去preventDefault()掉它们的默认行为,但直接这样会导致页面卡顿 详见 vue事件修饰符,所以使用@scroll.passive='函数'来绑定事件

  • 同时要在钩子函数mounted里,调用refresh以及对初始位置进行设置,里面用到了(nextTick函数用来在dom有变化后异步调用回调,保证数据变化引起的dom变化全部相应完毕 详见[vue中)nextTick函数(异步dom更新)](https://www.cnblogs.com/TRY0929/p/13617122.html)

  • 滚动条样式里,要注意的是 加上了两个属性值得关注:

    • -webkit-overflow-scrolling: touch;:属性控制元素在移动设备上是否使用滚动回弹效果.
      auto: 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止。
      touch: 使用具有回弹效果的滚动, 当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。(属性本身好像容易出bug)

    • // 不显示浏览器默认的滚动条
      &::-webkit-scrollbar {
        display: none;
      }
      

添加滚动条

  • v-for循环navigation里的项就可以了,注意一下,这里一行内文字也显示不完,但是直接加上ellipsis又会超出宽度,有个小技巧,在外层盒子加上flex布局,内层哪一行写上flex: 1,即为自动填充

  • 对多级目录进行缩进,我们刚刚以及计算出每个条目的level,现在定义一个函数 每多一层level缩进多15px,再动态绑定样式到v-for的item上 :style="contentItemStyle(item)"

contentItemStyle (item) {
	return {
		'margin-left': `${px2rem(item.level * 15)}rem`
	}
}
  • 当前显示章节条目高亮,这里直接动态绑定class selected即可,条件是当前的index和当前vuex中的section是否一致(这两者刚好含义是一样的)

  • 有时候可能会发现,当字体设置为14px后,有些英文字母无法完全显示(如y、g),这时因为某些字体的原因,英文开发时常见盒子问题,因此这里设置行高略高于字体大小

  • 点击条目进行跳转,这里直接利用book.js中的display(href)方法即可,专门用于跳转到某个特定的href界面的,但这里在跳转的同时要将标题栏菜单栏隐藏,不直接修改display函数,而是设置回调函数,在回调里面调用hideTitleAndMenu

消除一些已有bug

  • 当前以及解析除了一个navigation数组来存放章节信息等,所以在progress组件里,获取章节名称(getSectionName)函数可以简化为下面这样,原来的不能显示二级目录:
getSectionName () {
	return this.section ? this.navigation[this.section].label : ''
}
  • 当快速切换上下章节时,控制台会有错误产生,是因为电子书解析需要时间,无法在点击切换的同时马上就找到当前位置(currentLocation),所以在切换的时候要先判断一下是否以及解析完毕再进行操作

3.5 全文搜索

3.5.1 全文搜索原理

  • epubjs官方提供了doSearch方法来实现全文搜索,遍历每个章节的文字 找到每个章节里存在的含关键字的部分合成一个数组,所以最后返回的实际上是一个二维数组,数组的每一项都是一个数组
  • 所以这里要进行二维数组降维操作,之前也讲过一个将数组打瘪的flatten方法,但是二维数组的话比较简单,直接用[].concat.apply([], results))就将results这个二维数组变成一维了。这里其实是 利用了apply函数的第二个参数是 以数组的方式传入形参,所以就是将results里每个元素当作形参传进来了,利用concat合并即可。
doSearch (q) {
	return Promise.all(
		this.currentBook.spine.spineItems.map(item => item.load(this.currentBook.load.bind(this.currentBook))
		.then(item.find.bind(item, q)).finally(item.unload.bind(item)))
	).then(results => Promise.resolve([].concat.apply([], results)))
}

3.5.2 全文搜索布局

  • 搜索结果的显示也是用滚动条来实现,所以就在下面加一个滚动条组件再个性化定制即可(注意用bookAvailable来控制当前显示的是那个部分)
  • 用v-model双向绑定input的输入,再定义keyup.enter.exact(这个exact表示只能单独按下enter键才会触发 严格事件)事件,处理函数为search,里面调用了doSearch函数来实现搜索
  • 条目中搜索结果高亮,在doSearch调用完得到searchList之后,遍历数组的每个元素,将原本搜索文字替换为html的span标签,并在上面绑定class来控制样式,但是这样以来 html中写的直接是{{item.excerpt}},这样进行解析之后会直接将标签显示出来而不是进行解析,所以要用v-html=item.excerpt来进行显示(注意这里的字符串的replace函数是返回一个新的字符串,所以要赋值才能生效)
search () {
	if (this.searchText && this.searchText.length > 0) {
		this.doSearch(this.searchText).then(results => {
			this.searchList = results
		}).then(() => {
			if (this.searchList.length > 0) {
				this.searchList.map(item => {
          // 注意这里要赋值
					item.excerpt = item.excerpt.replace(this.searchText, `<span class="content-search-text">${this.searchText}</span>`)
					return item
				})
			}
		})
	}
}
  • 文本中搜索结果高亮,这里直接对displayContent方法改造一下,加入一个是否显示高亮的条件,这里控制高亮使用book.rendition.annotations.highlight(target)就可以
displayContent (target, highlight = false) {
	this.display(target, () => {
		this.hideTitleAndMenu()
	})
	if (highlight) {
		this.currentBook.rendition.annotations.highlight(target)
	}
}

3.6 动画效果

(这里的动画效果不是通过放一张动图,而是自己控制css来实现 真的吓死了)

  • 设置一个单独的EbookLoading组件,当bookAvailable还没变为true的时候显示该组件,隐藏目录组件
  • 具体实现大概如下:
    • 一个大方框,里面有六条线,左边三条右边三条(竖直排列),flex布局
    • 每条线的位置设置一个line和mask,通过line和mask的长度变化来形成动态效果
    • 有两个数组分别存放line和mask的长度,每次长度加一(减一),上一条线到一半的时候下一条开始变化
原文地址:https://www.cnblogs.com/TRY0929/p/13620695.html