马黑黑 发表于 2023-9-24 20:17

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>

马黑黑 发表于 2023-9-24 20:18

代码

<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>

马黑黑 发表于 2023-9-24 20:29

相关解释:

这是用 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 控件的监听与控制,以前的相关帖子都有过探讨,这里暂不做详细说明。

马黑黑 发表于 2023-9-24 20:38

一些计算还没有仔细比较。比如g的占位,我是用了 getBBox 取得文本元素占位的大约尺寸,然后估摸着做。实际上,文本应事先定义 font 相关属性,并在 text 占位、image 占位之间做好安排,以确保按钮两边的文本与按钮的距离是等距的。

小辣椒 发表于 2023-9-24 20:41

新的播放器出来了,等黑黑的实例{:4_189:}

马黑黑 发表于 2023-9-24 21:02

小辣椒 发表于 2023-9-24 20:41
新的播放器出来了,等黑黑的实例
这个其实就是实例,只是有些东东还需要斟酌

小辣椒 发表于 2023-9-24 21:05

马黑黑 发表于 2023-9-24 21:02
这个其实就是实例,只是有些东东还需要斟酌

小辣椒等直接套用{:4_189:}

马黑黑 发表于 2023-9-24 21:32

小辣椒 发表于 2023-9-24 21:05
小辣椒等直接套用

那个还需要时间调试

千羽 发表于 2023-9-24 21:42

俺飘过,等看精彩作业{:4_187:}

小辣椒 发表于 2023-9-24 22:02

马黑黑 发表于 2023-9-24 21:32
那个还需要时间调试

不用急的,我还有许多没有完成的,明天开始又没有时间玩了

马黑黑 发表于 2023-9-24 22:58

小辣椒 发表于 2023-9-24 22:02
不用急的,我还有许多没有完成的,明天开始又没有时间玩了

暂停一下也好

马黑黑 发表于 2023-9-24 22:59

千羽 发表于 2023-9-24 21:42
俺飘过,等看精彩作业

谢等

小辣椒 发表于 2023-9-25 15:11

马黑黑 发表于 2023-9-24 22:58
暂停一下也好

是的,慢慢来

小辣椒 发表于 2023-9-25 15:38

黑黑辛苦,这么忙还不断退出新的东东{:4_199:}

马黑黑 发表于 2023-9-25 19:04

小辣椒 发表于 2023-9-25 15:38
黑黑辛苦,这么忙还不断退出新的东东

没有退出,是推出{:4_170:}

马黑黑 发表于 2023-9-25 19:05

小辣椒 发表于 2023-9-25 15:11
是的,慢慢来

慢工出细活

红影 发表于 2023-9-26 13:53

看着简单的一个带进度条播放器,实际要考虑的东东很多。欣赏黑黑新的播放器效果{:4_187:}

马黑黑 发表于 2023-9-26 17:57

红影 发表于 2023-9-26 13:53
看着简单的一个带进度条播放器,实际要考虑的东东很多。欣赏黑黑新的播放器效果

这个精彩在于进度条的自定义,通过svg path标签的d属性设计进度条,设计自由度非常高

红影 发表于 2023-9-26 22:44

马黑黑 发表于 2023-9-26 17:57
这个精彩在于进度条的自定义,通过svg path标签的d属性设计进度条,设计自由度非常高

是啊,可以设计成任意不相交的路径的。

马黑黑 发表于 2023-9-26 22:49

红影 发表于 2023-9-26 22:44
是啊,可以设计成任意不相交的路径的。

相交也没问题,就是点击相交点时播放进度可能不是预期的
页: [1] 2 3 4 5
查看完整版本: svg自定义路径进度条播放器(预览版)