likes
comments
collection
share

用HTML5实现一个播放器

作者站长头像
站长
· 阅读数 17

先看看最终效果

用HTML5实现一个播放器

页面实现

HTML

<audio src="./爱在西元前 - 周杰伦.mp3" controls="controls" autoplay="autoplay"></audio>
<div class="wrap">
    <ul class="lrc-list">
    </ul>
</div>

就这俩元素就够了,现在大多数播放器禁止自动播放,所以autoplay属性基本无效

.wrap包裹整个页面,里面通过伪元素实现背景

CSS

:root {
    --margin: 20px;
    --height: 40px;
    --src: url(https://tse1-mm.cn.bing.net/th/id/OIP-C.OsuHeUeUgSNozCxofEpIHQC7Es?pid=ImgDet&rs=1);
}

* {
    margin: 0;
    padding: 0;
}

body {
    background-color: #000;
    color: #777;
    font-size: 14px;
    text-align: center;
}

ul {
    list-style: none;
}

基础的重置样式,body设置字体样式,让子元素继承

audio {
    width: 100%;
    height: var(--height);
    opacity: .8;
}

.wrap {
    position: relative;
    /* 减去播放条和自身的margin */
    height: calc(100vh - var(--height) - var(--margin));
    overflow: hidden;
    margin-top: var(--margin);
}

用HTML5实现一个播放器

接下来加上背景图片

.wrap::before {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    opacity: .4;
    content: "";
    background: var(--src) no-repeat center bottom;
    background-size: contain;
}

用HTML5实现一个播放器

再加个背景模糊效果,为了展示才设置.lrc-list高度的,后面要去掉,让他自动撑开

.lrc-list {
    transition: .6s;
    height: calc(100vh - var(--height) - var(--margin));
    backdrop-filter: blur(5px);
}

li {
    height: 30px;
    line-height: 30px;
    transition: .6s;
}

.is-playing {
    transform: scale(1.22);
    color: #fff;
}

用HTML5实现一个播放器

JS

歌词

首先就是歌词部分,我到网上下载了一段歌词文件

    const lrc = `[00:31.65] 作词:周杰伦 作曲:周杰伦 
    [00:01.65]
[00:31.86]古巴比伦王颁布了汉摩拉比法典
`;

首先,观察他的特点

  • 每一行歌词,以换行结尾
  • ]分割了每一行歌词和时间的位置

那么接下来就是用合理的数据结构存储歌词了

先明确一下,我们能获取什么信息

播放的时候,audio元素,提供timeupdate时间,当时间变化时触发

这时可以获取audio.currentTime,也就是现在播放的时间

数据结构

  • 经过分析,那么现在可以选择数组里放入对象存储
  • 顺序按照歌词时间
const lrc = [
    {
        word: '...',
        time: ...
    }
]

这样就能描述每一份歌词了

接下来就是实现了

function parseLrc() {
    const lrc = `[00:31.65] 作词:周杰伦 作曲:周杰伦 
    [00:01.65]
[00:31.86]古巴比伦王颁布了汉摩拉比法典
`;

    const res = [];
    lrcArr = lrc.split('\n');

    lrcArr.forEach((lrcDetail, index) => {
        const lrc = lrcDetail.split(']'),
            time = parseTime(lrc[0].slice(1).trim()),
            word = lrc[1].trim();

        res[index] = { time, word };
    });

    console.log(res)
    return res;
}

根据换行分割成数组,遍历他,这里换成return map是一样的

再根据]分割时间和歌词

时间需要把他解析成秒

function parseTime(timeStr) {
    const timeArr = timeStr.split(':');
    return numFixed(+timeArr[0] * 60 + +timeArr[1]);
}

function numFixed(num, dec = 2) {
    return Math.round(num * 10 ** dec) / 10 ** dec;
}

因为toFixed部分情况不准,所以我自己写了四舍五入的函数

num = 3.105为例,四舍五入两位,结果应该是3.11才对

用HTML5实现一个播放器

使用我的函数就是3.105 * 100 = 310.5,再四舍五入就是311,然后除回去100 = 3.11

处理后的结果

用HTML5实现一个播放器

获取数据

const lrc = parseLrc(),
    oAudio = document.getElementsByTagName('audio')[0],
    oUl = document.getElementsByClassName('lrc-list')[0],
    wrapHeight = document.getElementsByClassName('wrap')[0].offsetHeight;

let oLis = [],
    showLrc = false,
    liHeight = 0,
    maxHeight = 0;

接下来把歌词添加到ul,并获取各项参数

function addLrc() {
    const oFrag = document.createDocumentFragment();
    for (let i = 0; i < lrc.length; i++) {
        const li = document.createElement('li');
        li.innerText = lrc[i].word;
        oFrag.appendChild(li);
    }

    oUl.appendChild(oFrag);
    oLis = oUl.children;
    // 歌词列表高度 - 外包装高度 用来判断播放完毕
    maxHeight = oUl.offsetHeight - wrapHeight;
    liHeight = oLis[0].offsetHeight;
}

用HTML5实现一个播放器

基础布局就实现了

事件处理

现在要在播放时处理歌词滚动

oAudio.addEventListener('timeupdate', e => {
        const curTime = oAudio.currentTime;
        document.getElementsByClassName('is-playing')[0]?.classList.toggle('is-playing');
        transformLrc(curTime);
    });
    
function transformLrc(curTime) {
    let index = getLrcIndex(curTime),
        offsetY = liHeight / 2 + liHeight * index - wrapHeight / 2;

    if (offsetY < 0) offsetY = 0;
    if (offsetY > maxHeight) offsetY = maxHeight;

    oLis[index].classList.toggle('is-playing');
    oUl.style.transform = `translateY(${-offsetY}px)`;
}

function getLrcIndex(curTime) {
    let index;
    for (let i = 0; i < lrc.length; i++) {
        if (lrc[i].time > curTime) {
            index = i - 1;
            if (index < 0) return 0;
            return index;
        }
    };

    const len = lrc.length - 1;
    if (curTime > lrc[len].time) return len;
}

getLrcIndex根据时间遍历数组,找到第一个比他大的对象,再减一使用上一个

offsetY根据每行歌词高度,乘以索引,再减去一半居中

减去wrapHeight / 2是为了在屏幕居中

每次切换歌词时,给对应的歌词那一行元素,加上播放类,切换显示

至此,基本功能全部完成

锦上添花

window.addEventListener('click', () => {
        oUl.style.opacity = showLrc ? 1 : 0;
        showLrc = !showLrc;
    });

window.addEventListener('keyup', (e) => {
    if (e.code === 'Space') {
        e.preventDefault()
        oAudio.paused
            ? oAudio.play()
            : oAudio.pause();
    }
});

window.addEventListener('keydown', (e) => {
    // 阻止按键造成页面滚动
    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
        e.preventDefault();
    }
});

添加点击页面隐藏歌词,空格键暂停播放事件