Vue-eBookReader 学习笔记(阅读器解析和渲染部分)

1、阅读器解析和渲染

1.1 动态路由的设置

  观察成品可知,在地址栏会出现书名、分类的信息,而且可以在地址栏更改信息得到不同的书,这就利用了动态路由的技术。

1.1.1 现在先实现,在地址栏输入的值直接显示到页面的效果。

  • 在路由index.js文件中的ebook路由下,添加children属性来存放动态路由,children是一个数组,每个元素是对象,里面包含了一个动态路由:
{
    path: '/ebook',
    component: () => import('../views/ebook/index.vue'),
    children: [
      {
        //这里的冒号代表后面是参数,在EbookReader组件内部可以通过 $route.params.fileName来访问到
        path: ':fileName',
        component: () => import('../components/ebook/EbookReader')
      }
    ]
  }

1.1.2 构造书本地址

  • 简单功能实现完毕之后,来写:在地址栏输入 地址+分类+|+书名可以访问到电子书,其实也就是在刚刚基础上将EbookReader组件里的打印改为字符串拼接;

  • 字符串输入样例:http://localhost:8080/#/ebook/History|2013_Book_FungalDiseaseInBritainAndTheUn

mounted () {
    const fileName = this.$route.params.fileName.split('|').join('/')
    const baseUrl = '192.168.1.112:8082/epub/'
    console.log(`${baseUrl}${fileName}.epub`)
  }
  • 这样就从nginx上获取到了想要的资源,有了书本的地址就可以开始构造Book对象
  • 将书本地址放到vuex中存储,这时由于后来会有很多地方都要用到这个值,同样使用getter和mapGetters来实现简便取值;

1.2 书本构造开始

1.2.1 将书本渲染到dom上

  • this.book = new Epub(url) 创建新book对象

  • 获取rendition对象,主要用于电子书的渲染

    • this.rendition = this.book.renderTo('read', {
         window.innerWidth,
        height: window.innerHeight
        //这里课程里老师说要加,但是加上之后发现渲染出来的元素宽度为0,去掉就好了也不知道为什么
        // method: 'default'
      })
      
  • this.rendition.display() 展示出电子书来

1.2.2 实现左滑右滑翻页效果

  • 这里左滑右滑均不支持长按,且距离不能太短以与点击事件区别出来
  • this.rendition.on('事件名', '事件处理函数') 用来给book对象绑定事件
this.rendition.on('touchstart', event => {
	this.touchStartX = event.changedTouches[0].clientX
	this.touchStartTime = event.timeStamp
})
this.rendition.on('touchend', event => {
	const offsetX = event.changedTouches[0].clientX - this.touchStartX
	const time = event.timeStamp - this.touchStartTime
	if (time < 500 && offsetX < -40) {
		this.nextPage()
	} else if (time < 500 && offsetX > 40) {
		this.prePage()
	} else {
		this.toggleTitleAndMenu()
	}
	event.passive = false
	event.stopPropagation() //阻止事件传播
})
  • 上面这段代码是获取到滑动的起始和结束的位置与时间,通过简单的运算来判断滑动的方向以及时间
  • 这里用的是event.passive = false,而不是老师的event.preventDefault()是因为,滑动的时候会报错,设置passive为false也可以阻止默认行为

1.2.3 标题栏和菜单栏部分

  • 布局和样式都是用之前的代码,稍微改动即可,改完之后大概样子以及过渡动画也完成了
  • 这里需要滑动页面的时候标题栏菜单栏都要隐藏,要增加一个menuVisible的state来决定是否显示这两样东西,依然是用mapGatters来简化

1.2.4 用mixin技术来简化代码

1.2.4.1 代码复用

  • 发现在每个组件里都用到了以下代码,代码重复度太大:
import { mapGetters, mapActions } from 'vuex'
export const ebookMixin = {
  computed: {
    ...mapGetters([
      'menuVisible',
      'fileName'
    ])
  }
}
  • 所以将这段代码封装到utils/mixin.js文件中,再在要使用的组件里的script标签里用import { ebookMixin } from '../../utils/mixin',原理和使用mapGetters一样差不多
  • 再加上一个mixin的属性:mixins: [ebookMixin]

1.2.4.2 vuex中methods的调用方法简化

  • 之前利用了mapGetters和getter计数来让组件里引用state的数据简便了不少,其实vuex中同样可以使用类似的及时让外面的组件调用方法比较简单,本来是this.$store.dispatch('方法名','参数'),现在可以变为 this.方法名('参数')
  • 和getter差不多,在vuex的index.js文件中加上actions属性,同时在modules/actions文件里将方法都封装进去并且export出来,也就是将方法都放在一起方便管理
    • 注意这里和上面的getters有点不一样,是直接把book里的actions全拿出来放到actions.js文件中,接着把book里原来的actions删掉
  • 引用同样和mapGatters差不多,在组件里 import { mapActions } from 'vuex',但这是方法,所以...mapActions([''])要写在methods里哦(同样这一段也可以写在mixin里方便管理)

