使用epubjs开发一个电子书阅读页面

本文详细描述了使用UniApp框架开发的电子书阅读器,涵盖了阅读epub、翻页、目录浏览、字体设置等功能,并分享了在开发过程中遇到的问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下功能包括,阅读epub电子书,上一张,下一章,左翻页,右翻页,目录,设置字体,背景色,书签等功能,使用uniapp框架开发,遇到许多问题,现在是一个完整的功能,以后用到可以使用。
html

<template>
	<view>
		<scroll-view scroll-y class="page DrawerPage" :class="modalName=='viewModal'?'show':''">
			<cu-custom bgColor="bg-gradual-blue" :isBack="true" v-show="ifShowMenu">
				<block slot="backText">返回</block>
				<block slot="content">{{bookInfo.title}}</block>
			</cu-custom>
			<scroll-view scroll-y class="bg-white" style="padding-bottom: 80px;" :style="[{height:'calc(100vh - '+ this.CustomBar +'px + 50px)','background':themeList[defaultTheme].style.body.background}]">
				<view id="book"></view>
			</scroll-view>
			<view style="padding: 0 20px 0;" class="cu-bar tabbar bg-white shadow foot" v-show="ifShowMenu">
				<view class="action" @tap="prevPage">
					<view>上一章</view>
				</view>
				<view class="action">
					<text class="lg text-gray cuIcon-settings float-book-setting" style="font-size: 40upx;" @tap="setSettingRightPx" :class="{'float-book-setting-active':settingRightPx}"></text>
				</view>
				<view class="action" @tap="nextPage">
					<view>下一章</view>
				</view>
			</view>
			<uniTransition :show="settingRightPx" :modeClass="['slide-bottom']" :duration="500" @change="changeTools" class="setting-tools">
				<view class="setting-content-view">
					<text class="setting-font-size">大小:</text><slider class="setting-font-size-val" @change="intervalFontSizeChange" :value="interval" show-value min="10" max="22" />
				</view>
				<view class="setting-content-view">
					<text class="setting-font-bg">背景:</text>
					<view class="setting-font-bg-val">
						<view class="text-center setting-font-bg-txt" :style="{'background':item.style.body.background}" @tap="setTheme(index)" v-for="(item,index) in themeList" :key="index"></view>
					</view>
				</view>
				<button class="cu-btn block line-orange lg"  @tap="showBookMarkList"><text class="cuIcon-list"></text> 书签</button>
				<button class="cu-btn block line-orange lg"  @tap="tabSelect" data-target="viewModal"><text class="cuIcon-list"></text> 目录</button>
				<button class="cu-btn block bg-grey lg" @tap="setSettingRightPx"><text class="cuIcon-close"></text> 关闭</button>
			</uniTransition>
		</scroll-view>
		<!-- 二级菜单 -->
		<view class="DrawerClose" :class="modalName=='viewModal'?'show':''" @tap="hideModal">
			<text class="cuIcon-pullright"></text>
		</view>
		<scroll-view scroll-y class="DrawerWindow" :class="modalName=='viewModal'?'show':''">
			<view class="cu-list menu card-menu margin-top-xl margin-bottom-xl shadow-lg">
				<view class="cu-item arrow" v-for="(item,index) in navigation" :key="index">
					<view class="content" @tap="selectList(item)">
						<text class="cuIcon-github text-grey"></text>
						<text class="text-grey">{{item.label}}</text>
					</view>
				</view>
			</view>
		</scroll-view>
		<!-- 书签弹框 -->
		<view class="cu-modal" :class="bookMarkShow?'show':''">
			<view class="cu-dialog">
				<view class="cu-bar bg-white justify-end">
					<view class="content">书签</view>
					<view class="action" @tap="hideBookMark">
						<text class="cuIcon-close text-red"></text>
					</view>
				</view>
				<view class="padding-xl">
					书签名:<input placeholder="请输入书签名" v-model="bookMarkName"></input>
				</view>
				<view class="cu-bar bg-white justify-end">
					<view class="action">
						<button class="cu-btn line-green text-green" @tap="hideBookMark()">取消</button>
						<button class="cu-btn bg-green margin-left" @tap="saveBookMark()">确定</button>
		
					</view>
				</view>
			</view>
		</view>
		<!-- 书签列表 -->
		<view class="cu-modal drawer-modal justify-end" :class="bookMarkListShow?'show':''" @tap="hideBookMarkList()">
			<view class="cu-dialog basis-lg" @tap.stop="" :style="[{top:CustomBar+'px',height:'calc(100vh - ' + CustomBar + 'px)'}]">
				<view class="cu-list menu text-left">
					<view class="cu-item arrow" v-for="(item,index) in bookMarkList" :key="index">
						<view class="content">
							<view @tap="toMark(item)">{{item.bookmarkname}}</view>
						</view>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

