用meter标签做可控进度音频播放器
本帖最后由 马黑黑 于 2022-11-8 09:02 编辑 <br /><br /><style>#papa {
margin: 100px 0 0 calc(50% - 581px);
width: 1024px;
height: 640px;
background: gray;
display: grid;
place-items: center;
box-shadow: 3px 3px 20px #000;
user-select: none;
position: relative;
z-index: 1;
}
#mplayer {
position: absolute;
bottom: 20px;
grid-template-columns: auto auto auto;
gap: 6px;
display: grid;
place-items: center;
}
#btnplay {
margin-right: -4px;
width: 30px;
height: 30px;
color: red;
font: bold 30px/30px serif;
text-align: center;
cursor: pointer;
animation: rot 4s infinite linear;
animation-play-state: var(--state);
--state: paused;
}
#tmsg {
font: normal 16px sans-serif;
color: #eee;
}
#prog {
width: 200px;
height: 20px;
cursor: pointer;
}
#lrc {
--state: running;
--motion: cover1;
--tt: 5s;
position: absolute;
top: 20px;
font: bold 2.4em sans-serif;
color: hsl(240, 50%, 90%);
-webkit-background-clip: text;
filter: drop-shadow(1px 1px 2px hsla(30, 10%, 10%, .95));
}
#lrc::before {
position: absolute;
content: attr(data-lrc);
width: 20%;
height: 100%;
color: transparent;
overflow: hidden;
white-space: nowrap;
background: linear-gradient(180deg, hsla(240, 20%, 50%, .45), hsla(240, 50%, 60%, .75));
filter: inherit;
-webkit-background-clip: text;
animation: var(--motion) var(--tt) linear forwards;
animation-play-state: var(--state);
}
@keyframes cover1 { from { width: 0; } to { width: 100%; } }
@keyframes cover2 { from { width: 0; } to { width: 100%; } }
@keyframes rot { to { transform: rotate(1turn); } }
</style>
<div id="papa">
<div id="lrc" data-lrc="花潮论坛lrc在线">花潮论坛lrc在线</div>
<div id="mplayer">
<span id="btnplay">✲</span>
<meter id="prog" low="30" high="90" max="100" optimum="100" value="1"></meter>
<span id="tmsg">00:00 | 00:00</span>
</div>
</div>
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1977849891.mp3" loop autoplay></audio>
<script>
(function() {
let mKey = 0,
mSeek = false,
mFlag = true;
let lrcAr = [
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
];
btnplay.onclick = () => aud.paused ? aud.play() : aud.pause();
prog.onclick = (e) => {
aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth;
}
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
aud.addEventListener('seeked', () => calcKey());
aud.addEventListener('timeupdate', () => {
prog.value = aud.currentTime / aud.duration * 100;
tmsg.innerText = `${toMin(aud.currentTime)} | ${toMin(aud.duration)}`;
for (j = 0; j < lrcAr.length; j++) {
if (aud.currentTime >= lrcAr) {
if (mKey === j) showLrc(lrcAr);
else continue;
}
}
});
let mState = () => aud.paused ? (btnplay.style.setProperty('--state', 'paused'), lrc.style.setProperty('--state', 'paused')) : (btnplay.style.setProperty('--state', 'running'), lrc.style.setProperty('--state', 'running'));
let showLrc = (time) => {
let name = mFlag ? 'cover1' : 'cover2';
lrc.innerHTML = lrc.dataset.lrc = lrcAr;
lrc.style.setProperty('--motion', name);
lrc.style.setProperty('--tt', time + 's');
lrc.style.setProperty('--state', 'running');
mKey += 1;
mFlag = !mFlag;
};
let calcKey = () => {
for (j = 0; j < lrcAr.length; j++) {
if (aud.currentTime <= lrcAr) {
mKey = j - 1;
break;
}
}
if (mKey < 0) mKey = 0;
if (mKey > lrcAr.length - 1) mKey = lrcAr.length - 1;
let time = lrcAr - (aud.currentTime - lrcAr);
showLrc(time);
};
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;
};
})();
</script> 本帖最后由 马黑黑 于 2022-11-8 09:00 编辑
代码<style>
#papa {
margin: calc(50% - 581px);
width: 1024px;
height: 640px;
background: gray;
display: grid;
place-items: center;
box-shadow: 3px 3px 20px #000;
user-select: none;
position: relative;
z-index: 1;
}
#mplayer {
position: absolute;
bottom: 20px;
grid-template-columns: auto auto auto;
gap: 6px;
display: grid;
place-items: center;
}
#btnplay {
margin-right: -4px;
width: 30px;
height: 30px;
color: red;
font: bold 30px/30px serif;
text-align: center;
cursor: pointer;
animation: rot 4s infinite linear;
animation-play-state: var(--state);
--state: paused;
}
#tmsg {
font: normal 16px sans-serif;
color: #eee;
}
#prog {
width: 200px;
height: 20px;
cursor: pointer;
}
#lrc {
--state: running;
--motion: cover1;
--tt: 5s;
position: absolute;
top: 20px;
font: bold 2.4em sans-serif;
color: hsl(240, 50%, 90%);
-webkit-background-clip: text;
filter: drop-shadow(1px 1px 2px hsla(30, 10%, 10%, .95));
}
#lrc::before {
position: absolute;
content: attr(data-lrc);
width: 20%;
height: 100%;
color: transparent;
overflow: hidden;
white-space: nowrap;
background: linear-gradient(180deg, hsla(240, 20%, 50%, .45), hsla(240, 50%, 60%, .75));
filter: inherit;
-webkit-background-clip: text;
animation: var(--motion) var(--tt) linear forwards;
animation-play-state: var(--state);
}
@keyframes cover1 { from { width: 0; } to { width: 100%; } }
@keyframes cover2 { from { width: 0; } to { width: 100%; } }
@keyframes rot { to { transform: rotate(1turn); } }
</style>
<div id="papa">
<div id="lrc" data-lrc="花潮论坛lrc在线">花潮论坛lrc在线</div>
<div id="mplayer">
<span id="btnplay">✲</span>
<meter id="prog" low="30" high="90" max="100" optimum="100" value="1"></meter>
<span id="tmsg">00:00 | 00:00</span>
</div>
</div>
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1977849891.mp3" loop autoplay></audio>
<script>
(function() {
let mKey = 0,
mSeek = false,
mFlag = true;
let lrcAr = [
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
];
btnplay.onclick = () => aud.paused ? aud.play() : aud.pause();
prog.onclick = (e) => {
aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth;
}
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
aud.addEventListener('seeked', () => calcKey());
aud.addEventListener('timeupdate', () => {
prog.value = aud.currentTime / aud.duration * 100;
tmsg.innerText = `${toMin(aud.currentTime)} | ${toMin(aud.duration)}`;
for (j = 0; j < lrcAr.length; j++) {
if (aud.currentTime >= lrcAr) {
if (mKey === j) showLrc(lrcAr);
else continue;
}
}
});
let mState = () => aud.paused ? (btnplay.style.setProperty('--state', 'paused'), lrc.style.setProperty('--state', 'paused')) : (btnplay.style.setProperty('--state', 'running'), lrc.style.setProperty('--state', 'running'));
let showLrc = (time) => {
let name = mFlag ? 'cover1' : 'cover2';
lrc.innerHTML = lrc.dataset.lrc = lrcAr;
lrc.style.setProperty('--motion', name);
lrc.style.setProperty('--tt', time + 's');
lrc.style.setProperty('--state', 'running');
mKey += 1;
mFlag = !mFlag;
};
let calcKey = () => {
for (j = 0; j < lrcAr.length; j++) {
if (aud.currentTime <= lrcAr) {
mKey = j - 1;
break;
}
}
if (mKey < 0) mKey = 0;
if (mKey > lrcAr.length - 1) mKey = lrcAr.length - 1;
let time = lrcAr - (aud.currentTime - lrcAr);
showLrc(time);
};
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;
};
})();
</script>
本帖最后由 马黑黑 于 2022-11-8 08:36 编辑
较早前,介绍过 meter 标签:h5:meter标签简介 ,也用 meter 标签做过一个有趣的播放器:地球带月亮+meter播放器 。所以,这里就不对meter标签再重复介绍了,不了解 meter 标签的可以去看看简介,这里说一说我的实现思路和方法:
播放器仍然使用 id="mplayer" 的 div 盒子将按钮、进度条(即meter)、播放时间信息组织起来,以便在帖子中定位播放器时只需在CSS中操作 #mplayer 选择器。分析一下 #mplayer 选择器代码:
#mplayer {
position: absolute;
bottom: 20px;
display: grid;
grid-template-columns: auto auto auto;
gap: 6px;
place-items: center;
}
#mplayer 使用网格布局(grid),设置为一行(行不做设定即为默认一行)、三列(grid-template-columns),列宽均设为 auto 以令子元素按自己的实际尺寸占位;网格内元素间的距离 gap 为 6px,不让里面的可视内容挤在一起;放置子元素的对齐方式是 center(绝对居中)。
整个音频控制器 HTML 代码如下:
<div id="mplayer">
<span id="btnplay">✲</span>
<meter id="prog" low="30" high="90" max="100" optimum="100" value="1"></meter>
<span id="tmsg">00:00 | 00:00</span>
</div>
mplayer之下,共三个标签,一、三位 span 标签,分别用做文本按钮和播放时间信息,中间的就是 meter 标签,用做可控进度条。这些标签都有CSS样式制定:
① 按钮
#btnplay {
margin-right: -4px; /* 微调位置 */
width: 30px;
height: 30px;
color: red;
font: bold 30px/30px serif; /* 第二个 30px 确保文本垂直居中 */
text-align: center;/* 文本水平居中 */
cursor: pointer;
animation: rot 4s infinite linear; /* 运行关键帧动画 */
animation-play-state: var(--state); /* 动画初始状态 :根据变量动态确定 */
}
② 进度条 :高宽可根据需要设定
#prog {
width: 200px;
height: 20px;
cursor: pointer;
}
③ 播放时间信息 :字体及文本颜色
#tmsg {
font: normal 16px sans-serif;
color: #eee;
}
播放控制器在CSS和HTML层面并不复杂,唯 animation-play-state: var(--state) 的动画播放状态可能有些朋友会感到迷惑,这里略微解释一下。animation-play-state 是 CSS 控制 animation 的运行状态,值可为 paused(暂停)和 running(运行),我通过变量 var(--state) 来控制它,--state 变量在 JS 中动态管控,以便相应关键帧动画的播放与暂停。CSS中其实也应该设定这个值,放在 #btnplay选择器中,写为 --state: paused; 或 --state: running; ,这样会更严谨一些(#lrc 选择器也用到这个变量,也应该根据需要设定这个值)。
本帖最后由 马黑黑 于 2022-11-8 08:58 编辑
JS 对 meter 的控制,可将 meter 视为一个普通元素,同样能够在移动和点击中获得鼠标在其上的 offseX 值(也就是当前点击的点离meter左端是多少个像素距离),从而可以实现进度调控:
prog.onclick = (e) => {
aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth;
}
audio 播放器提供的 currentTime 是当前播放到的时间信息,duration 是当前音乐的总播放时长。当我们点击 meter 进度条时,是想通过 offsetX 值来控制 audio 当前播放到的时间,所以有以上算法,即,(音乐总时长 × offsetX) ÷ meter宽度。
audio 播放是驱动 meter 进度条的计算放在 audio 对 timeupdate 监听事件中:
aud.addEventListener('timeupdate', () => {
prog.value = aud.currentTime / aud.duration * 100;
//其他代码
});
meter 的 value 值表示 meter 的进度,它用百分比数值(但不要%号),所以播放进度的计算要乘上100。
本楼加上面的三楼,仅就播放控制器做相关解释。
纯音乐版代码
<style>
#papa {
margin: 100px 0 0 calc(50% - 581px);
width: 1024px;
height: 640px;
background: gray;
display: grid;
place-items: center;
box-shadow: 3px 3px 20px #000;
user-select: none;
position: relative;
z-index: 1;
}
#mplayer {
position: absolute;
bottom: 20px;
grid-template-columns: auto auto auto;
gap: 6px;
display: grid;
place-items: center;
}
#btnplay {
--state: paused;
margin-right: -4px;
width: 30px;
height: 30px;
color: red;
font: bold 30px/30px serif;
text-align: center;
cursor: pointer;
animation: rot 4s infinite linear;
animation-play-state: var(--state);
}
#tmsg {
font: normal 16px sans-serif;
color: #eee;
}
#prog {
width: 200px;
height: 20px;
cursor: pointer;
}
@keyframes cover1 { from { width: 0; } to { width: 100%; } }
@keyframes cover2 { from { width: 0; } to { width: 100%; } }
@keyframes rot { to { transform: rotate(1turn); } }
</style>
<div id="papa">
<div id="mplayer">
<span id="btnplay">✲</span>
<meter id="prog" low="30" high="90" max="100" optimum="100" value="1"></meter>
<span id="tmsg">00:00 | 00:00</span>
</div>
</div>
<audio id="aud" src="音频地址" loop autoplay></audio>
<script>
(function() {
btnplay.onclick = () => aud.paused ? aud.play() : aud.pause();
prog.onclick = (e) => {
aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth;
}
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
aud.addEventListener('seeked', () => aud.play());
aud.addEventListener('timeupdate', () => {
prog.value = aud.currentTime / aud.duration * 100;
tmsg.innerText = `${toMin(aud.currentTime)} | ${toMin(aud.duration)}`;
});
let mState = () => btnplay.style.setProperty('--state', aud.paused ? 'paused' : 'running');
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;
};
})();
</script>
黑黑朋友早晨好!歌曲很好听,没看到图。 感觉这个有点难度的 小辣椒 发表于 2022-11-8 12:44
感觉这个有点难度的
这个比用 div 或 span 做的简单 梦油 发表于 2022-11-8 09:29
黑黑朋友早晨好!歌曲很好听,没看到图。
这是白板示范 马黑黑 发表于 2022-11-8 12:50
这个比用 div 或 span 做的简单
那得慢慢学习,会了就会操作了 小辣椒 发表于 2022-11-8 12:51
那得慢慢学习,会了就会操作了
如果你觉得难,那是因为你的HTML5、CSS3和JS半生不熟造成的 马黑黑 发表于 2022-11-8 12:53
如果你觉得难,那是因为你的HTML5、CSS3和JS半生不熟造成的
都是没有很牢固的掌握 小辣椒 发表于 2022-11-8 12:54
都是没有很牢固的掌握
走马观花肯定会这样 记得以前学的时候,它是自己变色的,这个因为无法听歌,还看不到效果。现在这个能进度可控了,真好{:4_199:} 马黑黑 发表于 2022-11-8 12:51
这是白板示范
知道了。谢谢! 梦油 发表于 2022-11-8 15:11
知道了。谢谢!
不客气 红影 发表于 2022-11-8 13:57
记得以前学的时候,它是自己变色的,这个因为无法听歌,还看不到效果。现在这个能进度可控了,真好{:4_199: ...
meter 比 progress 好的地方就是它能变色。不过,HTML5是预设 meter 做完成量展示,progress 做进度展现 马黑黑 发表于 2022-11-8 17:55
meter 比 progress 好的地方就是它能变色。不过,HTML5是预设 meter 做完成量展示,progress 做进度展现
看到了,这个还是能变色的{:4_204:} 红影 发表于 2022-11-8 19:25
看到了,这个还是能变色的
progress标签可能更简单,之前应该介绍过 马黑黑 发表于 2022-11-8 19:38
progress标签可能更简单,之前应该介绍过
那个好像也是进度条。
页:
[1]
2