svg自定义路径进度条播放器(预览版)
<style>#mplayer { width: 200px; height: 80px; }
#mplayer text { user-select: none; }
#sPath { cursor: pointer; }
#progress { pointer-events: none; }
#btnPlay:hover { cursor: pointer; filter: brightness(1.2); }
</style>
<svg id="mplayer">
<path id="sPath" d="M10,10 V70 H190 V10" fill="none" stroke="lightgreen" stroke-width="4"></path>
<circle id="progress" cx="10" cy="10" r="5" fill="green" />
<g transform="translate(35,40)" fill="green">
<text id="cu" x="0" y="0">00:00</text>
<image id="btnPlay" x="50" y="-22" width="32" height="32" xlink:href="https://638183.freep.cn/638183/t23/btn/play.png"></image>
<text id="du" x="90" y="0">00:00</text>
</g>
</svg>
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1891331204" loop autoplay></audio>
<script>
let posAr = [];
let len = sPath.getTotalLength();
let playImg = 'https://638183.freep.cn/638183/t23/btn/play.png',
pauseImg = 'https://638183.freep.cn/638183/t23/btn/pause.png';
let toMin = (val) => { if (!val) return '00:00'; val = Math.floor(val); let min = parseInt(val / 60), sec = parseFloat(val % 60); if (min < 10) min = '0' + min; if (sec < 10) sec = '0' + sec; return min + ':' + sec; };
let mState = () => aud.paused ? btnPlay.setAttribute('xlink:href',playImg) : btnPlay.setAttribute('xlink:href',pauseImg);
let getMinItem = (ar) => {
ar.sort((a,b) => a - b);
return ar;
};
for(i = 0; i < len; i ++) {
posAr.push(sPath.getPointAtLength(i));
}
sPath.onclick = (e) => {
let ex = Math.round(e.offsetX), ey = Math.round(e.offsetY), yAr = [];
for(i = 0; i < len; i ++) {
let px = Math.round(posAr.x), py = Math.round(posAr.y);
if(Math.abs(ex-px) <= 4) {
yAr.push();
}
}
aud.currentTime = getMinItem(yAr) * aud.duration / len;
};
aud.addEventListener('timeupdate', () => {
let idx = Math.round(aud.currentTime * len / aud.duration);
progress.setAttribute('cx',posAr.x);
progress.setAttribute('cy',posAr.y);
cu.textContent = toMin(aud.currentTime);
du.textContent = toMin(aud.duration);
});
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
btnPlay.onclick = () => aud.paused ? aud.play() : aud.pause();
</script>
代码
<style>
#mplayer { width: 200px; height: 80px; }
#mplayer text { user-select: none; }
#sPath { cursor: pointer; }
#progress { pointer-events: none; }
#btnPlay:hover { cursor: pointer; filter: brightness(1.2); }
</style>
<svg id="mplayer">
<path id="sPath" d="M10,10 V70 H190 V10" fill="none" stroke="lightgreen" stroke-width="4"></path>
<circle id="progress" cx="10" cy="10" r="5" fill="green" />
<g transform="translate(35,40)" fill="green">
<text id="cu" x="0" y="0">00:00</text>
<image id="btnPlay" x="50" y="-22" width="32" height="32" xlink:href="https://638183.freep.cn/638183/t23/btn/play.png"></image>
<text id="du" x="90" y="0">00:00</text>
</g>
</svg>
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1891331204" loop autoplay></audio>
<script>
let posAr = [];
let len = sPath.getTotalLength();
let playImg = 'https://638183.freep.cn/638183/t23/btn/play.png',
pauseImg = 'https://638183.freep.cn/638183/t23/btn/pause.png';
let toMin = (val) => { if (!val) return '00:00'; val = Math.floor(val); let min = parseInt(val / 60), sec = parseFloat(val % 60); if (min < 10) min = '0' + min; if (sec < 10) sec = '0' + sec; return min + ':' + sec; };
let mState = () => aud.paused ? btnPlay.setAttribute('xlink:href',playImg) : btnPlay.setAttribute('xlink:href',pauseImg);
let getMinItem = (ar) => {
ar.sort((a,b) => a - b);
return ar;
};
for(i = 0; i < len; i ++) {
posAr.push(sPath.getPointAtLength(i));
}
sPath.onclick = (e) => {
let ex = Math.round(e.offsetX), ey = Math.round(e.offsetY), yAr = [];
for(i = 0; i < len; i ++) {
let px = Math.round(posAr.x), py = Math.round(posAr.y);
if(Math.abs(ex-px) <= 4) {
yAr.push();
}
}
aud.currentTime = getMinItem(yAr) * aud.duration / len;
};
aud.addEventListener('timeupdate', () => {
let idx = Math.round(aud.currentTime * len / aud.duration);
progress.setAttribute('cx',posAr.x);
progress.setAttribute('cy',posAr.y);
cu.textContent = toMin(aud.currentTime);
du.textContent = toMin(aud.duration);
});
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
btnPlay.onclick = () => aud.paused ? aud.play() : aud.pause();
</script>
相关解释:
这是用 svg 做的播放器,svg 代码在 第 09 至 17 行之间。第 10 行是播放器路径,这里使用昨天介绍的 svg path 之 M H V L 中的前三个指令 MHV 做成,比较简单。可以做更复杂的路径,不限于MHVL 指令。
第 12 至 15 行,用 g 标签分组,里面包含两个文本(text)标签,分别用于显示播放音乐的当前时间、音乐总时间,还有一个按钮标签,用 image 标签装载图片。g 标签的宽度由里面的元素决定,本例占用 130 px 的宽度,布局用 g 标签的转换属性 transform="translate(x,y)" 来实现,本例令其水平居中,所以 x 为 (200-130)÷ 2,y 则根据需要给出数值。
JS代码中,涉及一些算法,以及基于 audio 控件的监听与控制,以前的相关帖子都有过探讨,这里暂不做详细说明。 一些计算还没有仔细比较。比如g的占位,我是用了 getBBox 取得文本元素占位的大约尺寸,然后估摸着做。实际上,文本应事先定义 font 相关属性,并在 text 占位、image 占位之间做好安排,以确保按钮两边的文本与按钮的距离是等距的。 新的播放器出来了,等黑黑的实例{:4_189:} 小辣椒 发表于 2023-9-24 20:41
新的播放器出来了,等黑黑的实例
这个其实就是实例,只是有些东东还需要斟酌 马黑黑 发表于 2023-9-24 21:02
这个其实就是实例,只是有些东东还需要斟酌
小辣椒等直接套用{:4_189:} 小辣椒 发表于 2023-9-24 21:05
小辣椒等直接套用
那个还需要时间调试 俺飘过,等看精彩作业{:4_187:} 马黑黑 发表于 2023-9-24 21:32
那个还需要时间调试
不用急的,我还有许多没有完成的,明天开始又没有时间玩了 小辣椒 发表于 2023-9-24 22:02
不用急的,我还有许多没有完成的,明天开始又没有时间玩了
暂停一下也好 千羽 发表于 2023-9-24 21:42
俺飘过,等看精彩作业
谢等 马黑黑 发表于 2023-9-24 22:58
暂停一下也好
是的,慢慢来 黑黑辛苦,这么忙还不断退出新的东东{:4_199:} 小辣椒 发表于 2023-9-25 15:38
黑黑辛苦,这么忙还不断退出新的东东
没有退出,是推出{:4_170:} 小辣椒 发表于 2023-9-25 15:11
是的,慢慢来
慢工出细活 看着简单的一个带进度条播放器,实际要考虑的东东很多。欣赏黑黑新的播放器效果{:4_187:} 红影 发表于 2023-9-26 13:53
看着简单的一个带进度条播放器,实际要考虑的东东很多。欣赏黑黑新的播放器效果
这个精彩在于进度条的自定义,通过svg path标签的d属性设计进度条,设计自由度非常高 马黑黑 发表于 2023-9-26 17:57
这个精彩在于进度条的自定义,通过svg path标签的d属性设计进度条,设计自由度非常高
是啊,可以设计成任意不相交的路径的。 红影 发表于 2023-9-26 22:44
是啊,可以设计成任意不相交的路径的。
相交也没问题,就是点击相交点时播放进度可能不是预期的