马黑黑 发表于 2022-11-8 07:58

用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 08:00

本帖最后由 马黑黑 于 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:00

本帖最后由 马黑黑 于 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:00

本帖最后由 马黑黑 于 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。

本楼加上面的三楼,仅就播放控制器做相关解释。

山人 发表于 2022-11-8 09:24

纯音乐版代码
<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 09:29

黑黑朋友早晨好!歌曲很好听,没看到图。

小辣椒 发表于 2022-11-8 12:44

感觉这个有点难度的

马黑黑 发表于 2022-11-8 12:50

小辣椒 发表于 2022-11-8 12:44
感觉这个有点难度的

这个比用 div 或 span 做的简单

马黑黑 发表于 2022-11-8 12:51

梦油 发表于 2022-11-8 09:29
黑黑朋友早晨好!歌曲很好听,没看到图。

这是白板示范

小辣椒 发表于 2022-11-8 12:51

马黑黑 发表于 2022-11-8 12:50
这个比用 div 或 span 做的简单

那得慢慢学习,会了就会操作了

马黑黑 发表于 2022-11-8 12:53

小辣椒 发表于 2022-11-8 12:51
那得慢慢学习,会了就会操作了

如果你觉得难,那是因为你的HTML5、CSS3和JS半生不熟造成的

小辣椒 发表于 2022-11-8 12:54

马黑黑 发表于 2022-11-8 12:53
如果你觉得难,那是因为你的HTML5、CSS3和JS半生不熟造成的

都是没有很牢固的掌握

马黑黑 发表于 2022-11-8 13:08

小辣椒 发表于 2022-11-8 12:54
都是没有很牢固的掌握

走马观花肯定会这样

红影 发表于 2022-11-8 13:57

记得以前学的时候,它是自己变色的,这个因为无法听歌,还看不到效果。现在这个能进度可控了,真好{:4_199:}

梦油 发表于 2022-11-8 15:11

马黑黑 发表于 2022-11-8 12:51
这是白板示范

知道了。谢谢!

马黑黑 发表于 2022-11-8 17:53

梦油 发表于 2022-11-8 15:11
知道了。谢谢!

不客气

马黑黑 发表于 2022-11-8 17:55

红影 发表于 2022-11-8 13:57
记得以前学的时候,它是自己变色的,这个因为无法听歌,还看不到效果。现在这个能进度可控了,真好{:4_199: ...

meter 比 progress 好的地方就是它能变色。不过,HTML5是预设 meter 做完成量展示,progress 做进度展现

红影 发表于 2022-11-8 19:25

马黑黑 发表于 2022-11-8 17:55
meter 比 progress 好的地方就是它能变色。不过,HTML5是预设 meter 做完成量展示,progress 做进度展现

看到了,这个还是能变色的{:4_204:}

马黑黑 发表于 2022-11-8 19:38

红影 发表于 2022-11-8 19:25
看到了,这个还是能变色的

progress标签可能更简单,之前应该介绍过

红影 发表于 2022-11-8 21:10

马黑黑 发表于 2022-11-8 19:38
progress标签可能更简单,之前应该介绍过

那个好像也是进度条。
页: [1] 2
查看完整版本: 用meter标签做可控进度音频播放器