完善进度条模拟滑块的拖曳功能
本帖最后由 马黑黑 于 2022-11-28 18:26 编辑 <br /><br /><style>#papa { margin: 10px 0; width: 100%; position: relative; user-select: none; }
#papa > p { padding: 6px 0 6px 0; }
#mplayer { margin: auto; position: relative; width: 200px; display: grid; place-items: center; padding: 20px 0 20px 0; border: 1px solid tan; }
#prog { --xx: 0px; width: 100%; height: 4px; background: lightgray; position: absolute; display: grid; place-items: center; }
#prog::before, #prog::after { position: absolute; content: ''; }
#prog::before { left: 0; width: var(--xx); height: 100%; border-radius: 6px; background: teal; }
#prog::after { left: calc(var(--xx) - 5px); opacity: .85; width: 16px; height: 16px; background: radial-gradient(transparent 2px, teal 0, black); border-radius: 50%; cursor: pointer; }
</style>
<div id="papa">
<p>用普通标签模拟进度条的好处主要在于对浏览器的兼容性好,不会因浏览器的迭代而变成不可用。早些时候,大佬们改造过meter标签令其多姿多彩,遗憾的是,随着浏览器的发展,相关属性已被废弃,被改造的meter现在啥也看不到了。而用像div、span这样的普通标签封装的带滑块的模拟进度条不存在这样的境遇,因为这类标签一般没有实验性的属性,即使有,我们在做进度条时也不会用到。</p>
<p>用普通标签做带滑块的进度条也有局限,我们需要额外编程,以使得进度条拥有接近input这样的拖曳滑块功能。此前,我们封装的音频播放器中,有两个用普通标签模拟滑块的插件,它们已经支持拖曳功能,但在交互操作上并不友好,也存在或多或少的bugs,需要改进。</p>
<p>模拟进度条的实现思路是,使用一个span标签及其两给伪元素来完成进度条的模拟,其中,span主标签充当底轨,伪元素一个做进度指示,另一个做滑块。进度指示和滑块通过CSS变量获得状态的实时变更。此前的成品问题在于,进度条轨道偏细的话,拖曳操作容易失效,会拖的话还能将进度指示和滑块拖出轨道以外。这些问题都需要解决,方法简述如下:</p>
<p>设置一个 mDrag 布尔变量,默认值为假,鼠标单击滑块(或轨道)时令其为真,滑块此时处于可拖曳的状态。就是说,要拖曳滑块,先按下鼠标键,且必须是在滑块(或轨道)上按下才准许。</p>
<p>拖曳过程我们用鼠标指针的移动事件来完成。拖曳交互动作,鼠标指针可能会滑出轨道之外,特别是轨道不够粗时常会发生,为此,我们另辟蹊径,onmousemove事件不交给进度条元素,而是其父元素,这样就可以大大扩展鼠标指针的有效拖曳的范围,拖曳功能将和range差不多。</p>
<p>mDrag 变量要适时变回假,否则任何时候鼠标指针滑过进度条轨道都可以拖曳滑块。进度条的载体即父元素范围可能还不能满足需要,为此我们可以扩展到整个文档,将onmouseup事件交由document处理,mDrag变量是自定义的变量,应该不太可能引发冲突。</p>
<p>我们还应考虑轨道上的点击事件,为此,还要给轨道一个 onclick 事件,该事件要做的事情和onmousemove事件相同,但要独立存在。为此,我们需要将滑块和进度指示状态的变更封装成一个函数提供给两个鼠标事件使用。下面是效果和实现代码:</p>
<p><br></p>
<div id="mplayer"><span id="prog"></span></div>
<p><br></p>
<pre>
<style>
<span style='color: green'>/* 播放器父元素 */</span>
<span style='color: red;'>#mplayer </span>{
<span style='color: blue;'>margin</span>: 100px auto 0;
<span style='color: blue;'>position</span>: relative;
<span style='color: blue;'>width</span>: 200px;
<span style='color: blue;'>display</span>: grid;
<span style='color: blue;'>place-items</span>: center;
<span style='color: blue;'>padding</span>: 20px 0 20px 0;
<span style='color: blue;'>border</span>: 1px solid tan;
}
<span style='color: green'>/* 进度条主元素 <span style='color: blue;'></span>: 模拟进度条底轨 */</span>
<span style='color: red;'>#prog </span>{
<span style='color: blue;'>--xx</span>: 0px;
<span style='color: blue;'>width</span>: 100%;
<span style='color: blue;'>height</span>: 4px;
<span style='color: blue;'>background</span>: lightgray;
<span style='color: blue;'>position</span>: absolute;
<span style='color: blue;'>display</span>: grid;
<span style='color: blue;'>place-items</span>: center;
}
<span style='color: green'>/* 进度条伪元素总设定 */</span>
<span style='color: red;'>#<span style='color: blue;'>prog</span>:<span style='color: blue;'></span>:before, #<span style='color: blue;'>prog</span>:<span style='color: blue;'></span>:after </span>{
<span style='color: blue;'>position</span>: absolute; <span style='color: blue;'>content</span>: <span style='color: magenta'>''</span>;
}
<span style='color: green'>/* 进度条伪元素一 <span style='color: blue;'></span>: 模拟进度指示 */</span>
<span style='color: red;'>#<span style='color: blue;'>prog</span>:<span style='color: blue;'></span>:before </span>{
<span style='color: blue;'>left</span>: 0;
<span style='color: blue;'>width</span>: var(--xx);
<span style='color: blue;'>height</span>: 100%;
<span style='color: blue;'>border-radius</span>: 6px;
<span style='color: blue;'>background</span>: teal;
}
<span style='color: green'>/* 进度条伪元素二 <span style='color: blue;'></span>: 模拟滑块 */</span>
<span style='color: red;'>#<span style='color: blue;'>prog</span>:<span style='color: blue;'></span>:after </span>{
<span style='color: blue;'>left</span>: calc(var(--xx) - 5px);
<span style='color: blue;'>opacity</span>: .85;
<span style='color: blue;'>width</span>: 16px;
<span style='color: blue;'>height</span>: 16px;
<span style='color: blue;'>background</span>: radial-gradient(transparent 2px, teal 0, black);
<span style='color: blue;'>border-radius</span>: 50%;
<span style='color: blue;'>cursor</span>: pointer;
}
</style>
<<span style='color:darkred'>div</span> <span style='color: red'>id</span><span style='color: blue'>=</span><span style='color: magenta'>"mplayer"</span>>
<<span style='color:darkred'>span</span> <span style='color: red'>id</span><span style='color: blue'>=</span><span style='color: magenta'>"prog"</span>><<span style='color: darkred'>/span</span>>
<<span style='color: darkred'>/div</span>>
<script>
<span style='color: blue'>let</span> mDrag = false; <span style='color: green'>/* 拖曳标识 */</span><span style='color: green'>
<span style='color: green'>/* 进度条鼠标按下事件 : 可拖曳 */</span>
</span>prog.onmousedown = (e) => mDrag = true;<span style='color: green'>
<span style='color: green'>/* 文档鼠标松开 : 不可拖曳 */</span>
</span>document.onmouseup = () => mDrag = false;<span style='color: green'>
<span style='color: green'>/* 父元素 mplayer 鼠标移动事件 */</span>
</span>mplayer.onmousemove = (e) => {
<span style='color: blue'>if</span>(mDrag) moveBar(e.offsetX);
};<span style='color: green'>
<span style='color: green'>/* 进度条单击事件 */</span>
</span>prog.onclick = (e) => moveBar(e.offsetX);<span style='color: green'>
<span style='color: green'>/* 自定义函数 : 通过CSS变量 --xx 处理进度条状态 */</span>
</span>let moveBar = (xx) => {
<span style='color: blue'>if</span>(xx < 0) xx = 0;
<span style='color: blue'>if</span>(xx > prog.offsetWidth - 5) xx = prog.offsetWidth - 5;
prog.style.setProperty(<span style='color: magenta'>'--xx'</span>, xx + <span style='color: magenta'>'px'</span>);
}
</script>
</pre>
</div>
<script>
let mDrag = false;
prog.onmousedown = (e) => mDrag = true;
document.onmouseup = () => mDrag = false;
prog.onclick = (e) => moveBar(e.offsetX);
mplayer.onmousemove = (e) => {
if(mDrag) moveBar(e.offsetX);
};
let moveBar = (xx) => {
if(xx < 0) xx = 0;
if(xx > prog.offsetWidth - 5) xx = prog.offsetWidth - 5;
prog.style.setProperty('--xx', xx + 'px');
}
</script> 一楼的效果演示,拖曳滑块时,鼠标指针只要不离开方框(即进度条的父元素或曰载体),拖曳都有效。可以在拖曳时有意让鼠标指针偏离进度条但不要偏离矩形方框(当然可以试一试)。
滑块在左右两侧会突破两头的边线一点点,这是刻意设计的:令滑块中心的(透明)圆点作为播放的起点。由于滑块是伪元素,它被视为主元素的一部分,所以,在两头,超出两头边线的部分能接受鼠标相关操作,这就是完善前模拟滑块可以拖动到左右超出进度条很长距离的原因(感兴趣的可以去19号插件看看,它提供的应用实例那里可以这么做)。完善后,这个问题不复存在,并且,我们在滑块边缘的鼠标操作仍然有效,交互的友好性得到提升。 拖动起来很方便,这个解说感觉好复杂啊。为了更好的体验效果,要付出那么多精力细调,真不容易。{:4_187:} 红影 发表于 2022-11-28 18:47
拖动起来很方便,这个解说感觉好复杂啊。为了更好的体验效果,要付出那么多精力细调,真不容易。
编程事无巨细都得弄好 马黑黑 发表于 2022-11-28 19:01
编程事无巨细都得弄好
这种精神值得我等学习{:4_187:} 红影 发表于 2022-11-28 19:58
这种精神值得我等学习
做任何事情其实都一样,差不多 马黑黑 发表于 2022-11-28 20:55
做任何事情其实都一样,差不多
嗯,精益求精才能得到好结果。 红影 发表于 2022-11-28 21:37
嗯,精益求精才能得到好结果。
也不一定是精益求精,更多的是抓虫 马黑黑 发表于 2022-11-28 21:46
也不一定是精益求精,更多的是抓虫
这也是精益求精的内容之一啊。 红影 发表于 2022-11-28 22:02
这也是精益求精的内容之一啊。
脸红{:5_111:} 马黑黑 发表于 2022-11-28 22:02
脸红
自己付出的努力,有什么脸红的{:4_173:} 红影 发表于 2022-11-28 22:34
自己付出的努力,有什么脸红的
不红不正常 哪怕一个小小的动作,却要付出如此之多的努力,真是要把任何事做到尽善尽美,精益求精。
看来,小马是个追求完美的人{:4_199:} 马黑黑 发表于 2022-11-28 23:13
不红不正常
应是因为自豪而红的吧{:4_173:} 红影 发表于 2022-11-29 10:47
应是因为自豪而红的吧
不好意思
樵歌 发表于 2022-11-29 08:05
哪怕一个小小的动作,却要付出如此之多的努力,真是要把任何事做到尽善尽美,精益求精。
看来,小马是个追 ...
果酱了 马黑黑 发表于 2022-11-29 12:07
不好意思
还是太谦虚的缘故,所以脸皮薄{:4_173:} 红影 发表于 2022-11-29 13:20
还是太谦虚的缘故,所以脸皮薄
不是,是猪脸皮吃得少了 马黑黑 发表于 2022-11-29 18:21
不是,是猪脸皮吃得少了
猪头肉就完了,还猪脸皮{:4_173:} 红影 发表于 2022-11-29 20:02
猪头肉就完了,还猪脸皮
猪皮脸老贵了,比猪头肉贵得多