先看看最终效果
页面实现
HTML
<audio src="./爱在西元前 - 周杰伦.mp3" controls="controls" autoplay="autoplay"></audio>
<div class="wrap">
<ul class="lrc-list">
</ul>
</div>
就这俩元素就够了,现在大多数播放器禁止自动播放,所以autoplay
属性基本无效
.wrap
包裹整个页面,里面通过伪元素实现背景
CSS
:root {
--margin: 20px;
--height: 40px;
--src: url(https://tse1-mm.cn.bing.net/th/id/OIP-C.OsuHeUeUgSNozCxofEpIHQC7Es?pid=ImgDet&rs=1);
}
* {
margin: 0;
padding: 0;
}
body {
background-color: #000;
color: #777;
font-size: 14px;
text-align: center;
}
ul {
list-style: none;
}
基础的重置样式,body
设置字体样式,让子元素继承
audio {
width: 100%;
height: var(--height);
opacity: .8;
}
.wrap {
position: relative;
/* 减去播放条和自身的margin */
height: calc(100vh - var(--height) - var(--margin));
overflow: hidden;
margin-top: var(--margin);
}
接下来加上背景图片
.wrap::before {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: .4;
content: "";
background: var(--src) no-repeat center bottom;
background-size: contain;
}
再加个背景模糊效果,为了展示才设置.lrc-list
高度的,后面要去掉,让他自动撑开
.lrc-list {
transition: .6s;
height: calc(100vh - var(--height) - var(--margin));
backdrop-filter: blur(5px);
}
li {
height: 30px;
line-height: 30px;
transition: .6s;
}
.is-playing {
transform: scale(1.22);
color: #fff;
}
JS
歌词
首先就是歌词部分,我到网上下载了一段歌词文件
const lrc = `[00:31.65] 作词:周杰伦 作曲:周杰伦
[00:01.65]
[00:31.86]古巴比伦王颁布了汉摩拉比法典
`;
首先,观察他的特点
- 每一行歌词,以换行结尾
]
分割了每一行歌词和时间的位置
那么接下来就是用合理的数据结构存储歌词了
先明确一下,我们能获取什么信息
播放的时候,audio
元素,提供timeupdate
时间,当时间变化时触发
这时可以获取audio.currentTime
,也就是现在播放的时间
数据结构
- 经过分析,那么现在可以选择数组里放入对象存储
- 顺序按照歌词时间
const lrc = [
{
word: '...',
time: ...
}
]
这样就能描述每一份歌词了
接下来就是实现了
function parseLrc() {
const lrc = `[00:31.65] 作词:周杰伦 作曲:周杰伦
[00:01.65]
[00:31.86]古巴比伦王颁布了汉摩拉比法典
`;
const res = [];
lrcArr = lrc.split('\n');
lrcArr.forEach((lrcDetail, index) => {
const lrc = lrcDetail.split(']'),
time = parseTime(lrc[0].slice(1).trim()),
word = lrc[1].trim();
res[index] = { time, word };
});
console.log(res)
return res;
}
根据换行分割成数组,遍历他,这里换成return map
是一样的
再根据]
分割时间和歌词
时间需要把他解析成秒
function parseTime(timeStr) {
const timeArr = timeStr.split(':');
return numFixed(+timeArr[0] * 60 + +timeArr[1]);
}
function numFixed(num, dec = 2) {
return Math.round(num * 10 ** dec) / 10 ** dec;
}
因为toFixed
部分情况不准,所以我自己写了四舍五入的函数
以num = 3.105
为例,四舍五入两位,结果应该是3.11
才对
使用我的函数就是3.105 * 100 = 310.5
,再四舍五入就是311
,然后除回去100
= 3.11
处理后的结果
获取数据
const lrc = parseLrc(),
oAudio = document.getElementsByTagName('audio')[0],
oUl = document.getElementsByClassName('lrc-list')[0],
wrapHeight = document.getElementsByClassName('wrap')[0].offsetHeight;
let oLis = [],
showLrc = false,
liHeight = 0,
maxHeight = 0;
接下来把歌词添加到ul
,并获取各项参数
function addLrc() {
const oFrag = document.createDocumentFragment();
for (let i = 0; i < lrc.length; i++) {
const li = document.createElement('li');
li.innerText = lrc[i].word;
oFrag.appendChild(li);
}
oUl.appendChild(oFrag);
oLis = oUl.children;
// 歌词列表高度 - 外包装高度 用来判断播放完毕
maxHeight = oUl.offsetHeight - wrapHeight;
liHeight = oLis[0].offsetHeight;
}
基础布局就实现了
事件处理
现在要在播放时处理歌词滚动
oAudio.addEventListener('timeupdate', e => {
const curTime = oAudio.currentTime;
document.getElementsByClassName('is-playing')[0]?.classList.toggle('is-playing');
transformLrc(curTime);
});
function transformLrc(curTime) {
let index = getLrcIndex(curTime),
offsetY = liHeight / 2 + liHeight * index - wrapHeight / 2;
if (offsetY < 0) offsetY = 0;
if (offsetY > maxHeight) offsetY = maxHeight;
oLis[index].classList.toggle('is-playing');
oUl.style.transform = `translateY(${-offsetY}px)`;
}
function getLrcIndex(curTime) {
let index;
for (let i = 0; i < lrc.length; i++) {
if (lrc[i].time > curTime) {
index = i - 1;
if (index < 0) return 0;
return index;
}
};
const len = lrc.length - 1;
if (curTime > lrc[len].time) return len;
}
getLrcIndex
根据时间遍历数组,找到第一个比他大的对象,再减一使用上一个
offsetY
根据每行歌词高度,乘以索引,再减去一半居中
减去wrapHeight / 2
是为了在屏幕居中
每次切换歌词时,给对应的歌词那一行元素,加上播放类,切换显示
至此,基本功能全部完成
锦上添花
window.addEventListener('click', () => {
oUl.style.opacity = showLrc ? 1 : 0;
showLrc = !showLrc;
});
window.addEventListener('keyup', (e) => {
if (e.code === 'Space') {
e.preventDefault()
oAudio.paused
? oAudio.play()
: oAudio.pause();
}
});
window.addEventListener('keydown', (e) => {
// 阻止按键造成页面滚动
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
e.preventDefault();
}
});
添加点击页面隐藏歌词,空格键暂停播放事件
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END