耗时三个月,我高仿了一个起点小说阅读器

5,836 阅读7分钟

前言

起因是最近看小说的APP广告越来越多,但不少书源内容也时常出现问题。正好摆烂太久让我很有负罪感,就想着趁着这个契机学点新的东西。公司里用的都是vue技术栈,所以我想着用vue3做个小项目,顺便熟悉一下vue3的语法。从八月开始,断断续续搞了点Demo,直到年底稍微有点空闲,才开始着手把整个项目完善起来。

项目地址

github

gitee

支持平台

平台是否支持
H5
Android
IOS
小程序需要修改renderjs

项目介绍

eReader 是一款基于 uni-app 开发的小说阅读器,功能完善,使用便捷,支持跨平台部署。移动端完全由前端实现,无需后端支持,打包后即为一个独立的APP,极大降低了部署和维护成本。H5端由于跨域问题需要启用一个简单的后端服务器,但移动端打包后完全开箱即用。

技术架构与部署

  • uni-app 的跨平台特性使得该项目在移动端和H5端之间无缝切换,移动端是纯前端实现,不依赖额外的服务器。

  • H5端需要启用后端服务器来解决跨域问题,但移动端完全是前端应用,避免了额外的服务器负担,极大简化了部署和维护流程。

  • 使用技术栈如下

    1. Vue3 + TypeScript:项目基于Vue3和TypeScript实现。
    2. Node + Express:H5端使用Node + Express搭建了一个简单的后台,负责爬取数据。
    3. uni.request:在APP端通过uni.request获取数据,不用启用后端应用。
    4. Cheerio:用Cheerio来解析HTML,提取书籍信息。
    5. uni.setStorage:数据缓存使用了uni.setStorage存储。
    6. 阅读引擎:主要是用 canvas.measureText 来计算文本宽度,通过JS计算宽高分页,支持两端对齐、标点避头等排版优化。
    7. 分页:分页计算用了uni-app的 renderjs 操作Canvas, uni.createCanvasContext 在APP端性能表现不佳,应尽量避免使用。
    8. 海报分享:海报分享功能使用了 limeui-painter

平台功能

  • 丰富的书源:内置多个书源,满足大多数阅读需求,并支持灵活切换。

  • 全面的功能:包括书架管理、小说搜索、阅读器设置(夜间模式、字体、背景主题、翻页方式)、章节缓存等,功能齐全。

  • 个性化体验:支持书签、目录跳转、缓存、夜间模式等用户自定义设置。

  • 逻辑闭环:书源管理、阅读设置、书签等功能平滑切换,确保使用流畅、体验一致。

  • 详细功能列表

    1. 书架:可以加入/移除书架、置顶小说、分享(APP端)、查看详情、搜索、小说排序和浏览历史等功能。
    2. 分组:可以管理小说分组,支持新增、删除、修改、置顶等操作。
    3. 精选推荐:集成了 夸克热搜 的书单推荐,帮助大家发现热门书籍。
    4. 我的:包括书源管理、浏览历史、夜间模式、关于、意见反馈、缓存清除和分享等设置。
    5. 小说搜索:内置了 12 个书源,基本能满足大部分人的阅读需求。
    6. 书籍详情:展示书籍信息、简介、目录等,支持分享功能。
    7. 阅读器:支持添加/移除书架、添加/删除书签、查看目录、白天/夜间模式切换、翻页方式、字号和背景主题切换等多项个性化设置。此外,还支持其余书源切换章节缓存(包括缓存全部、缓存后20章和缓存当前章节后的所有章节)。
    8. 目录:支持目录查看、缓存状态、书签、章节跳转、快速跳转(比如去当前章节、去底部)等功能。

项目结构

