一. 前言
完整代码链接:https://github.com/CHANGsome/awesome-demos
最近看到这篇文章 Reddit | 最糟糕的音量控制设计大赛…
其中一个作品是这样的:
感觉这个摇摇乐挺有意思的,所以自己做了一个:
做完之后觉得应该用起来才完整,又做了个音乐播放器:
可以,面目全非,谁能想到这个摇杆是用来调音量的呢~
二. 摇摇乐实现思路
摇摇乐本身的实现并不复杂,主要有两个问题需要解决:
- 鼠标拖拽时让杆子动起来。
- 记住摇过的圈数。
1. 实现拖拽摇动杆子的动画
先解决第一个问题,怎么让杆子跟着鼠标动?
从图中可以看出来,摇杆在绕着底部旋转,所以只要知道旋转角度就好了。
transform: `rotate(${current_deg}deg)`
监听鼠标的mousemove
事件,在鼠标每次移动的时候实时计算更新当前的旋转角度current_deg
:
当前旋转角度的正切 tan deg = det_x / det_y
det_x = 鼠标x坐标 - 坐标原点x坐标
det_y = 鼠标y坐标 - 坐标原点y坐标
其中,坐标值都根据离视口左上角的距离计算
2. 旋转圈数计数
实现用它来控制媒体音量的话,设定是:
- 顺时针旋转一圈,圈数+1,音量+1
- 逆时针旋转一圈,圈数-1,音量-1
我们让杆子每次经过 y 轴正半轴的时候算是一圈结束,此时的旋转角度 deg = 0
。
理论上来讲我们只要判断:
let circles = 0; // 旋转的圈数
handler.onmousemove=()=>{
...
// 如果是顺时针方向旋转
if(current_deg === 0){
circles ++;
}
...
}
但是这里存在一个问题:浏览器会限制事件的触发频率,onmousemove
的触发是有间隔时间的。所以经常取不到旋转角度 current_deg
刚好为 0 的时候,所以这个时候需要委婉一点判断。
判断是否顺时针方向转了一圈的条件:
- 什么时候圈数+1:
current_deg < last_deg,即旋转角度从 359 度变为 1 度的时候,在顺时针旋转的时候因为角度一直时递增的(0 度增到趋近于 360 度),所以current_deg
小于last_deg
的时候就是从第四象限转到第一象限的时候。- 判断顺时针方向旋转:
(1)当前鼠标的 x 坐标大于上一次的 x 坐标:不是充分必要条件,因为onmousemove
绑定 在document
上,有时候拖拽鼠标会往回移动。
(2)所以加一个条件一起判断是否顺时针:current_deg < 10,只在离 y 正半轴10度的地方判断。
判断是否逆时针方向转了一圈的条件:
- 什么时候圈数-1:current_deg > last_deg,即从第一象限转到第四象限的时候。
- 判断逆时针方向旋转:
(1)当前鼠标的 x 坐标小于上一次的 x 坐标
(2)360 - current_deg < 10
至此,最难的部分解决了,剩下的就是怎么把它放在一个音乐播放器上了。
三. 自制一个音乐播放器
HTML 本身有 audio 标签可以用来播放音乐,但是直接使用的话有自定义样式,为了将上面的音量控制旋转杆用上去,这里重新封装 audio 标签。
具体实现:
- 新建一个 audio 元素,不带任何自定义控件,此时在页面上什么都不显示。
<audio
src={audioSrc}
preload="true"
>
您的浏览器不支持 audio 标签。
</audio>
- 需要用到的 audio 的属性:
duration 音频总长度
currentTime 音频当前播放时间
volume 当前音频音量
- 需要用到的 audio 的几个事件:
canplay 当浏览器可以播放音频/视频时
pause 当音频/视频已暂停时
play 当音频/视频已开始或不再暂停时
timeupdate 当目前的播放位置已更改时
其他就是很简单的逻辑,点击某个按钮触发相应的事件。
四. 总结
使用该音量控制组件的时候有一个问题:当摇动杆子时监听 document 的 mousemove 事件,此时会触发其他区域的文字和内容的选中和 hover 效果,所以需要禁止掉。
解决方案:设置一个变量 isMouseMove 来记录mousemove事件是否被触发,当被触发的时候设置以下 css 样式:
user-select: none; // 禁止内容选中
pointerEvents: 'none'; // 禁止鼠标其他事件