zoukankan      html  css  js  c++  java
  • 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标签
  • 相关阅读:
    一道看似简单的sql需求却难倒各路高手
    MahApps.Metro怎么调用消息窗口
    CodeSmith Generator 7.0.2激活步骤
    8款图表插件推荐
    VS的代码分析工具
    RDLC系列之六 打印纸张的大小(未解决)
    初识python
    应用程序的更新
    Expression<Func<T,TResult>>和Func<T,TResult>
    HTML5 history新特性pushState、replaceState
  • 原文地址:https://www.cnblogs.com/TRY0929/p/13602595.html
Copyright © 2011-2022 走看看