|-- undefined
    |-- .prettierignore
    |-- .prettierrc.js
    |-- index.html
    |-- package.json
    |-- tsconfig.json
    |-- vite.config.ts
    |-- src
        |-- App.vue
        |-- env.d.ts
        |-- main.ts
        |-- manifest.json
        |-- pages.json
        |-- type.d.ts
        |-- uni.scss
        |-- api  #请求接口
        |   |-- common.ts
        |-- components
        |   |-- BookTip.vue  #阅读页第一次打开提示
        |   |-- Expand.vue  #书籍详情简介收起与展开
        |   |-- share.vue  #分享组件
        |   |-- TabBar.vue  #重写tabbar,没使用uni自带tabbar
        |   |-- global  #全局组件
        |   |   |-- g-confirm.vue  #确认和输入弹窗
        |   |   |-- g-icon-fonts.vue  #图标
        |   |   |-- g-page.vue  #每个页面根元素,主要是做主题切换,设置全局css样式(uniapp的APP.vue没有根元素)
        |   |   |-- g-popup.vue  #底部和中间弹窗封装
        |   |   |-- g-statusbar.vue  #顶部statusbar占位组件,h5端高度为0,app端有默认高度
        |   |-- painter  #海报绘制组件
        |   |-- popover  #书架排序气泡窗
        |-- directives   #vLongPress指令封装
        |   |-- index.ts
        |-- pages
        |   |-- blank  #我的-跳转页面
        |   |   |-- about.vue  #关于我们
        |   |   |-- agreement.vue  #用户协议
        |   |   |-- feedback.vue   #意见反馈
        |   |   |-- history.vue  #浏览历史
        |   |   |-- origin.vue  #书源管理
        |   |   |-- policy.vue  #隐私政策
        |   |-- bookDetail  #书籍详情页
        |   |-- catalogs  #目录页
        |   |-- groupDetail  #分组详情页
        |   |-- reader  #阅读器
        |   |   |-- index.vue
        |   |   |-- index_v1.vue  #第一版,使用columns布局分页
        |   |   |-- index_v2.vue  #第二版,使用canvas.measureText计算宽度,js计算宽高进行分页(算法不完善,可以看看思路)
        |   |   |-- readerLayout.ts #第三版,感谢 [@前端一锅煮] 大佬的分享
        |   |   |-- components
        |   |       |-- Origin.vue  #换源组件
        |   |       |-- Renderjs.vue  #使用uniapp的rendejs获取 document 文档对象
        |   |       |-- Renderjs_v2.vue #第二版renderjs
        |   |-- search  #搜索页
        |   |-- tabBar  #自定义tabbar
        |       |-- book.vue  #精选
        |       |-- home.vue  #书架
        |       |-- personal.vue  #我的
        |       |-- components
        |           |-- addGroup.vue  #书架、分组详情里[移至分组]功能
        |           |-- bookDetail.vue  #书架、分组详情里长按展示详情功能
        |           |-- groupItem.vue  #分组项
        |-- parser  #app端数据解析
        |   |-- catalog.ts  #目录解析
        |   |-- content.ts  #章节内容解析
        |   |-- index.ts 
        |   |-- search.ts  #搜索内容解析
        |   |-- source.ts  #内置书源
        |   |-- top.ts  #精选内容解析
        |-- static
        |-- store  #store
        |   |-- AppOption.ts  #app的系统信息
        |   |-- index.ts  #一些缓存相关数据处理:书架、历史、缓存章节、搜索历史等
        |-- styles
        |-- types
        |-- utils
            |-- Config.ts
            |-- Control.ts
            |-- index.ts
            |-- request.ts  #请求处理和响应拦截
            |-- RequestHeader.ts  #最初是想伪造请求头的,但是uni的app端ua固定了

后续功能优化

  • 错误处理:当前未处理极端情况下的错误请求,导致产品在特定条件下可能不够健壮,后续会加强异常处理。

  • 网络字体支持:项目打包后APK约15MB,内置字体包增大了文件体积,后续会考虑支持网络字体加载以实现更丰富的阅读体验。

  • 书源导入与更新:第三方书源存在不稳定性,网站变动可能导致解析错误。后续会考虑支持书源离线导入和在线更新,有助于解决此问题。

  • 听书功能:作为干眼症患者,听书功能对我来说还是非常重要的,未来计划加入该功能。

  • 去除广告:第三方书源可能包含广告和无关链接,影响阅读体验。后续考虑支持长按选择内容去除,并应用到所有章节,将极大提升阅读质量。

项目展示

h5表现

  • 书架 PixPin_2025-01-15_11-11-06.gif
  • 精选

PixPin_2025-01-15_11-33-27.gif

  • 我的 PixPin_2025-01-15_11-23-31.gif

  • 搜索

PixPin_2025-01-15_11-35-22.gif

  • 详情

PixPin_2025-01-15_13-47-58.gif

  • 阅读器

PixPin_2025-01-15_13-51-20.gif

app端表现(IOS)

Android端未完整测试,可能存在部分兼容问题

  • 书架(亮) 书架.jpg

  • 搜索(亮) 081215e1ebc858e4a3e2bb6b25e7591.jpg

  • 书源管理(亮) 97a215463434014bfca7a9e306758bb.jpg

  • 我的(亮) c24b6f22642b73a89fb29c61c486eda.jpg

  • 浏览历史(亮)

5378049e7f666f069a3c7893964859f.jpg

  • 分组(暗)

647cdc2d9ef238c763c0481ac3f4dd6.jpg

  • 分组详情(暗) 605e704b77169fcfd8f6d7c75b06974.jpg

  • 我的(暗) 0d43113e996a69f52d58813504784b5.jpg

  • 意见反馈(暗) ea5984628d3013dab64992e49b72e0c.jpg

  • 详情(暗) 8c2feac4fb0a99b3be6ed359c91cdad.jpg

  • 分享

325a55ea810dde156bf03c8e9acfd1f.jpg

  • 阅读器

ios.gif

总结

  • 最初只是为了学习新技术栈,项目框架、组件设计没考虑太多。但随着功能的增加,组件复用和方法抽象的需求变得明显,过程中也渐渐感觉到有些力不从心。

  • 尽管仍有一些缺漏,但是整体来看来这个项目已经勉强算得上是一个完整的、功能闭环的产品。作为一个人独立完成,自己也算是比较满意了。

  • 开发过程中遇到了不少挑战,比如阅读器排版引擎就经历了三次重构,才最终达到了理想效果。那段时间搞得头都要秃了(本来所剩无几的发量越加稀少)。 后续会写写教程,记录下开发过程中遇到的坑。

相关

水了几篇文章,回家过年咯(逃~)

参考

感谢下面两位大佬的文章

  1. 一个基于Vue.js的小说阅读器
  2. 你不知道的阅读器排版引擎~