在触控板中我们的操作往往是自然的,所谓自然体现在我们向下滚动一个网页在鼠标中需要向下,而在触控板上需要向上拖动,因为触控板的操作是类似于屏幕的。但是在b站等一些视频播放器中,我们想通过触控板调整音量向下时,习惯性向下拖动,导致他音量增加了,这是浏览器并未区分触控板与鼠标的方向,此刻你的操作仍然是相反的,这在网页浏览中符合直觉,但是音量调整中又并非如此。因此这里通过阻止默认音量的滚动事件(wheel)通过对wheelDelta的值进行判断从而区分触控板与鼠标进行分别的操作。

这是一个不完全的文章,行文只代表研究思路,不一定是最优解。

并不是所有触控板都支持滚动操作,支持的大部分都有一定的精细程度。

具体的参数可能与硬件或浏览器环境有关,仅在Matebook 14(2019)Macbook Air(2020)与两款鼠标(具体见下)下进行测试。

事情の始末

标题其实有点花里胡哨了,事情还是很简单的。

是这样的,我最近开始比较多的使用触控板了,因为我没有把显示器带到学校来,但是我才注意到一个问题,就是触控板的方向和鼠标的方向是相反的,这点你可以自己感受一下(当然macos下是默认统一的,需要装一个软件来单独逆转某个部分,不然就只能设置是否自然滚动)。

于是我们可以很自然的想到在网页中他们滚动的正负值也应该是相反的,因此就出现了我们简介中的问题。最近用触控板比较多,偶尔看视频调个音量也只是想优雅拖拽一下,所以还是想解决这个问题的,要解决这个问题的思路还是很简单的:我新建一个鼠标滚轮事件,在其中区分触控板与鼠标,然后阻止默认事件,对触控板事件的值取反,这样就可以实现触控板向下的时候音量也随之调低对,但是对于我这个渣渣来说,事情又没这么简单。

Wheel Event

Wheel是这样一个事件,他会接收鼠标或类似输入设备的参数,他分别会返回如下参数,(来自MDN,有省略)。

参数 说明
WheelEvent.deltaX 返回double值,该值表示滚轮的横向滚动量。
WheelEvent.deltaY 返回double值,该值表示滚轮的纵向滚动量。
WheelEvent.deltaZ 返回double值,该值表示滚轮的z轴方向上的滚动量。

有了事件我们得知道如何判断,当然我是参考自stackoverflow的这个回答得出来了大致的思路,即鼠标滚轮与触控板由于精细程度的原因他们得到的滚动量数值有着较明显的差异。

但是答案中的代码经过我的测试始终没法得到一个满意的结果,于是我自己做了以下测试。

经过我的测试,他们有着较为明显的界限,并且都分布在一个范围内。

See the Pen wheel event test by Max C. Foo (@maxchang3) on CodePen.


(你也可以自己试试你的值为多少)

(鼠标下,以罗技MX Anywhere2s在Chrome下为例,对deltaY取绝对值,增量可达数千(装b一下瞎写个符号表达),英菲克P-M6在Edge下为180左右,也可达360至数千)

(触控板下,以Macbook Air(2020)在Chrome下为例,对deltaY取绝对值,增量可达200左右,Matebook 14(2019)类似))

所以为了区分触控板,我们必须要找到一个比较良好的界点,但是问题是这样的,我们前面测试过,这和硬件都是有区别的,并且他们的区间是有所重复的,因此在滚动量这里我们并不能精准的区分触控板与鼠标。这里实际上我还没有找到正确答案。

但是我们足够幸运,因为我们不需要这么高的触控板速度,毕竟——我们只是想调音量而已,在调节音量的场景下,我们的速度基本不会达到很高的阈值,经过我的测试取值100来分别是否是触控板基本已经符合我们调音量的要求了。

更不用提一般我们有音量的时候基本是要稍微精细一点的操作的,而想要一滑直接静音——依靠触控板的惯性,这个值也不会高到很夸张。

所以我写了如下代码

1
2
3
4
5
function handler(e) {
let isTrackpad = (e.wheelDeltaY && Math.abs(e.wheelDeltaY) < 100);
console.log(isTrackpad)
}
document.addEventListener("wheel", handler, false);

对于触控板的判断,确实有时候会出问题,但是对于鼠标的判断基本是精准的,毕竟鼠标一般到不了100以下。

操作b站播放器 - 数值

上面的思路基本完成后,问题就来了,我们如何让新的滚轮事件触发音量调节呢,首先肯定要阻止默认的事件:

1
2
e.stopImmediatePropagation();
e.stopPropagation();

但是怎么出发音量呢,说真的我是倾向于找到一个播放器自己的函数的,但是我并没有找到,当然我肯定他在 https://s1.hdslb.com/bfs/static/player/main/widgets/jsc-player.4a422538.js 或者另一个 Video.js里,但是由于他里面已经混淆过我很难找到。

于是我搜索一些项目是否实现了这些功能,我找到了quickdo,这里他找到对应的video标签,内有一个函数player.volume()可以返回当前音量(范围为0到1),使用player.volume=xxx来赋值音量,我的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function handler(e) {
//阻止默认事件
e.stopImmediatePropagation();
e.stopPropagation();
let isTrackpad = (e.wheelDeltaY && Math.abs(e.wheelDeltaY) < 100);
// 全屏界面时进行操作
let isFullscreen = (Array.from(document.querySelector('body').classList)).includes("player-fullscreen-fix");
// flag 用于把是否是触控板转换为正负值,从而实现触控板下的反转。
let flag = -1;
//36000来自我鼠标的最低速度360,这样可以保证最后音量的增量1%对应我鼠标最慢的增量。
let MAGIC_NUMBER = 36000;
// 触控板下这个值要除以一个值,因为触控板精度过高,增量低,这样除后的值正好是500
//500是经过计算后被wheelDeltaY所除后的增量达到约一个刻度(也就是触控板轻微挪动是1%)。
if(isTrackpad && isFullscreen) {flag = 1;MAGIC_NUMBER/=72; };
// 获取播放器对象
let player = document.querySelector(".bilibili-player-video").firstChild;
// 新音量即触控板反转操作,鼠标正常操作,增量为DektaY除以魔📖,这里是提前计算好的。
let newVolume = player.volume - flag*e.wheelDeltaY/MAGIC_NUMBER;
// 小于0取0,大于1取1
newVolume = (newVolume<1)?Math.max(newVolume,0):1;
player.volume = newVolume;
}
document.addEventListener("DOMMouseScroll", handler, false);//兼容firefox
document.addEventListener("wheel", handler, false);

这里数值的计算还是比较有意思的,因为你要保证你接管后的触控板和鼠标轻微挪动的增量是一个刻度(1%),但是问题来了,为什么我要两个都接管呢?不能只取触控板的情况吗。

因为这里是我拿最后的源码倒推的过程,实际上是,当我只管触控板的时候,经常会和原有的事件冲突,并且一个更重要的问题,这样调整音量确实是管用的,但是——他不会像你默认事件一样显示数值,因为我还是没找到播放器直接调用的地方。这里还是差了一次封装。

操作b站播放器 - 界面

不优雅的解决方案,我参考了上面的quickdo项目,自己重新实现了hinter这一弹出层并重新调用了他。

并且,又单独修改了音量条和其数值,最后达到了一个姑且还行的效果,已经发到了greayfork

最后实现还是不优雅,但是又不能不能用,当然如果能精准的区分触控板,直接调用播放器的音量调整(带弹窗和数据同步的那种,我估计这个肯定是能找到的,当然我没……)估计是最好的了。

就这样吧,拜拜。