暑假了,还是不能让自己闲着,学点自己感兴趣的知识,写点自己喜欢的代码。
=========================
今天写了一个播放器的雏形,带歌词显示。
没去自定义播放器,主要是写歌词显示效果。效果图如下:
首先当然是要准备一个 mp3 文件。
一、HTML 结构
很简单:
<audio id="myPlayer" class="myPlayer" controls src="music/music_zndsb.mp3"></audio>
<div class="container" id="container">
<ul class="lrcList" id="lrcList">
<li>歌词歌词歌词歌词1</li>
<li class="current">歌词歌词歌词歌词2</li>
<li>歌词歌词歌词歌词3</li>
<li>歌词歌词歌词歌词4</li>
<li>歌词歌词歌词歌词5</li>
</ul>
</div>
<img class="img" src="images/bg1.jpg" alt="">
播放器就用 audio 标签。
主要是歌词部分。一个div嵌套一个 ul;每句歌词都是一个 li;当前歌词有个高亮的类 current。
最后使用一张自己喜欢的图作为背景图。
二、CSS
*{
margin: 0;
padding: 0;
}
ul,li,ol{
list-style: none;
}
.myPlayer{
display: block;
width: 600px;
margin-top: 50px;
margin-left: auto;
margin-right: auto;
opacity: 0.5;
transition:all 0.2s;
}
.myPlayer:hover{
opacity: 1;
}
.img{
position: absolute;
z-index: -1; /* 让图片在页面的内容下方 */
top: 0;
width: 100%;
height: 100%;
object-fit: cover; /* 调整图片内容不变形 */
}
.container{
width: 600px;
margin-left: auto;
margin-right: auto;
margin-top: 30px;
height: 180px;
overflow: hidden;
position: relative;
}
.lrcList{
font-size: 16px;
line-height: 30px;
color: #869cd3;
text-align: center;
transition:all 0.2s; /* 过渡动画。实现歌词上下移动的动画 */
}
.lrcList li{
transition:all 0.2s;
height: 30px;
opacity: 0.5;
}
.lrcList .current{
transform: scale(1.4);
color: #fff;
opacity: 1;
}
三、歌词
这个案例因为重点在歌词的显示,所以就把歌词写在里一个变量里。
歌词很长,因此用了 ES6 里的模板字符串。
let lrc = `[00:00.00]在你的身边 - 盛哲
[00:02.25]词:盛哲
[00:04.51]曲:盛哲
[00:06.77]安静地又说分开
[00:09.49]
[00:10.80]没有依赖却是太多依赖
[00:15.70]
[00:18.66]寂寞的广场中央
[00:21.35]
[00:22.57]是谁的对白追赶我的空白
[00:29.46]
[00:30.55]爱就爱了不怕没来过
[00:33.58]恨就恨了我从没想过
[00:36.33]
[00:37.85]是怕独念一个人太深刻
[00:41.40]
[00:42.34]爱就爱了不怕没来过
[00:45.48]恨就恨了我从没想过
[00:48.02]
[00:48.84]到过的地方熟悉曾经的模样
[00:54.20]我以为忘了想念
[00:56.88]
[00:58.08]而面对夕阳希望你回到今天
[01:04.43]
[01:06.01]我记得捧你的脸
[01:09.88]在双手之间安静地看你的眼
[01:16.43]像秋天落叶温柔整个世界
[01:22.65]
[01:23.91]我想在你的身边
[01:27.58]
[01:29.95]忘了这路有多长
[01:32.63]
[01:33.64]想和你去看季节慢慢变换
[01:39.12]
[01:41.65]又来到这座广场
[01:44.39]
[01:45.45]听风随落叶已是最后一片
[01:52.53]
[01:53.53]爱就爱了不怕没来过
[01:56.39]恨就恨了我从没想过
[01:59.15]
[02:00.89]是怕独念一个人太深刻
[02:04.54]
[02:05.30]爱就爱了不怕没来过
[02:08.37]恨就恨了我从没想过
[02:11.47]
[02:12.31]到过的地方熟悉曾经的模样
[02:17.16]我以为忘了想念
[02:20.72]而面对夕阳希望你回到今天
[02:27.38]
[02:29.02]我记得捧你的脸
[02:32.77]在双手之间安静地看你的眼
[02:39.41]像秋天落叶温柔整个世界
[02:45.58]
[02:46.98]我想在你的身边
[02:50.51]
[02:52.86]我想在你的身边
[02:56.43]
[02:58.76]我想在你的身边
[03:03.04]
[03:04.66]就让那往事随风
[03:08.39]让它带走伤带走痛
[03:10.83]带回那日暮的梦
[03:14.93]花开落
[03:17.56]
[03:18.20]云会走
[03:19.62]
[03:21.12]铺满天
[03:22.81]
[03:24.00]而你笑着在我的身边
[03:28.29]我以为忘了想念
[03:31.01]
[03:31.85]而面对夕阳希望你回到今天
[03:38.37]
[03:40.08]我记得捧你的脸
[03:43.71]在双手之间安静地看你的眼
[03:50.45]像秋天落叶温柔整个世界
[03:56.47]
[03:58.07]我想在你的身边
[04:01.82]
[04:03.88]我想在你的身边
[04:07.20]
[04:09.88]我想在你的身边
[04:13.63]在你的身边`;
这个案例目前是把歌词写死了。后期可以做成 lrc 文件,通过 AJAX 异步加载,并解析。
四、关键 JavaScript 解析
首先是解析歌词。
歌词目前是一个很长的字符串,但是每句歌词都独立成行。所以,为了便于独立歌词出来,要把歌词分解为数组。每个元素就是每一句歌词。
考虑到歌词分为时间和文本部分,所以歌词数组里的元素是一个 Object。其属性有 time 和 word 两部分。
// 把歌词字符串处理为 Object 对象
/**
* 解析歌词字符串,的到歌词对象数组
* 每个歌词对象:
* {
time:开始时间,
word:歌词
* }
*/
function parseLRC(LRC){
let lines = LRC.split('\n'); // 把歌词转为数组
let LRCArr = []; // 歌词数组
// 遍历数组
lines.forEach(item => {
// item数据: [00:06.77]安静地又说分开
// 切割字符
let parts = item.split("]"); // [00:06.77 , 安静地又说分开
let timer = parts[0].slice(1).trim(); // 00:06.77
let obj = {
time: parseTime(timer),
word: parts[1].trim()==""?"":parts[1] // 安静地又说分开
}
// console.info( obj );
LRCArr.push(obj);
});
return LRCArr;
}
把时间要转为对应的 x 秒,写一个时间处理函数。
/*
把时间字符串转为时间数字
eg:
01:06.77 => 66.77
*/
function parseTime(timer){
let t = timer.split(":");
let result = Number(t[0])*60 + Number(t[1]);
return result ;
}
找到当前播放进度的歌词,也就是歌词数组的索引。
/*
* 计算出,再当前播放器播放到第几秒的情况
* LRCData 应该高亮显示的歌词下标。
* 高亮歌词是: 比当前时间数【第一次大】的上一句。
* 如果没有任何歌词显示,就为 -1 。
* 返回值:当前歌词对应的索引
*/
function findIndex(){
// 播放器当前时间
let index = -1;
let curTime = doms.audio.currentTime;
for(let i=0; i<=LRCData.length-1 ; i++){
if( curTime < LRCData[i].time ){
index = i - 1;
return index;
}
}
// 找遍了,都没有歌词,说明播放完毕里,显示最后一句歌词。
index = LRCData.length-1
return index;
}
歌词显示。为了方便歌词的显示,歌词结构用的是 ul 和 li。因此,要遍历歌词数组,生成对应的 li。
因为,createElement 会多次操作DOM,造成 DOM 重绘。为了提高效率,是用 Fragment 同一收集 li 后,集体绘制,减少 DOM 重绘次数。
// 界面部分
/*
生成歌词 li
*/
function createLrcList(lrc){
// 避免多次操作 DOM。创建一个 DOM 片段,它不会显示,但是可以集中处理 DOM。
let frag = document.createDocumentFragment();
doms.lrcList.innerHTML = "";
lrc.forEach(item=>{
let li = document.createElement("li");
li.innerHTML = item.word ;
frag.appendChild(li);
});
doms.lrcList.appendChild(frag);
}
五、完整 JS 代码
// 把歌词字符串处理为 Object 对象
/**
* 解析歌词字符串,的到歌词对象数组
* 每个歌词对象:
* {
time:开始时间,
word:歌词
* }
*/
function parseLRC(LRC){
let lines = LRC.split('\n'); // 把歌词转为数组
let LRCArr = []; // 歌词数组
// 遍历数组
lines.forEach(item => {
// item数据: [00:06.77]安静地又说分开
// 切割字符
let parts = item.split("]"); // [00:06.77 , 安静地又说分开
let timer = parts[0].slice(1).trim(); // 00:06.77
let obj = {
time: parseTime(timer),
word: parts[1].trim()==""?"":parts[1] // 安静地又说分开
}
// console.info( obj );
LRCArr.push(obj);
});
return LRCArr;
}
/*
把时间字符串转为时间数字
eg:
01:06.77 => 66.77
*/
function parseTime(timer){
let t = timer.split(":");
let result = Number(t[0])*60 + Number(t[1]);
return result ;
}
/*
* 计算出,再当前播放器播放到第几秒的情况
* LRCData 应该高亮显示的歌词下标。
* 高亮歌词是: 比当前时间数【第一次大】的上一句。
* 如果没有任何歌词显示,就为 -1 。
* 返回值:当前歌词对应的索引
*/
function findIndex(){
// 播放器当前时间
let index = -1;
let curTime = doms.audio.currentTime;
for(let i=0; i<=LRCData.length-1 ; i++){
if( curTime < LRCData[i].time ){
index = i - 1;
return index;
}
}
// 找遍了,都没有歌词,说明播放完毕里,显示最后一句歌词。
index = LRCData.length-1
return index;
}
// 界面部分
/*
生成歌词 li
*/
function createLrcList(lrc){
// 避免多次操作 DOM。创建一个 DOM 片段,它不会显示,但是可以集中处理 DOM。
let frag = document.createDocumentFragment();
doms.lrcList.innerHTML = "";
lrc.forEach(item=>{
let li = document.createElement("li");
li.innerHTML = item.word ;
frag.appendChild(li);
});
doms.lrcList.appendChild(frag);
}
/*
设置歌词 ul 的偏移量
*/
function setOffset(index){
let dis =-1*( index * liH + liH/2 - conH/2 ); // 位移距离
doms.lrcList.style.transform = `translateY(${dis}px)`;
console.info( dis );
}
/*
设置歌词高亮
*/
function setLight(index){
let ul = doms.lrcList;
let lis = ul.children;
let cur = document.querySelector(".current");
if( cur ){ // 如果存在
cur.classList.remove("current");
}
lis[index].classList.add("current");
}
let doms = {
audio:document.querySelector("audio"),
lrcList:document.querySelector("#lrcList"),
container:document.querySelector("#container")
}
let LRCData = parseLRC(lrc);
createLrcList(LRCData); // 创造歌词 li
let conH = doms.container.clientHeight; // 容器高度
let liH = doms.lrcList.children[0].clientHeight; // li 高度
// 初始化歌词位置,让第一句歌词在歌词区中间
doms.lrcList.style.transform = `translateY(${-1*( liH/2 - conH/2)}px)`;
doms.audio.addEventListener("timeupdate",function(){
let index = findIndex();
setLight(index); // 歌词位移
setOffset(index); // 歌词高亮
});
完毕~