马黑PHP整站系统

集成快捷键的多曲播放器

位置: 首页 > 代码集锦[ 发布时间: 2025.2.23  作者: 马黑  阅读: 67 ]

效果

代码

<style>
	#mboard { margin-bottom: 170px; width: 500px; height: 40px; background: beige; border-radius: 8px; box-shadow: 2px 2px 4px rgba(0,0,0,.5); display: flex; justify-content: center; align-items: center; gap: 8px; position: relative; }
	#mboard:hover #mlist { opacity: 1; }
	#mboard img { width: 26px; cursor: pointer; }
	#mboard img:hover { filter: drop-shadow(1px 1px 1px rgba(0,0,0,.5)); }
	#tMsg1, #tMsg2 { width: 45px; font-size: 13px; text-align: center; }
	#volwrap { position: absolute; width: 60px; height: 40px; right: 75px; display: grid; place-items: center; background: none; }
	#volwrap:hover #volume { display: inline; }
	#btnMute:hover ~ #volwrap > #volume { display: inline; }
	#volume { position: absolute; width: 50px; height: 4px; opacity: .75; display: none; }
	#prog { --track: gray; --prog: red; --prg: 0%; width: 200px; height: 20px; cursor: pointer; background: linear-gradient(to right, var(--prog) var(--prg), var(--track) 0) no-repeat 0% 50%/100% 2px; }
	#mlist { position: absolute; top: 45px; width: 100%; height: 160px; padding: 12px; border-radius: 0px; overflow: auto; scrollbar-width: thin; background: inherit; column-count: 2; column-rule: 2px solid #eee; column-fill: auto; box-sizing: border-box; border: 1px solid gray; opacity: 0; transition: .7s; }
	.list { margin: 0 8px; padding: 0 0 0 30px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; line-height: 20px; text-align: left; position: relative; }
	.list::before { position: absolute; content: attr(data-idx); width: 30px; left: 0; }
	.list1 { color: black; cursor: pointer; }
	.list2 { color: red; cursor: default; }
	.list1:hover { color: red; }
</style>

<div id="mboard">
	<div id="mlist"></div>
	<img id="btnPrev" src="https://638183.freep.cn/638183/web/icon/prev.svg" title="前一曲 (Alt+↑)" alt="" />
	<img id="btnPlay" src="https://638183.freep.cn/638183/web/icon/play.svg" title="播放/暂停 (Alt+X)" alt="" />
	<img id="btnNext" src="https://638183.freep.cn/638183/web/icon/next.svg" title="下一曲 (Alt+↓)" alt="" />
	<span id="tMsg1">00:00</span>
	<span id="prog"></span>
	<span id="tMsg2">00:00</span>
	<img id="btnMute" src="https://638183.freep.cn/638183/web/icon/unmuted.svg" title="静音 (Alt+J)" alt="" />
	<img id="btnCirc" src="https://638183.freep.cn/638183/web/icon/circs.svg" title="随机循环 (Alt+S)" alt="" />
	<div id="volwrap"><input id="volume" type="range" min="0" max="1" step="0.1" value="1" /></div>
	<audio id="aud"></audio>
</div>

<script>
	
let lastVolume = 1, currentId = null, circ = true, muted = false, playAr = [], lists = [];

const musics = [
	['https://music.163.com/song/media/outer/url?id=1321297488','跟太阳系说再见'],
	['https://music.163.com/song/media/outer/url?id=1380590400','优雅的邂逅-致丁仪'],
	['https://music.163.com/song/media/outer/url?id=1406003054','逝去的时间(time is over)'],
	['https://music.163.com/song/media/outer/url?id=1421093308','人列计算机'],
	['https://music.163.com/song/media/outer/url?id=1482053654','地球反抗军'],
	['https://music.163.com/song/media/outer/url?id=1488362358','黑暗战役A'],
	['https://music.163.com/song/media/outer/url?id=1488360994','黑暗战役B'],
	['https://music.163.com/song/media/outer/url?id=1322066478','歌行者'],
	['https://music.163.com/song/media/outer/url?id=524406882','地下世界-Without Spiccato'],
	['https://music.163.com/song/media/outer/url?id=534254637','水滴-摧毁舰队+metal drums'],
	['https://music.163.com/song/media/outer/url?id=1384983167','为了生存-末日方舟开场曲'],
	['https://music.163.com/song/media/outer/url?id=1331220129','四维空间深不见底'],
	['https://music.163.com/song/media/outer/url?id=1363443031','章北海登上自然选择号'],
	['https://music.163.com/song/media/outer/url?id=2015042301','人生的旅途'],
	['https://music.163.com/song/media/outer/url?id=520502976','面壁者的沉思+CL'],
];

const btnImg = {
	play: 'https://638183.freep.cn/638183/web/icon/play.svg',
	pause: 'https://638183.freep.cn/638183/web/icon/pause.svg',
	unmute: 'https://638183.freep.cn/638183/web/icon/unmuted.svg',
	mute: 'https://638183.freep.cn/638183/web/icon/muted.svg',
	prev: 'https://638183.freep.cn/638183/web/icon/prev.svg',
	next: 'https://638183.freep.cn/638183/web/icon/next.svg',
	circs: 'https://638183.freep.cn/638183/web/icon/circs.svg',
	circ1: 'https://638183.freep.cn/638183/web/icon/circ1.svg',
};