js


<script>
	import Epub from 'epubjs'
	import uniTransition from "../../colorui/components/uni-transition.vue"
	export default {
		components: {uniTransition},
		data() {
			return {
				contentHideHeight:'calc(100vh - '+ this.CustomBar +'px + 50px)',
				contentShowHeight:'calc(100vh - '+ this.CustomBar +'px - 50px)',
				ifShowMenu:true,
				settingRightPx:false,
				CustomBar: this.CustomBar,
				interval: 16,
				bookInfo:{},
				showTopBar: true,
				rendition: null,
				defaultFontSize: 16,
				defaultTheme: 3,
				themeList: [
					{
						name: 'default',
						style: { 
							body: {
								'color': '#000', 'background': '#fff'
							}
						}
					},
					{
						name: 'eye',
						style: {
							body: {
								'color': '#000', 'background': '#ceeaba',
							}
						}
					},
					{
						name: 'night',
						style: {
							body: {
								'color': '#fff', 'background': '#000'
							}
						}
					},
					{
						name: 'gold',
						style: {
							body: {
								'color': '#000', 'background': 'rgb(241,236,226)',
							}
						}
					}
				],
				modalName: null,
				navigation:[],
				locations:null,
				bookMarkShow:false,
				touchStartTime:0,
				bookMarkName:"",
				bookMarkListShow:false,
				bookMarkList:[]
			}
		},
		onReady:function(){
			console.log("页面初始化");
		},
		onLoad: function(option) { //option为object类型,会序列化上个页面传递的参数
			if(!option.bookId){
				uni.showLoading({
				    title: '电子书加载异常'
				});
				return;
			}
			uni.showLoading({
			    title: '电子书加载中'
			});
			this.$http({
				url:'/library/app/cnoocebook/bookInfo',
				data:{"bookId":option.bookId},
				method: "GET"
			}).then(response=>{
				uni.hideLoading();
				if(response.data){
					this.bookInfo=response.data;
					this.readEBook();
				}
			}).catch(error=>{
				uni.showToast({
					icon: 'none',
				    title: error,
				    duration: 2000
				});
			});
		},
		methods: {
			//阅读电子书
			readEBook(){
				//从本地缓存中获取字体大小设置
				const fontSize=uni.getStorageSync('font_size');
				this.defaultFontSize = fontSize?fontSize:16;
				this.interval=this.defaultFontSize;
				//从本地缓存中获取主题设置
				const theme=uni.getStorageSync('default_theme')
				this.defaultTheme = theme?theme:3;
				let DOWNLOAD_URL = "https://xxxx/"+this.bookInfo.eid;
				this.book = new Epub(DOWNLOAD_URL)
				this.rendition = this.book.renderTo('book', {
					//滚动翻页
					// flow: "paginated",
					 flow: 'scrolled-doc',
					width: window.innerWidth,
					height: window.innerHeight,
					method: 'default',
					restore: false
				});
				this.rendition.display();
				//内容加载完后进行回调
				this.rendition.hooks.content.register((contents) => {
					uni.hideLoading();
					this.scalePage(contents.content);
				});
				 
				 //图书解析完毕回调
				 this.book.ready.then(() => {
				 	// 生成目录
				 	this.navigation = this.book.navigation.toc;
				 	console.log(this.navigation);
				 	// 生成Locations对象
				 	let generate = this.book.locations.generate()
				 	console.log(generate);
				 	return generate;
				 }).then(result => {
				 	// 保存locations对象
				 	this.locations = this.book.locations;
				 	console.log(this.locations);
				 })
				// 获取Theme对象
				this.themes = this.rendition.themes
				// 设置默认字体
				this.setFontSize(this.defaultFontSize)
				//初始化主题
				this.registerTheme()
				// 设置默认主题
				this.setTheme(this.defaultTheme)
				//左右滑动翻页点击开始
				this.rendition.on('touchstart', e => {
					this.touchStartX = e.changedTouches[0].pageX;
					this.touchStartY = e.changedTouches[0].pageY;
					this.touchStartTime = e.timeStamp;
				});
				//左右翻页,显示隐藏操作菜单,显示书签弹框
				this.rendition.on('touchend', e => {
					 const offsetX = e.changedTouches[0].pageX - this.touchStartX
					 const offsetY = e.changedTouches[0].pageY - this.touchStartY
					 const time = event.timeStamp - this.touchStartTime
					 if (time<500&&Math.abs(offsetX) > (Math.abs(offsetY)+50) && offsetX > 100) {
						this.prevPage();
					 } else if (time<500&&Math.abs(offsetX) > (Math.abs(offsetY)+50) && offsetX < -100) {
						this.nextPage();
					 }else if(time<500&&offsetX==0 && offsetY == 0){
						this.showMenu();
					 }else if(time>=500){
						 this.bookMarkShow=true;
					 }
				});
			},
			//设置页面中图片的大小
			scalePage(body){
				let images = body.getElementsByTagName("img");
				let iheight = body.clientHeight,oheight;
				for (let i = 0; i < images.length; i++) {
					images[i].style.width='100%';
				}
			},
			//设置按钮显示隐藏
			setSettingRightPx(){
				if(this.settingRightPx){
					this.settingRightPx=false;
				}else{
					this.settingRightPx=true;
				}
			},
			//改变字体大小
			intervalFontSizeChange(e) {
				console.log(e.target.value);
				this.interval = e.target.value
				this.setFontSize(this.interval);
			},
			//上一页
			prevPage() {
				if (this.rendition) {
					uni.showLoading({
					    title: '电子书加载中'
					});
					this.rendition.prev().then((data) => {
					  uni.hideLoading();
					})
				}
			},
			//下一页
			nextPage() {
				if (this.rendition) {
					uni.showLoading({
					    title: '电子书加载中'
					});
					this.rendition.next().then((data) => {
					  uni.hideLoading();
					})
					
				}
			},
			//跳转到指定页
			toHref(href) {
				this.rendition.display(href);
			},
			//设置背景色
			setTheme(index) {
				this.themes.select(this.themeList[index].name);
				this.defaultTheme = index;
				uni.setStorage({
				    key: 'default_theme',
				    data: this.defaultTheme,
				    success: function () {
				        console.log('主题设置成功');
				    }
				});
			},
			//注册背景色
			registerTheme() {
				this.themeList.forEach(theme => {
					this.themes.register(theme.name, theme.style)
				})
			},
			//设置字体大小
			setFontSize(fontSize) {
				this.defaultFontSize = fontSize
				if (this.themes) {
					this.themes.fontSize(fontSize + 'px')
					uni.setStorage({
					    key: 'font_size',
					    data: fontSize,
					    success: function () {
					        console.log('字体大小设置成功');
					    }
					});
				}
			},
			//选中一级菜单,同时显示二级菜单页面
			tabSelect(e) {
				this.tabCur = e.currentTarget.dataset.id;
				this.scrollLeft = (e.currentTarget.dataset.id - 1) * 60;
				this.modalName = e.currentTarget.dataset.target;
			},
			//隐藏二级菜单
			hideModal(e) {
				this.modalName = null
			},
			//选择二级菜单触发事件
			selectList(data) {
				console.log(JSON.stringify(data));
				this.hideModal();
				this.toHref(data.href);
				this.settingRightPx=false;
			},
			//显示菜单
			showMenu(){
			    this.ifShowMenu = ! this.ifShowMenu;
			},
			//隐藏标签弹框
			hideBookMark(){
				this.bookMarkShow=false;
			},
			//保存书签
			saveBookMark(){
				if(!this.bookMarkName){
					uni.showToast({
						icon: 'none',
					    title: "请输入书签名再提交",
					    duration: 2000
					});
					return false;
				}
				//得到当前书签的百分比数
				let bookMark=(this.rendition.location.start.percentage).toFixed(4);
				//保存书签
				this.$http({
					url:'/library/app/bookmark/save',
					data:{"bookid":this.bookInfo.id,"bookmarkname":this.bookMarkName,"bookmarkpercentage":bookMark},
					method: "POST"
				}).then(response=>{
					if(response.code==0){
						uni.showToast({
						    title: '书签保存成功',
						    duration: 2000
						});
						this.bookMarkShow=false;
					}
				}).catch(error=>{
					uni.showToast({
						icon: 'none',
					    title: error,
					    duration: 2000
					});
				});
			},
			//隐藏书签列表
			hideBookMarkList(){
				this.bookMarkListShow=false;
			},
			//显示书签列表
			showBookMarkList(){
				this.$http({
					url:'/library/app/bookmark/getList',
					data:{"bookid":this.bookInfo.id},
					method: "GET"
				}).then(response=>{
					if(response.code==0){
						this.bookMarkListShow=true;
						this.settingRightPx=false;
						this.bookMarkList=response.list;
					}
				}).catch(error=>{
					uni.showToast({
						icon: 'none',
					    title: error,
					    duration: 2000
					});
				});
			},
			//跳转书签
			toMark(mark){
				const location = this.locations.cfiFromPercentage(mark.bookmarkpercentage);
				this.rendition.display(location);
				this.bookMarkListShow=false;
			}
		}
	}
