0. 引子
每年总有那么两周。
明明不用工作,但却不能休息,宅在家还得爆肝。
没错,国庆&春节。
春节假期的第一天,我就入手了最近一个很火的国产修仙游戏《鬼谷八荒》,无数网友为之肝疼,就下面这条评论,我已经在B站、微博、A社、QQ、微信多个渠道看到了。
为了体验一下修仙的感觉,我也入坑了这个游戏,两天玩了差不多9个小时,今天起床,一阵不以人的主观意志为转移
的猛烈肝痛,让我意识到作者是真的打算让我修仙。
总的来说,这个游戏体系很丰富,但还处于内测阶段,玩法剧情上还有很多缺陷,现阶段只是单纯的肝,还没有自动刷怪的功能,每次打材料的过程中,现实世界的你都有极小概率直接爆肝飞升。
这种现象最常见的是在日本,过马路被卡车撞能穿越,玩游戏肝爆能穿越,下班路上也能穿越。
代表人物就是《転生したらスライムだった件》中的三上悟,下班路上被人刺死,穿越到了异世界。
这也是最近我第二肝的事情,把第一部从头看了一遍。
24集看完,我的肝还没有感觉到异常,但是天有不测风云,一条半剧透的弹幕,出现在了我的视野里,我身体躺在床上,而我唯物主义
的大脑一直闪过被剧透的画面。
垂死病中惊坐起,绝知此事要躬行。
我搜出了史莱姆正在连载的漫画,快速补完了已经更新的剧情,但是内容和动漫相比,故事线还是很接近,等于没看,所以我准备去补小说。
百度搜索了几个史莱姆小说的资源,但是网站内容过于拉胯,我的肝已经很不舒服了,不能让我眼睛也不舒服,所以我决定自己搭一个史莱姆小说在线阅读的网站!!!
1. 进入正题
1.1 epub介绍
自己搭一个小说阅读网站,仔细理一下思路其实实现很简单。
一个介绍页
,展示小说相关信息。
一个阅读页
,用于阅读小说。
介绍页很简单,阅读页的话为了方便使用已有的阅读器引擎epub.js。
小说的格式通常有很多,txt、pdf、epub、mobi等等,为了有更优的阅读体验,我选择了epub的格式,因为epub其实本身就是一种电子书的格式协议,有这非常完善的电子书信息可以让我们程序去获取。
我在网上找到了史莱姆的epub资源,将epub后缀改为zip,就可以发现epub到底是怎么存储的了。
小说需要阅读的部分其实都在OPS文件中,以html的形式存在,当然也就支持小说插图。
1.2 epubjs入门
epubjs是一个专门用来解析epub文件的库。
https://github.com/futurepress/epub.js/blob/master/documentation/README.md
利用其api就可以快速完成解析。
- 安装
npm install epubjs
- 创建一个book对象
this.book = new Epub('01.epub')
- 通过Book.renderTo生成Rendition对象
this.rendition = this.book.renderTo('read', {
width: window.innerWidth,
height: window.innerHeight,
method: 'default'
})
- 通过Rendtion.display渲染电子书
this.rendition.display()
- 翻页
function prevPage() {
if (this.rendition) {
this.rendition.prev()
}
}
function nextPage() {
if (this.rendition) {
this.rendition.next()
}
}
核心api就这几个,只需要根据路由来控制需要读取的epub文件即可。
1.3 手动引入
引入如下文件即可,可以下载到本地。
<script src="https://github.com/futurepress/epub.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jszip/3.5.0/jszip.js"></script>
1.4 菜单栏
我们需要在阅读过程中对界面进行一些配置,比如字体、背景、亮度等,这也是epubjs所开放api支持的,我们需要一个菜单栏来操作它。
代码如下,因为过长,样式部分省略。
<template>
<div class="menu-bar">
<transition name="slide-up">
<div class="menu-wrapper" :class="{'hide-box-shadow': ifSettingShow || !ifTitleAndMenuShow}" v-show="ifTitleAndMenuShow">
<div class="icon-wrapper">
<span class="icon-menu icon" @click="showSetting(3)"></span>
</div>
<div class="icon-wrapper">
<span class="icon-progress icon" @click="showSetting(2)"></span>
</div>
<div class="icon-wrapper">
<span class="icon-bright icon" @click="showSetting(1)"></span>
</div>
<div class="icon-wrapper">
<span class="icon-a icon" @click="showSetting(0)">A</span>
</div>
</div>
</transition>
<transition name="slide-up">
<div class="setting-wrapper" v-show="ifSettingShow">
<div class="setting-font-size" v-if="showTag === 0">
<div class="preview" :style="{fontSize: fontSizeList[0].fontSize + 'px'}">A</div>
<div class="select">
<div class="select-wrapper" v-for="(item, index) in fontSizeList" :key="index" @click="setFontSize(item.fontSize)">
<div class="line"></div>
<div class="point-wrapper">
<div class="point" v-show="defaultFontSize === item.fontSize">
<div class="small-point"></div>
</div>
</div>
<div class="line"></div>
</div>
</div>
<div class="preview" :style="{fontSize: fontSizeList[fontSizeList.length - 1].fontSize + 'px'}">A</div>
</div>
<div class="setting-theme" v-else-if="showTag === 1">
<div class="setting-theme-item" v-for="(item, index) in themeList" :key="index" @click="setTheme(index)">
<div class="preview" :style="{background: item.style.body.background}" :class="{'no-border': item.style.body.background !== '#fff'}"></div>
<div class="text" :class="{'selected': index === defaultTheme}">{{item.name}}</div>
</div>
</div>
<div class="setting-progress" v-else-if="showTag === 2">
<div class="progress-wrapper">
<input class="progress" type="range"
max="100"
min="0"
step="1"
@change="onProgressChange($event.target.value)" @input="onProgressInput($event.target.value)"
:value="progress"
:disabled="!bookAvailable"
ref="progress">
</div>
<div class="text-wrapper">
<span>{{bookAvailable ? progress + '%' : '加载中...'}}</span>
</div>
</div>
</div>
</transition>
<content-view :ifShowContent="ifShowContent"
v-show="ifShowContent"
:navigation="navigation"
:bookAvailable="bookAvailable"
@jumpTo="jumpTo"></content-view>
<transition name="fade">
<div class="content-mask"
v-show="ifShowContent"
@click="hideContent"></div>
</transition>
</div>
</template>
<script>
import ContentView from '@/components/Content'
export default {
components: {
ContentView
},
props: {
ifTitleAndMenuShow: {
type: Boolean,
default: false
},
fontSizeList: Array,
defaultFontSize: Number,
themeList: Array,
defaultTheme: Number,
bookAvailable: {
type: Boolean,
default: false
},
navigation: Object
},
data() {
return {
ifSettingShow: false,
showTag: 0,
progress: 0,
ifShowContent: false
}
},
methods: {
hideContent() {
this.ifShowContent = false
},
jumpTo(target) {
this.$emit('jumpTo', target)
},
onProgressInput(progress) {
this.progress = progress
this.$refs.progress.style.backgroundSize = `${this.progress}% 100%`
},
onProgressChange(progress) {
this.$emit('onProgressChange', progress)
},
setTheme(index) {
this.$emit('setTheme', index)
},
setFontSize(fontSize) {
this.$emit('setFontSize', fontSize)
},
showSetting(tag) {
this.showTag = tag
if (this.showTag === 3) {
this.ifSettingShow = false
this.ifShowContent = true
} else {
this.ifSettingShow = true
}
},
hideSetting() {
this.ifSettingShow = false
}
}
}
</script>
3. 效果
章节信息直接硬编码在了介绍页,点击跳转按照epub的编号做了,打包好代码,直接在局域网的一个树莓派上部署,起飞~
谨以此文。
纪念我坚强无比,最后却因为被剧透而快爆掉的肝。
Q.E.D.