歌词滚动效果
知识点:
-
引入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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具