</script>

css

<style>
.float-book-setting{
	line-height: 60upx;
	border-radius: 40upx;
	-webkit-transform: rotate(0deg);
	transform: rotate(0deg);
	-webkit-transition: -webkit-transform .3s;
	transition: -webkit-transform .3s;
	transition: transform .3s;
	transition: transform .3s,-webkit-transform .3s;
}
.float-book-setting-active{
	-webkit-transform: rotate(135deg);
	transform: rotate(135deg);
}
.setting-tools{
	position: fixed;
	width: 100%;
	bottom: 0;
	z-index: 1025;
	box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
	background-color: white;
}
.setting-font-size{
	position: absolute;
}
.setting-font-size-val{
	margin-left: 100upx;
}
.setting-font-bg{
	position: absolute;
}
.setting-font-bg-val{
	margin-left: 100upx;
}
.setting-font-bg-txt{
	margin: 10upx;
	width: 40upx;
	height: 40upx;
	float: left;
	border: 1px solid #eeee;
	box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
}
.setting-dir{
	text-align: center;
	height: 35px;
	line-height: 35px;
}
	
.setting-close{
	text-align: center;
	height: 35px;
	line-height: 35px;
}
.setting-content-view{
	height: 100upx;
	line-height: 100upx;
	margin: 0 10px 0;
}

	page {
		background-image: var(--gradualBlue);
		width: 100vw;
		overflow: hidden;
	}

	.DrawerPage {
		position: fixed;
		width: 100vw;
		height: 100vh;
		left: 0vw;
		background-color: #f1f1f1;
		transition: all 0.4s;
	}

	.DrawerPage.show {
		transform: scale(0.9, 0.9);
		left: 85vw;
		box-shadow: 0 0 60upx rgba(0, 0, 0, 0.2);
		transform-origin: 0;
	}

	.DrawerWindow {
		position: absolute;
		width: 85vw;
		height: 100vh;
		left: 0;
		top: 0;
		transform: scale(0.9, 0.9) translateX(-100%);
		opacity: 0;
		pointer-events: none;
		transition: all 0.4s;
		padding: 100upx 0;
	}

	.DrawerWindow.show {
		transform: scale(1, 1) translateX(0%);
		opacity: 1;
		pointer-events: all;
	}

	.DrawerClose {
		position: absolute;
		width: 40vw;
		height: 100vh;
		right: 0;
		top: 0;
		color: transparent;
		padding-bottom: 30upx;
		display: flex;
		align-items: flex-end;
		justify-content: center;
		background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.6));
		letter-spacing: 5px;
		font-size: 50upx;
		opacity: 0;
		pointer-events: none;
		transition: all 0.4s;
	}

	.DrawerClose.show {
		opacity: 1;
		pointer-events: all;
		width: 15vw;
		color: #fff;
		padding-bottom: 60px;
	}

	.DrawerPage .cu-bar.tabbar .action button.cuIcon {
		width: 64upx;
		height: 64upx;
		line-height: 64upx;
		margin: 0;
		display: inline-block;
	}

	.DrawerPage .cu-bar.tabbar .action .cu-avatar {
		margin: 0;
	}

	.DrawerPage .nav {
		flex: 1;
	}

	.DrawerPage .nav .cu-item.cur {
		border-bottom: 0;
		position: relative;
	}

	.DrawerPage .nav .cu-item.cur::after {
		content: "";
		width: 10upx;
		height: 10upx;
		background-color: currentColor;
		position: absolute;
		bottom: 10upx;
		border-radius: 10upx;
		left: 0;
		right: 0;
		margin: auto;
	}

	.DrawerPage .cu-bar.tabbar .action {
		flex: initial;
	}
