歌词滚动效果(学习笔记)
目录结构:
HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music</title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<audio controls src="./assets/Music.mp3"></audio>
<div class="container">
<ul class="lrc-list">
</ul>
</div>
<script src="./js/data.js"></script>
<script src="./index.js"></script>
</body>
</html>
index.js文件
/**
* 解析歌词字符串
* 得到一个歌词对象的数组
* 每个歌词对象:
* {time:开始时间, words: 歌词内容}
*/
function parseLrc() {
var lines = lrc.split("\n");
var result = []; // 歌词对象数组
for (var i = 0; i < lines.length; i++) {
var str = lines[i];
var parts = str.split("]");
var timeStr = parts[0].substring(1);
var obj = {
time: parseTime(timeStr),
words: parts[1],
};
result.push(obj);
}
return result;
}
/**
* 将一个时间字符串解析为数字(秒)
* @param {String} timeStr 时间字符串
* @returns
*/
function parseTime(timeStr) {
var parts = timeStr.split(":");
return +parts[0] * 60 + +parts[1];
}
var lrcData = parseLrc();
// 获取需要的 dom
var doms = {
audio: document.querySelector("audio"),
ul: document.querySelector(".container ul"),
container: document.querySelector(".container"),
};
/**
* 计算出,在当前播放器播放到第几秒的情况下
* lrcData数组中,应该高亮显示的歌词下标
* 如果没有任何一句歌词需要显示,则得到-1
*/
function findIndex() {
// 播放器当前时间
var curTime = doms.audio.currentTime;
for (var i = 0; i < lrcData.length; i++) {
if (curTime < lrcData[i].time) {
return i - 1;
}
}
// 找遍了都没找到(说明播放到最后一句)
return lrcData.length - 1;
}
// 界面
/**
* 创建歌词元素 li
*/
function createLrcElements() {
var frag = document.createDocumentFragment(); // 文档片段
for (var i = 0; i < lrcData.length; i++) {
var li = document.createElement("li");
li.textContent = lrcData[i].words;
frag.appendChild(li); // 改动了 dom 树
}
doms.ul.appendChild(frag);
}
createLrcElements();
// 容器高度
var containerHeight = doms.container.clientHeight;
// 每个 li 的高度
var liHeight = doms.ul.children[0].clientHeight;
// 最大偏移量
var maxOffset = doms.ul.clientHeight - containerHeight;
/**
* 设置 ul 元素的偏移量
*/
function setOffset() {
var index = findIndex();
var offset = liHeight * index + liHeight / 2 - containerHeight / 2;
if (offset < 0) {
offset = 0;
}
if (offset > maxOffset) {
offset = maxOffset;
}
doms.ul.style.transform = `translateY(-${offset}px)`;
// 去掉之前的 active 样式
var li = doms.ul.querySelector(".active");
if (li) {
li.classList.remove("active");
}
li = doms.ul.children[index];
if (li) {
li.classList.add("active");
}
}
doms.audio.addEventListener("timeupdate", setOffset);
index.css文件
* {
margin: 0;
padding: 0;
}
body {
background: #000; /* 背景颜色设置为黑色 */
color: #666; /* 文本颜色设置为深灰色 */
text-align: center; /* 文本居中对齐 */
}
audio {
width: 450px; /* 音频播放器宽度设置为450px */
margin: 30px 0; /* 音频播放器上下边距设置为30px */
}
.container {
height: 420px; /* 容器高度设置为420px */
overflow: hidden; /* 隐藏溢出容器的内容 */
/* border: 2px solid #fff; 容器边框设置为2px白色,已注释 */
}
.container ul {
/* 列表过渡效果设置为0.6秒 */
transition: 0.6s;
list-style: none; /* 移除列表的默认样式 */
}
.container li {
height: 30px; /* 列表项高度设置为30px */
/* 列表项边框设置为1px白色,已注释 */
line-height: 30px; /* 行高设置为30px */
transition: 0.2s; /* 列表项过渡效果设置为0.2秒 */
}
.container li.active {
color: #fff; /* 激活状态的列表项文本颜色设置为白色 */
/* font-size: ; 激活状态的列表项字体大小,已注释 */
transform: scale(1.2); /* 激活状态的列表项缩放比例设置为1.2 */
}
data.js部分代码
var lrc = `[00:00.00]Call Me Maybe - Megan Nicole
[00:05.14]I threw a wish in the well don't ask me
[00:07.65]I'll never tell
[00:08.81]I looked to you as it fell
[00:10.90]And now you're in my way
[00:12.93]I trade my soul for a wish
[00:14.92]Pennies and dimes for a kiss I wasn't looking for this
[00:18.90]But now you're in my way
[00:20.92]Your stare was holdin' ripped jeans skin was showin'
[00:24.64]Hot night wind was blowin' where you think you're going baby
[00:28.90]Hey I just met you and this is crazy
[00:32.48]
[00:33.11]But here's my number so call me maybe `;