1.2.5 字号设置

1.2.5.1 布局部分

  • 字体的菜单栏部分会多出上面那一截,主要的实现思路是:

    • 字体进度条左边是 最小的字体,右边是最大的字体

    • 将菜单栏横着主要分成七个部分(这个数量是由总共有多少可以字号设置来决定的),每个部分分为左右两个部分 显示一个边框(视觉上就是一条直线)

    • 几个部分由中间的小点隔开,其实每个小点的地方都是一个大点,但只有当选中某一个部分(字体)时,那个地方的大点才会显示出来表示选中

  • 将fontSizeList这种静态数据都放在统一的文件下管理,这里放在utils文件夹下的book.js中

    • import { FONT_SIZE_LIST } from '../../utils/book'引入进来
    • 再在数据里定义好自己的数据 fontSizeList值为FONT_SIZE_LIST
export const FONT_SIZE_LIST = [
  { fontSize: 12 },
  { fontSize: 14 },
  { fontSize: 16 },
  { fontSize: 18 },
  { fontSize: 20 },
  { fontSize: 22 },
  { fontSize: 24 }
]
  • 每个小圆点有个字体,全局有defaultFontSize为当前的字体,每次触发点击事件后通过setFontSize来修改默认字体
  • 注意点:
    • 当字体设置面板出来之后,设置面板的阴影要隐藏
    • 设置面板隐藏,下一次再出来的时候字体设置面板应该是不出现的

1.2.6 字体部分

1.2.6.1 布局

  • 字体和字号在同一个面板上,上面是字号条 占比2/3,下面是字号 1/3,其中字号设置点开有弹窗效果
  • 占比就用flex布局,垂直分布,一个flex为2一个为1

1.2.6.2 字号设置弹窗

  • 弹窗分为上下两个部分,上面是标题(后面支持中英切换),下面是字体列表

  • 先对大致布局进行设置,再写css,就不再多说,但是都用弹性布局(非常好!!!)

  • 下面的字体列表用v-for循环显示完之后,选中的那个要单独变颜色,用:class="{'selected': isSelected(item)}"来控制加不加上那个selected的类

1.2.6.3 字体设置(比字号设置复杂)

  • 有一步的设置方法和字号设置差不多,this.currentBook.rendition.themes.font(font),但这一步之后字体并没有发生变化
  • epubjs的渲染是通过iframe来实现的,iframe中才是真实的阅读器的dom,要设置字体必须在这个dom里面设置,所以直接写是没用的(里面是一个独立的dom)
  • 利用epubjs的钩子函数:
// content代表iframe里的dom已经加载完毕可以访问
// contents是管理资源文件
// addStylesheet是手动添加style样式的函数
this.rendition.hooks.content.register(contents => {
          Promise.all(
            [contents.addStylesheet('http://10.69.198.212:8082/fonts/daysOne.css'),
            contents.addStylesheet('http://10.69.198.212:8082/fonts/cabin.css'),
            contents.addStylesheet('http://10.69.198.212:8082/fonts/montserrat.css'),
            contents.addStylesheet('http://10.69.198.212:8082/fonts/tangerine.css').then(() => {})
            ])
        })
  • 观察epubjs中contents的addStylesheet函数源码,发现它将接收的参数拼成一个url,利用link标签引入css,所以要接收url的css,因此就将字体文件放入nginx服务器上,传入的就是nginx服务器下的那个url
  • 上面的register函数返回是一个promise对象,所以可以利用Promise.all这个函数来对那些promise对象进行处理,就可以在全部注册完之后干点什么了
环境变量
  • http://10.69.198.212:8082这个网址很可能在生产环境和开发环境是不一样的,所以在这里不能直接写死网址,而是要添加环境变量

    环境变量

  • 在项目根目录下添加文件 .env.development,在里面写上要替换的网址 只有以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中

// .env.development
VUE_APP_RES_URL=http://10.69.198.212:8082
// 替换之后
contents.addStylesheet(`${process.env.VUE_APP_RES_URL}/fonts/cabin.css`),
  • 环境变量是在服务器启动的时候加入,所以这里要重启服务器

1.2.7 字体和字号设置离线缓存

  • 一般会有要求 在客户下一次进入页面的时候,设置仍然保持上一次退出的样子,所以这里用到了localStorage来实现

cookie localStorage sessionStorage三者异同

  • 先安装 cnpm i --save web-storage-cache

  • 在utils文件夹下创建localStorage.js文件用来封装localStorage的操作

// 基本操作
import Storage from 'web-storage-cache'

const localStorage = new Storage()

export function setLocalStorage (key, value) {
  return localStorage.set(key, value)
}

export function getLocalStorage (key) {
  return localStorage.get(key)
}

export function removeLocalStorage (key) {
  return localStorage.delete(key)
}

