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)
- 發現i18n.t()函式使用的時候報錯
-
設定主題要先在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標籤