</style>

### 如何在 UniApp 中集成和使用 EpubJS 进行电子书阅读 #### 配置环境 为了使 `EpubJS` 能够顺利运行于基于 Vue.js 构建的应用程序如 UniApp 上,需先安装必要的依赖项。通过 npm 或 yarn 安装 epubjs 库。 ```bash npm install epubjs --save ``` 或者 ```bash yarn add epubjs ``` #### 导入并初始化 EpubJS 接着,在项目中的合适位置导入此模块,并创建一个新的 EPUB 实例来加载目标文件: ```javascript import { ref } from 'vue'; // 引入epubjs import ePub from 'epubjs'; const bookUrl = "https://path-to-your-book.epub"; // 替换成实际EPUB 文件路径 let rendition; export default { setup() { const viewerRef = ref(null); function initReader() { let book = ePub(bookUrl); rendition = book.renderTo(viewerRef.value, { width: window.innerWidth, height: window.innerHeight, method: "default" }); rendition.display(); } return { viewerRef, initReader }; }, }; ``` 这段代码展示了如何定义一个名为 `initReader()` 的函数用于初始化读者界面[^2]。 #### 创建视图组件 最后一步是在页面上预留一块区域作为书籍显示区,并调用上述方法完成渲染工作: ```html <template> <div id="reader"> <!-- 显示图书的地方 --> <div class="book-view" ref="viewerRef"></div> <!-- 控制按钮等其他UI元素可以放在这里 --> <!-- 初始化读取器 --> <button @click="initReader">打开书籍</button> </div> </template> <style scoped> .book-view { position: relative; width: 100%; height: calc(100vh - 50px); /* 减去顶部导航栏高度 */ } </style> ``` 以上就是关于怎样利用 `EpubJS` 来增强 UniApp 应用功能的一个简单介绍[^3]。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火柴有猿

您的鼓励,将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值