const setVolume = (val) => Math.min(1, Math.max(0, val));
const scrollX = (elm, x) => {
	if (elm.scrollWidth < elm.offsetWidth) return;
	elm.scroll({left: x, top: 0, hehavior: 'smooth'});
};

const ranAr = (total) => {
	let ar = Array(total).fill().map((_,key) => key);
	ar.sort(() => 0.5 - Math.random());
	return ar;
};

playAr = ranAr(musics.length);

musics.forEach((item,key) => {
	let p = document.createElement('p');
	p.className = 'list list1';
	p.dataset.idx = key + 1;
	p.innerText = p.title = item[1];
	p.onclick = () => mplay(key);
	mlist.appendChild(p);
	lists.push(p);
});

const mplay = (idx = null) => {
	if(musics.length === 0 || (idx !== null && idx === currentId)) return;
	if(idx === null) {
		if(playAr.length === 0) playAr = ranAr(musics.length);
		let tmpIdx = Math.floor(Math.random() * playAr.length);
		idx = playAr[tmpIdx];
		playAr.splice(tmpIdx, 1);
	}
	aud.src = musics[idx][0];
	if(idx !== null && idx !== currentId) aud.play();
	lists[idx].className = 'list list2';
	if(currentId !== null) lists[currentId].className = 'list list1';
	currentId = idx;
	scrollX(mlist, lists[currentId].offsetLeft - 22);
};

const setMute = () => {
	if(lastVolume === 0) return;
	muted = !muted;
	muted ? aud.volume = 0 : aud.volume = lastVolume;
};

const s2m = (seconds) => {
    const secs = Math.floor(seconds || 0);
    return `${String(secs/60|0).padStart(2,'0')}:${String(secs%60).padStart(2,'0')}`;
};

const mState = () => {
	btnPlay.src = aud.paused ? btnImg.play : btnImg.pause;
	btnPlay.title = (aud.paused ? '播放' : '暂停') + ' (Alt+X)';
};

document.addEventListener('keydown', e => {
	if(!e.altKey) return;
	switch (e.keyCode) {
		case 38:
			btnPrev.click();
			break;
		case 40:
			btnNext.click();
			break;
		case 74:
			setMute();
			break;
		case 83:
			btnCirc.click();
			break;
		case 88:
			btnPlay.click();
			break;
		case 187: case 107:
			aud.volume = setVolume(aud.volume + 0.1);
			lastVolume = aud.volume;
			break;
		case 189: case 109:
			aud.volume = setVolume(aud.volume - 0.1);
			lastVolume = aud.volume;
			break;
		default:
			return;
	}
});

aud.onplaying = aud.onpause = () => mState();
aud.onended = () => { circ ? mplay() : aud.play(); };

aud.ontimeupdate = () => {
	prog.style.setProperty('--prg', aud.currentTime/aud.duration*100 +'%');
	tMsg1.innerText = s2m(aud.currentTime);
	tMsg2.innerText = s2m(aud.duration);
};

aud.onvolumechange = () => {
	btnMute.src = aud.volume === 0 ? btnImg.mute : btnImg.unmute;
	volume.value = aud.volume;
}

btnPlay.onclick = () => aud.paused ? aud.play() : aud.pause();
btnPrev.onclick = () => { if(currentId > 0) mplay(currentId - 1); };
btnNext.onclick = () => { if(currentId < musics.length - 1) mplay(currentId + 1); };
btnMute.onclick = () => setMute();

btnCirc.onclick = () => {
	circ = !circ;
	btnCirc.src = circ ? btnImg.circs : btnImg.circ1;
	btnCirc.title = (circ ? '随机循环' : '单曲循环') + ' (Alt+S)';
};

volume.onchange = () => aud.volume = lastVolume = volume.value;
prog.onclick = (e) => aud.currentTime = e.offsetX * aud.duration / prog.offsetWidth;
prog.onmousemove = (e) => prog.title = s2m(e.offsetX * aud.duration / prog.offsetWidth);
volwrap.onmousemove = () => volwrap.title = '音量 : ' + volume.value + ' (Alt++/-)';
mboard.onmouseenter = () => scrollX(mlist, lists[currentId].offsetLeft - 22);

mlist.onwheel = (e) => {
	const delta = Math.abs(e.deltaX) > 0 ? e.deltaX : e.deltaY;
	mlist.scrollLeft += delta * 1.5;
	e.preventDefault();
};

mplay();

</script>

前一篇: 集成快捷键的单曲播放器
下一篇: DeepSeek用canvas制作的石英钟

发表评论:

       

评论列表 [2条]

#2 | 悄然 于 2025-2-23 10:16 发布: 复杂对应的是功能强大~~Alt+↓↑是上一曲下一曲,Alt+S是单曲循环~~选的十几首音乐也超赞。。慢慢听。。

#1 | 飞飞 于 2025-2-23 10:12 发布: 多曲的出来了,除了之前四个键盘控制,还多了前一曲,后一曲。。。随机循环,单曲循环。。。感觉好复杂啊。。

Copyright © 2023 All Right Reserved 马黑PHP文章管理整站系统v1.8
联系我们: gxblk@163.com