学会歌词滚动实现,让你的音乐页面更炫酷!

2024-08-20 11:09:35 浏览数 (2)

实现效果

歌词滚动.gif歌词滚动.gif

实现分析

解析歌词字符串转化为对象

代码语言:javascript复制
var lyrics = `[00:00.000]漂洋过海来看你-孙露
[00:09.480]词:李宗盛
[00:18.960]曲:李宗盛
[00:28.440]为你我用了半年的积蓄
[00:31.980]漂洋过海的来看你
......`;

function parseLyrics() {
    let Things = lyrics.split('n');
    let result = [];
    for (var i = 0; i < Things.length; i  ) {
        let str = Things[i].split(']');
        let timeStr = str[0].substring(1);
        let timeArr = timeStr.split(':');
        let time =  timeArr[0] * 60    timeArr[1];
        let obj = {
            time: time,
            words: str[1],
        };
        result.push(obj);
    }
    return result;
}
  • parseLyrics 函数通过对输入的歌词字符串进行分割和处理,将时间部分转换为以秒为单位的数值,并与对应的歌词文本组合成对象,存储在 result 数组中。这样的对象形式方便后续根据时间进行查找和操作。

根据歌词对象和播放器当前播放时间计算当前播放到的歌词

代码语言:javascript复制

function subscript() {
    let curTime = doms.audio.currentTime;
    for (var i = 0; i < lyricsData.length; i  ) {
        if (curTime < lyricsData[i].time) {
            return i - 1;
        }
    }
    return lyricsData.length - 1;
}
  • subscript 函数接受当前音频的播放时间 curTime ,通过遍历歌词对象数组,比较时间来确定当前播放到的歌词索引。

把解析的歌词内容呈现到页面

代码语言:javascript复制
function initLyricsHtml() {
    let frag = document.createDocumentFragment();
    for (var i = 0; i < lyricsData.length; i  ) {
        let li = document.createElement('li');
        li.textContent = lyricsData[i].words;
        frag.appendChild(li);
    }
    doms.ul.appendChild(frag);
}
  • initLyricsHtml 函数创建 li 元素并填充歌词文本,使用 DocumentFragment 来优化性能。DocumentFragment 是一个轻量级的文档片段,它可以容纳多个 DOM 节点,当将其添加到文档中时,只会发生一次页面重绘或回流,从而提高性能。最后将所有 li 元素添加到 ul 中展示歌词。

根据当前播放到的歌词计算出 ul 向上偏移的位置以及当前高亮歌词

QQ截图20240819143328.pngQQ截图20240819143328.png
2种边界情况.png2种边界情况.png
代码语言:javascript复制
function setOffset() {
    let index = subscript();
    let offset = liHeight * index   liHeight / 2 - containerHeight / 2;
    if (offset < 0) {
        offset = 0;
    }
    if (offset > maxOffset) {
        offset = maxOffset;
    }
    doms.ul.style.transform = `translateY(-${offset}px)`;

    let liActive = doms.ul.querySelector('.active');
    if (liActive) {
        liActive.classList.remove('active');
    }
    let li = doms.ul.children[index];
    if (li) {
        li.classList.add('active');
    }
}
  • 首先计算了一些与页面布局相关的高度值,如容器高度、每行歌词高度和最大偏移量。
  • setOffset 函数根据当前播放的歌词索引计算 ul 的偏移量,确保当前歌词在容器中显示合适,并设置当前高亮的歌词行。使用 translateY 而不是 top 主要是因为 translateY 是通过 CSS 变换来实现元素的位移,它在性能上通常比直接修改 top 属性更优。translateY 可以利用硬件加速,并且不会引起页面的重布局,从而使页面的渲染更加流畅。

给播放器添加监听器监控 timeupdate 事件,执行 ul 偏移

代码语言:javascript复制
doms.audio.addEventListener('timeupdate',setOffset);
  • doms.audio.addEventListener('timeupdate', setOffset); 这行代码使得在音频播放时间不断更新时,能够及时调用 setOffset 函数来调整歌词的显示位置和高亮状态。

完整的代码

index.html

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>歌词滚动</title>
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
    <link rel="stylesheet" href="index.css" />
</head>
<body>
    <audio controls src="./孙露-漂洋过海来看你.mp3"></audio>
    <div class="container">
        <ul class="lrc-list"></ul>
    </div>
    <script src="data.js"></script>
    <script src="index.js"></script>
</body>
</html>

index.css

代码语言:javascript复制
*{
    margin: 0;
    padding: 0;
}
body{
    background: #000;
    color: #666;
    text-align: center;
}
audio{
    width: 450px;
    margin: 30px 0;
}
.container{
    height: 420px;
    overflow: hidden;
}

.container ul{
    transition: 0.6s;
}

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

.container li.active{
    color:#fff;
    transform: scale(1.3);
} 

index.js

代码语言:javascript复制
function parseLyrics() {
    let Things = lyrics.split('n');
    let result = [];
    for (var i = 0; i < Things.length; i  ) {
        let str = Things[i].split(']');
        let timeStr = str[0].substring(1);
        let timeArr = timeStr.split(':');
        let time =  timeArr[0] * 60    timeArr[1];
        let obj = {
            time: time,
            words: str[1],
        };
        result.push(obj);
    }
    return result;
}

let doms = {
    audio: document.querySelector('audio'),
    ul: document.querySelector('.container ul'),
    container: document.querySelector('.container'),

}

let lyricsData = parseLyrics();

function subscript() {
    let curTime = doms.audio.currentTime;
    for (var i = 0; i < lyricsData.length; i  ) {
        if (curTime < lyricsData[i].time) {
            return i - 1;
        }
    }
    return lyricsData.length - 1;
}

function initLyricsHtml() {
    let frag = document.createDocumentFragment();
    for (var i = 0; i < lyricsData.length; i  ) {
        let li = document.createElement('li');
        li.textContent = lyricsData[i].words;
        frag.appendChild(li);
    }
    doms.ul.appendChild(frag);
}

initLyricsHtml();

let containerHeight = doms.container.clientHeight;
let liHeight = doms.ul.children[0].clientHeight;
let maxOffset = doms.ul.clientHeight - containerHeight;


function setOffset() {
    let index = subscript();
    let offset = liHeight * index   liHeight / 2 - containerHeight / 2;
    if (offset < 0) {
        offset = 0;
    }
    if (offset > maxOffset) {
        offset = maxOffset;
    }
    doms.ul.style.transform = `translateY(-${offset}px)`;

    let liActive = doms.ul.querySelector('.active');
    if (liActive) {
        liActive.classList.remove('active');
    }
    let li = doms.ul.children[index];
    if (li) {
        li.classList.add('active');
    }
}

doms.audio.addEventListener('timeupdate',setOffset);

0 人点赞