歌词滚动效果

知识点:

  • 引入css、图标和js文件的方法和顺序

  • audio音乐播放器,controls显示自带的播放组件,currentTime是当前播放时间

  • 动画时间transition:1s,需要添加到父组件上

  • 修改transform的translate值展现歌词滚动,使用scale(1.2)表示字体变大,这两个都不会直接操作dom树

  • 字符串分割split,字符串截取substring

  • 运算符将字符串转换为数字

  • 文档片段是脱离dom树的,document.createDocumentFragment()

  • 动态生成节点并添加document.createElement('li') ul.appendChild('li')

  • 添加与移除class样式 添加:ul.children[index].className='active'; 或者 ul.children[index].classList.add('active'); 移除:li.classList.remove('active');

  • 时间变化的事件监听关键词为:timeupdate

建立文件夹目录

其中assets文件夹下是歌曲的MP3
文件和一个标签页的图标;css文件夹下是该页面的样式;js文件夹下有两个js文件,data.js是歌曲的歌词lrc文件,index.js是滚动效果的实现方式;index.html是实现的主页面


布局静态页面

1. 引入图标和css文件

<link rel="shortcut icon" href="./assets/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="./css/index.css">

2. 引入js文件

js文件先后顺序一定是data在前,index在后,因为index需要使用到对歌词的处理

<script src="./js/data.js"></script>
<script src="./js/index.js"></script>

3. 页面布局

    <audio controls src="./assets/2863832879.mp3"></audio>
    //li通过js动态生成,也可以先写硬编码方便调试代码
    <div class="container">
        <ul class="lrc-list"></ul>
    </div>

下面为css样式

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

.container ul{
    transition: 0.6s;/*歌词跳转动画,从一个li到下一个li*/
    list-style: none;
}

.container li{
    height: 30px;
    line-height: 30px;
    transition: 0.6s;/*歌词样式变化动画,从li变化到.active*/
}

.container li.active{
    color: #fff;
    transform: scale(1.2);/*歌词变大1.2倍*/
}

样式效果大致如下:


4. 编写js文件
(1)处理歌词文件,将其从字符串格式转变为对象数组的形式方便操作。
定义一个parseLrc的函数用于处理歌词,该函数返回处理好的歌词数组。
我们先查看歌词字符串:

发现歌词是一行行组成,每一行可由]分为左右两边,左边是时间,右边是歌词。我们可以使用split函数,将字符串按行分割。再根据得到的数组将每一行按]分割为时间字符串和歌词,接下来解析时间字符串,先将分割好的时间字符串的[去掉,这里可以使用substring函数,然后定义一个parseTime函数,传入时间字符串,返回一个秒数,大致思路是使用split将传入的时间字符串分割,数组下标为0的为分数,下标为1的是秒数,可轻易计算出总的秒数返回。在得到秒和歌词后,将其组合成对象的形式填充进数组。具体代码如下:

//将歌词转变成对象数组
function parseLrc() {
    let lines = lrc.split('\n');
    let result = [];
    for (let i = 0; i < lines.length; i++) {
        const str = lines[i];
        const parts = str.split(']');
        const timeStr = parts[0].substring(1);
        const obj = {
            time: parseTime(timeStr),
            words: parts[1]
        }
        result.push(obj)
    }
    return result;
}
/**
 * 将时间字符串解析为秒
 * @param {*} timeStr 时间字符串
 * @returns 秒
 */
function parseTime(timeStr) {
    let parts = timeStr.split(":");
    return (+parts[0] * 60 + +parts[1]);
    // 通过运算符+可将字符串直接转换为数字
}
const lrcData = parseLrc();

(2)获取高亮歌词

先获取下面可能会用到的dom元素。

const doms = {
    audio: document.querySelector('audio'),
    ul: document.querySelector('.container ul'),
    container: document.querySelector('.container')
};

进行了歌词格式化后,我们已经有了一个歌词的对象数组,我们可以使用audio内置的currentTime获取当前播放的时间,将该时间与每个歌词的播放时间比较,将第一个当前播放时间大于歌词时间的下标取出,注意需要减1,此时需要注意边界值,在0秒的时候是没有高亮的,因此下标可以为-1;由于最后一句歌的下标减1将是倒数第二句,因此循环结束时还没有返回下标,就返回最后一句歌词的下标,具体代码如下:

/**
 * 播放器播放到第几秒的情况
 * 计算出在当前情况下,应该高亮显示的歌词下标
 */
function findIndex() {
    const currTime = doms.audio.currentTime;
    for (let i = 0; i < lrcData.length; i++) {
        if (currTime < lrcData[i].time) {
            return i - 1;
        }
    }
    return lrcData.length - 1;
}

(3)动态生成歌词列表

定义一个函数createLrcElements用于处理生成歌词列表,生成li,修改信息,添加到ul的子节点。

function createLrcElements() {
    for (let i = 0; i < lrcData.length; i++) {
        const li = document.createElement('li');
        li.textContent = lrcData[i].words;
        doms.ul.appendChild(li);
    }
}

其实上面的代码可以进行优化,因为ul.appendChild需要执行数组的长度的次数,每次添加都是在修改dom节点,可以先添加到脱离dom树的文档片段中,最后才批量插入。

function createLrcElements() {
    const frag = document.createDocumentFragment();//优化:使用文档片段,脱离dom树
    for (let i = 0; i < lrcData.length; i++) {
        const li = document.createElement('li');
        li.textContent = lrcData[i].words;
        frag.appendChild(li);
    }
    doms.ul.appendChild(frag);
}
createLrcElements();

(4)设置ul的偏移量

我们这里修改偏移量是使用transform,而不是使用margin等其他方法,因为transform的修改与渲染浏览器的主线程无关,可以减少资源浪费。那么怎么计算ul的偏移量呢?当歌词下标为index时,它在div中的高度应该为前面的li的高度值和再加上半个li的高度,然后减去div容器的高度的一半,最终得到的就是该变形高度。但是我们也需要注意边缘值,比如在第一句歌词时,如果按照上述算法,歌词的变形高度会是负值,因此我们需要进行判断,当变形高度为负数时,需要将变形的高度取0。同理,当变形高度大于最大偏移量(ul的高度减去容器的高度),需要将变形高度设置为最大偏移量。偏移量设置完后,需要将当前的播放的歌词样式修改为高亮,当然,在修改前需要将其他所有高亮的歌词的高亮样式移除

const containerHeight = doms.container.clientHeight;
const ulHeight = doms.ul.clientHeight;
const liHeight = doms.ul.children[0].clientHeight;
const maxOffset = ulHeight - containerHeight;
/**
 * 设置ul的偏移量
 */
function setOffset() {
    const index = findIndex();
    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 li = doms.ul.querySelector('.active');
    if (li) { li.classList.remove('active'); }
    // doms.ul.children[index].className='active';
    li = doms.ul.children[index];
    if (li) { li.classList.add('active'); }
}

(5)事件监听
最后我们需要在audio中添加事件监听,当时间改变(timeupdate)时,就要调用setOffset方法

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

最终结果展示

posted @ 2023-03-15 18:44  超重了  阅读(228)  评论(0编辑  收藏  举报