export function clearLocalStorage () {
  localStorage.clear()
}
  • 下面的具体操作代码,每本书都在localStorage里有自己的单独设置
  • localStorage里键是${fileName}-info,值是一个book对象,以后每次要加新的设置就直接在book对象中加就可以了
// 具体操作代码
export function setBookObject (fileName, key, value) {
  let book = getLocalStorage(`${fileName}-info`)
  if (!book) {
    book = {}
  }
  book[key] = value
  setLocalStorage(`${fileName}-info`, book)
}

export function getBookObject (fileName, key) {
  const book = getLocalStorage(`${fileName}-info`)
  if (book) {
    return book[key]
  } else {
    return null
  }
}

export function getFontFamily (fileName) {
  return getBookObject(fileName, 'fontFamily')
}

export function saveFontFamily (fileName, font) {
  setBookObject(fileName, 'fontFamily', font)
}

  • 加完这些之后要设置,每次换完字体就要saveFontFamily,以及一开始渲染的时候也要设置,开始的时候利用rendition.display返回的是一个promise对象来进行操作
this.rendition.display().then(() => {
	const font = getFontFamily(this.fileName)
	if (font) {
		this.currentBook.rendition.themes.font(font)
		this.setDefaultFontFamily(font)
	} else {
		saveFontFamily(this.fileName, 'Default')
	}
})
  • 其余的设置也是差不多的操作

1.2.8 语言国际化

  • 首先准备好了两个不同语言的文件, 里面准备好了各个地方需要的文字
  • 利用 VueI18N插件
    • 安装 cnpm i --save vue-i18n
    • 在src目录下新建lang文件夹来管理语言
    • lang文件夹下的index.js文件里使用插件, 同时注意要在main.js中引入这个插件文件并注册, 不然无法使用
    • 同样利用localStorage来存储当前的语言, 以备下一次打开时需要

1.2.9 主题设置

  • 这里主题依旧是静态资源文件, 所以在utils下的book.js中仍然要加上theme相关信息

  • 主题的名字也用到了国际化语言, 所以book.js中也要使用插件vuei18n, 这里就有问题了

    • 发现i18n.t()函数使用的时候报错 i18n is undefied, 明明i18n已经在main.js的根组件中注册好了, 为什么还是使用不了?
    • 这是由于book.js中代码执行的时候, i18n插件还没被注册完毕. 也没什么好的解决方法, 就在开头引入i18n插件吧 import i18n from '../lang'
    • 可以看看这篇文章 如何让一个vue项目支持多语言(vue-i18n)
  • 设置主题要先在eBook对象中注册主题 分别需要名字以及对应的样式, 这里由于在EBook Reader组件以及EbookThemeSetting组件中都用到了themeList, 所以也将theme List的那个混入过程加入到mixin中, 这样只要引用了mixin就都能取到themeList

  • 其余就和字体 字号一样, initTheme里判断默认主题什么什么的, 还有localStorage的存储~

  • 小bug, 发现每个主题只能切换一次就不能再换回来了, 所以这里把epubjs的版本换成0.3.71 就行了

1.2.10 全局主题设置

  • 主要的思想是动态添加css文件, 我们知道, css文件的添加是通过link标签实现的, 所以这里也就是动态添加link标签来实现 ( 这个函数写在book.js文件中并且export出去, 要使用的地方就引用 )
export function addClass (url) {
  const link = document.createElement('link')
  link.setAttribute('rel', 'stylesheet')
  link.setAttribute('href', url)
  link.setAttribute('type', 'text/css')
  document.querySelector('head').appendChild(link)
}
  • 这里的link标签需要url,我们将主题文件放到nginx服务器下, (注意这里的地址还是要使用环境变量, ~包括之前的book的地址也要). 每次切换主题的时候就调用addClass这个函数

  • 每次选择切换一次主题, 不仅电子书的主题要切换 全局主题也要切换, 写另外一个函数来根据不同的电子书主题同步切换到全局主题

// 这个函数在EbookSettingTheme和EbookReader中都要用到,所以写在mixin中供全局调用
initGlobalStyle () {
      switch (this.defaultTheme) {
        case 'Default': addClass(`${process.env.VUE_APP_RES_URL}/theme/theme_default.css`)
          break
        case 'Gold': addClass(`${process.env.VUE_APP_RES_URL}/theme/theme_gold.css`)
          break
        case 'Eye': addClass(`${process.env.VUE_APP_RES_URL}/theme/theme_eye.css`)
          break
        case 'Night': addClass(`${process.env.VUE_APP_RES_URL}/theme/theme_night.css`)
          break
        default: addClass(`${process.env.VUE_APP_RES_URL}/theme/theme_default.css`)
      }
    }
  • 但是这也下去会有一个问题, 每切换一次主题就多添加一个link标签, 加重了渲染的负担, 所以同时还要想办法移除之前添加的link标签
原文地址:https://www.cnblogs.com/TRY0929/p/13602595.html