微信读书网页版终于能自动阅读了!
微信读书移动端
不知道有多少小伙伴使用微信读书App的? 在移动端有个非常好用的功能叫自动阅读. 这个功能解放了双手, 让阅读更加沉浸式.
微信读书网页端痛点
但是我更喜欢用电脑看书, 电脑版的微信读书就是个网页, 非常的简洁, 除了阅读书籍外没有任何功能. 包括自动阅读. 这就很麻烦了, 这对我来说真的是非常刚需的功能, 没有了自动阅读我都看不下去了, 时不时就要拿鼠标去点一下滚轴. 我想应该不止我一个人遇到了这个问题, 所以先搜索了下浏览器插件.
很遗憾, 看起来也就第一个用的人多点. 但是看起来都不包含自动阅读的能力. 看样子只能自己写一个了.
原谅我词穷, 我真想不到该起什么名字...就和他们同名也无妨, 反正我只想解决我自己的需求.
设计思路
结合日常阅读的习惯, 我设计了以下几个功能点.
- 开启/关闭自动阅读
- 调整自动阅读速度
- 开启自动阅读时, 有时需要手动让滚轴往上/往下走一些
- 保存配置, 避免每次都要重新设置
首先我倾向于选择键盘按键来实现这些功能, 因为按下一个按键一定比用手抓到鼠标再点击目标要快的多. 经常玩游戏的同学都很熟悉WASD的键位, 在DNF中, D是向右走, 也就是向前行. 而A则是往回走. 因此, 我设定A是降低阅读速度, D则是增加阅读速度.
那么剩下的W和S则对应了往上和往下滚动一小段距离. 这个需求场景是当我们在阅读时, 可能因为某个片段有些复杂或过于简单, 而导致我们想短暂地调整下阅读进度.
而开启和关闭自动阅读, 则是定在了'x'的键位上. 因为我小时候玩的一款叫彩虹岛的网游就是按X攻击的. 因此X在我印象中充满了战斗气息. 怎么样, 我这套键位设计的很合理吧!
成果展示
自动阅读
正如前面我们所约定的那样, 我们打开一本书进行阅读时, 页面右下角会提示你
只需要按下x, 就会开启自动阅读了. 并且这个速度还是可调的
设置中心
不止是这样哦. 因为我显示器比较大, 我发现页面两侧有很大的空白. 所以我还设置了屏占比的功能
这个功能其实是和另一个微信助手借鉴的. 但是他们设置的有bug. 视频版里有详细介绍.
在进入微信读书的时候, 我的插件会在左上角放一颗七龙珠.
至于为什么是七龙珠...没啥特别的原因. 我真就是需要icon的时候突然想起七龙珠了, 就整了一颗放这. 可以点击这颗龙珠召唤, 不是, 点击打开设置面板.
屏占比
官方默认就是1000px的, 最大可以设置2000px. 在我的大显示器上几乎全屏铺满了. 我拿手机拍摄大家对比下.
勿扰模式
前文展示了在调整速度、切换界面的时候右下角是有toast提示的. 有时候这些额外的元素可能会吸引我们的注意, 而插件的用法非常简单, 并不需要反复提醒. 因此, 在设置中我们可以开启勿扰模式, 世界都会变得安静.
菜单自定义显隐
不知道大家有没有感觉这一排的icon很鸡肋
这些icon常驻在页面中, 但是谁会没事干天天改字号? 谁会亮色和暗色主题来回切换? 在我看来就是影响我沉浸式阅读的存在. 因此我做了2个改动. 第一个改动是将其位置从主体页面中移除, 放在了header区域
其次, 正如我前文所说, 这大部分的icon都用不着. 对我来说, 只有目录
这个功能确实偶尔会用到. 因为可能对某些章节不感兴趣, 想跳过去. 所以, 我就对他做了个自定义显隐功能.
怎么样? 是不是更合理一些了?
实现方案
浏览器扩展开发, 我建议使用Plasmo进行开发. 他是一个浏览器扩展开发的解决方案, 允许你使用 React 进行开发, 并提供了HMR、打包等一系列的开箱即用的方案. 官方文档写的非常通俗易懂. 不过你需要先了解基础的浏览器插件开发知识, 如Background、Content Script等.
接下来我们来思考如何实现前文中提到的业务. 这里我想挑几个我觉得值得一聊的内容详细说说.
自动阅读
如何让页面自动阅读呢? 转化成技术的用词就是: 如何让滚轴高度稳定的往下滚动. 这个比较简单, 思路如下
- 获取到页面的滚轴高度
- 将滚轴高度设置为基于之前获取到的滚轴高度再+上一定的滚轴高度
但是这里有个技术细节, 假定我们的代码片段如下
// speed 是用户设置的阅读速度
let speed;
function addScrollTop(): void {
let recordScroll = document.documentElement.scrollTop;
recordScroll += speed;
document.documentElement.scrollTop = recordScroll;
}
现在当我们调用addScrollTop
时, 就会往下滚动一些距离. 那么请问, 调用一次也就一次, 调用两次也就两次, 如何让他不停的调用自己呢? 可能有同学会想到setInterval
, 那现在我们来思考一个场景.
当我在使用微信读书的时候, 突然有人给我发了条电脑微信, 我中断了读书的状态回复微信, 结束后再回来, 请问刚刚的自动阅读是不是始终在自动阅读? 如果你使用的是定时器, 那么一定是在继续阅读的. 这将导致你必须往回寻找刚刚的阅读进度.
那么我是如何解决这个问题的呢. 我使用的是requestAnimationFrame
, 这个API可以让你的显示器窗口不可见时, 暂停自动阅读. 也就是说, 当我读到某个进度而划走窗口做别的事情时, 再回来, 进度可以无缝衔接. 那么什么叫显示器窗口不可见呢?
其实就是几个桌面窗口的切换. 但是如果我的浏览器和微信都在同一个窗口下, 那么我点击微信, 也就是浏览器失焦的时候, 会停止自动阅读吗? 答案是不会的. 这符合用户习惯, 因为我无法判断用户失焦时是不是期望停止自动阅读. 毕竟阅读时顺手回几句简短的消息也是很正常的操作. 但是切换窗口则通常是有更复杂的任务要执行, 短时间回不来.
判断翻页
之前在完成初版的时候出现了一个bug, 当我阅读完一整页的内容时, 进行翻页. 注意, 翻页是需要手动操作的. 按下右方向键就可以了, 这是官方提供的. 在翻页后, 阅读进度会闪现到很后面. 我们不难分析表现可以推测出, 问题的原因是第一页结束的时候, 滚轴当前的位置比如是800, 翻页后从0开始看. 但因为代码中记得刚刚是看到了800的高度, 所以下一个渲染时间点就把进度调整到了800+speed的位置. 因此, 我们需要在翻页时重置记忆的滚轴高度. 那么怎么判断翻页了呢? 通过对页面的研发不难发现, 当章节变化的时候, 页面上会显示章节的标题.
那么我们只需要监听这个章节的DOM内容变化即可. 我们需要借助MutationObserver
的API
const chapterTitle = document.querySelector('.readerTopBar_title_chapter');
const documentObserver = new MutationObserver(function () {
// 重置滚轴高度
});
// 对章节标题 DOM 进行监听
documentObserver.observe(chapterTitle, {
attributes: true,
childList: true,
characterData: true,
subtree: true,
});
保存频率过高导致报错
自动阅读的速度、屏占比等都是自动保存的. 每次更改阅读速度, 都会直接保存在浏览器当中.
// 每次需要保存配置的时候就调用该方法
function updateConfig(config) {
// storage 是 Plasmo 提供的存储模块
storage.set('config', config);
}
那假如我连续几十次调整速度, 这个函数就被调用了几十次, 然后控制台就报错了.
This request exceeds the MAX_WRITE_OPERATIONS_PER_MINUTE quota
查了下资料, 大概意思是说浏览器不允许插件在短时间内大量的执行存储操作. 那么显然我们就需要上防抖了. 为此我借助了Rxjs中的防抖操作符, 玩Angular的同学应该很熟悉.
const updateConfig$ = new Subject<Config>();
// 更新操作任你调, 我只执行最后一次.
updateConfig$.pipe(debounceTime(500)).subscribe((config: Config) => {
storage.set('config', config);
});
function updateConfig(config) {
updateConfig$.next(config);
}
源码中我最终移除了这个方案, 因为只因为这一个场景引入一个lib感觉不太优雅. 我手动用定时器来解决这个问题. 反正目的都是一样的, 能实现防抖就可以.
结语
这个插件只是为了方便我自己使用微信读书. 如果你也需要微信读书, 快来体验沉浸式阅读吧! 目前已上架下列应用商店, 项目地址: https://github.com/Eve-Sama/weread-web-extension
- Chrome: https://chromewebstore.google.com/detail/himocmagklembngmjkephklagajfbill
- Firefox: https://addons.mozilla.org/zh-CN/firefox/addon/微信读书助手v2/
我是前夕, 专注于前端和成长, 希望我的内容可以帮助到你. 公众号: 前夕小课堂
本文禁止转载!