新建工程
基础代码结构

- 只要把 favicon.ico 图片放到站点根目录, 就可以被识别到, 不建议这样做
- 要在HTML文件中通过标签引入favicon.ico
- 原生的audio控件的样式是固定的, 调整的代价很大, 所以专业的音乐网站都是用<div>画, 然后通过JS控制功能, 我们这里就用这个控件就行
静态结构
要实现的效果

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="./assets/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="./css/index.css" />
<title>Document</title>
</head>
<body>
<audio controls src="./assets/music.mp3"></audio>
<div class="container">
<ul class="lrc-list">
<li>Lorem ipsum dolor sit.</li>
<li>Cupiditate itaque doloribus nihil?</li>
<li class="active">Quos molestiae voluptas illo.</li>
<li>Inventore consequatur pariatur harum?</li>
<li>Quam voluptatum eligendi aliquam.</li>
<li>Eos eligendi amet ratione.</li>
<li>Voluptatem iure labore maiores.</li>
<li>Soluta deleniti beatae aliquam.</li>
<li>Corrupti id ipsam laboriosam!</li>
<li>Tempore enim inventore reiciendis?</li>
<li>Ad modi placeat similique!</li>
<li>Eius, cum dignissimos! Corporis.</li>
<li>Eaque repudiandae minima accusantium.</li>
<li>Aliquid repudiandae natus dicta.</li>
<li>Hic, distinctio magnam. Aspernatur.</li>
<li>Minima quas maxime vero!</li>
<li>Tempora, animi dolores. Ipsum?</li>
<li>Aliquid reiciendis et aspernatur?</li>
<li>Amet neque dolore maiores!</li>
<li>Nulla error magni sit!</li>
<li>Repellat similique qui dolorum?</li>
<li>Eveniet blanditiis itaque deleniti!</li>
<li>Quod alias fuga mollitia?</li>
<li>Ab aperiam rem asperiores.</li>
<li>Ipsam dolor magni repudiandae.</li>
<li>Dignissimos illum libero hic.</li>
<li>Doloribus repellendus optio dolores!</li>
<li>Perspiciatis repellat omnis in?</li>
<li>Ad, molestiae vero. Et?</li>
<li>Facilis distinctio totam est?</li>
</ul>
</div>
</body>
</html>
* {
margin: 0;
padding: 0;
}
body {
background: #000;
color: #666;
/* 让行盒的子元素实现居中, 文字属于行盒 */
text-align: center;
}
audio {
width: 450px;
margin: 30px 0;
}
.container {
height: 420px;
overflow: hidden;
}
.container .lrc-list {
transform: 0.2s;
/* 引发重排: 元素的几何信息发生改变 */
/* margin-top: -20px; */
/* 渲染效率更高 */
transform: translateY(-20px);
}
.container li {
height: 35px;
line-height: 35px;
/* 过渡属性: 数值类的属性有效 */
/* width: 0->100 有效 */
/* opacity: 1-> 0 有效 */
/* color: #000 -> #fff 有效 */
/* display: block -> none 无效 */
/* 通常加加给容器, 双程过渡 */
/* 加给元素, 类名出现时有过渡, 类名移除时没有过渡 */
transition: 0.2s;
}
.container li.active {
color: #fff;
/* 引发重排: 元素的几何信息发生改变 */
/* font-size: 30px; */
/* 渲染效率更高 */
transform: scale(1.4);
}
- lorem乱数假文: 用于测试判断的文字
- 快速生成: li*30>lorem4
搞定数据
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
... ...
<script src="./js/data.js"></script>
<script src="./js/index.js"></script>
</body>
</html>
// 1.查看数据
// 已有的数据是一个字符串, 不利于后续的操作, 自己处理一下
// console.log(lrc);
/**
* 处理时间字符串, 把时间换算为秒
* @param {String} timeStr
* @returns 秒值
*/
function parseTime(timeStr) {
const parts = timeStr.split(":")
return +parts[0] * 60 + +parts[1]
}
/**
* 处理歌词字符串, 返回歌词数组
* [{time:开始时间, words: 歌词内容}]
*/
function parseLrc() {
const lines = lrc.split("\n")
const lrcArr = []
lines.forEach(item => {
const parts = item.split("]")
const timeStr = parts[0].substring(1)
const obj = {
time: parseTime(timeStr),
words: parts[1]
}
lrcArr.push(obj)
})
return lrcArr
}
const lrcData = parseLrc()
console.log(lrcData);
// 2.数据处理完了, 开始考虑数据和功能的关联
// 高亮显示的歌词决定了列表的偏移量
const doms = {
audio: document.querySelector("audio")
}
/**
* 根据当前播放器的秒值, 查找lrcData数组中,对应的歌词对象的索引下标
*/
function findIndex() {
const curTime = doms.audio.currentTime;
for (let index = 0; index < lrcData.length; index++) {
if (curTime < lrcData[index].time) {
return index - 1
}
}
return lrcData.length - 1
}
findIndex()
搞定页面

// 1.查看数据
// 已有的数据是一个字符串, 不利于后续的操作, 自己处理一下
// console.log(lrc);
/**
* 处理时间字符串, 把时间换算为秒
* @param {String} timeStr
* @returns 秒值
*/
function parseTime(timeStr) {
const parts = timeStr.split(":")
return +parts[0] * 60 + +parts[1]
}
/**
* 处理歌词字符串, 返回歌词数组
* [{time:开始时间, words: 歌词内容}]
*/
function parseLrc() {
const lines = lrc.split("\n")
const lrcArr = []
lines.forEach(item => {
const parts = item.split("]")
const timeStr = parts[0].substring(1)
const obj = {
time: parseTime(timeStr),
words: parts[1]
}
lrcArr.push(obj)
})
return lrcArr
}
const lrcData = parseLrc()
// 2.数据处理完了, 开始考虑数据和功能的关联
// 高亮显示的歌词决定了列表的偏移量
const doms = {
audio: document.querySelector("audio"),
ul: document.querySelector("ul"),
container: document.querySelector(".container")
}
/**
* 根据当前播放器的秒值, 查找lrcData数组中,对应的歌词对象的索引下标
*/
function findIndex() {
const curTime = doms.audio.currentTime;
for (let index = 0; index < lrcData.length; index++) {
if (curTime < lrcData[index].time) {
return index - 1
}
}
return lrcData.length - 1
}
// 3. 考虑界面逻辑
/**
* 创建歌词元素
*/
function createLrcElements() {
const frag = document.createDocumentFragment() // 文档片段
lrcData.forEach(item => {
const li = document.createElement("li")
li.textContent = item.words
// doms.ul.append(li) // 改动Dom树
frag.appendChild(li)
})
doms.ul.appendChild(frag)
}
createLrcElements()
const containerHeight = doms.container.clientHeight; // 容器高度
const liHeight = doms.ul.children[0].clientHeight; // 歌词li高度
const maxOffset = doms.ul.clientHeight - 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")
}
li = doms.ul.children[index]
if (li) {
li.classList.add("active")
}
}
// 4.考虑事件
doms.audio.addEventListener("timeupdate", setOffset)