1. 程式人生 > 實用技巧 >Vue-eBookReader 學習筆記(閱讀器解析和渲染部分)

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', {
        width: 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標籤