知识点:
-
引入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)