引言
前几天在使用网易云网页版听歌时,看着那个页面的歌词随歌曲进行高亮,突然也想自己手动地去实现一下,于是呢,就仿照了网易云音乐的网页自己也写了个页面。效果图如下:
当然了,此处不做css的样式介绍,主要是想分享一下在做这个页面时遇到的关于处理歌词随歌曲进行滚动高亮的方式。
思路
在实现歌词随歌曲进度进行自动高亮时,你需要清楚地知道你想要实现的效果时什么?再者,你需要知道如何设计容器,使得歌词可以有滚动的效果,并进行定位跟踪。
预计达到效果:
- 在歌曲播放时,歌词可以根据歌曲播放的时间进行跟踪,并准确的显示出当前所对应的歌词。
- 在进行歌曲快进时,歌词应该要定位到歌曲快进的地方,并高亮处理歌词。
设计
- 首先,你需要设计两个容器,分别是用来做歌词的显示容器和歌词的存放容器的
- 此处使用的<audio></audio>标签,我们可以通过标签获取歌曲的总时长,歌曲当前播放的位置,以及使用audio自带的timeUpdate事件进行监听,然后进行歌词的定位。
- 歌词的样式在网上一般可以直接找到,需要有带时间格式的那种,然后我们自己进行解析歌词,以数组的方式进行存储json型数据
- 通过timeUpdate事件循环判断当前歌曲播放的位置,并设置css高亮对应的歌词,同时,滚动一定距离,使得高亮的那行歌词始终显示在中间。
- 当进行快进或快退时,也是根据当前歌曲的播放位置,使用timeUpdate事件进行监听,并高亮相应行的歌词,和滚动到对应的位置。
实现
- <audio>标签提供了一个监听音乐播放的事件,叫做timeUpdate,使用audio.addEventListener("timeUpdate",function(){})进行音乐播放的监听,然后我们的一切歌词监听都是以这个来方法来进行开发的。这是一个实时监听的方法,可以监听到每一毫秒。因为循环时会有一定时间的损失,所以此处我们用时间区间段去判别,并进行高亮。
- 在监听器中,我们每次都要循环变量解析完成的歌词,然后通过循环变量歌词数组,匹配对应的时间,匹配方式:歌曲播放时间大于当前歌词行对应的时间并且小于下一行歌词对应的时间,都进行高亮处理。
- 在进行歌词滚动的时候,我们一般是在前几行的时候只进行高亮处理,此处我们可以设置一个默认的不滚动的距离。当高亮的歌词的标签距离顶部的距离大于默认值时,此时就需要进行滚动了,所以此时要计算出要滚动的距离,滚动距离=滚动条长度/数组长度*当前数组的index -默认不滚动的距离,这个值及为即将滚动的距离。
- 这样在歌曲播放时,就可以高亮歌词了,快进或快退时也可以定位到相应的歌词。
代码
- 容器设置,设置两个<div>标签,其中关系为父子容器关系,父容器div的宽高为固定值,overflow设置为hidden,子容器的div设置overflow-y:auto;
<audio src="渡红尘.mp3"></audio> <div class="music-content-lrc" > <div class="music-content-lrc-playing" id="lc"> 暂无歌词 </div> </div>
- 使用<audio>标签的timeUpdate事件进行监听。
//获取audio对象 var audio = document.getElementById("audio"); //设置监听事件 audio.addEventListener("timeupdate",audioPlugns.timeUpdate);
- timeUpdate事件,audio的监听事件
timeUpdate:function(){ //获得当前播放时间 var ct = audio.currentTime; var du = audio.duration; if(audio.ended == true){ var $this = $("#pp"); $this.removeClass("music-play-playing").addClass("music-play-pause"); //更改状态 $this.attr("flag",0); //audio.currentTime = 0; audioPlugns.myRotate(0); //循环模式 audioPlugns.myMode(); } audioPlugns.renderLrc(ct,du); }
- 歌词高亮渲染及定位
/*歌词渲染*/ renderLrc:function(ct,du){ var pList = $("#lc").find("p"); $("#lc").find('p').removeClass('activated'); //滚动条的高度 var sc = document.getElementById("lc").scrollHeight; pList.each(function(index,element){ var t = parseFloat($(this).attr("time")) - audioPlugns.delay; var pt = parseFloat($(this).prev().attr("time")); var nt = parseFloat($(this).next().attr("time")); if(ct >= t && ct <= nt){ $(this).addClass("activated");//高亮 var x = index*sc/pList.length - audioPlugns.fraction; //设置滚动距离 audioPlugns.sh = x > 0 ? x : 0; } // 最后一行高亮,因为此处去掉了lrc中的所有空内容 if(ct >= t && isNaN(nt)){ $(this).addClass("activated"); } }) $("#lc").animate({"scrollTop":audioPlugns.sh},audioPlugns.freq); var cw= (ct/du *audioPlugns.duration + audioPlugns.now)+"px"; $("#mc").css({"width":cw}); $("#musicNow").html("<em>"+audioPlugns.buautifulTime(ct)+"</em> / "+audioPlugns.buautifulTime(du)); }
- 解析歌词
/*解析歌词*/ parseLrc:function(){ var arr = audioPlugns.lrc.split("\n"); var lrcArray = []; var html = ""; for(var i=0;i<arr.length;i++){ if(arr[i] != ""){ var tempArray = arr[i].split("]"); if(tempArray.length >1 ){ var offset = tempArray[0].substring(1,tempArray[0].length-1); var text = tempArray[1]; lrcArray.push({"offset":offset,"text":text}); //去掉内容为空的数据 if(text != ""){ html+="<p time="+audioPlugns.parseTime(offset).toFixed(1)+">"+text+"</p>"; } } } } $("#lc").html(html); return lrcArray; }
- 解析时间
/*解析时间*/ parseTime:function(time){ var tl = time.split(":"); switch(tl.length){ case 1 : return parseFloat(tl[0]); case 2: return parseFloat(tl[0]) * 60 + parseFloat(tl[1]); case 3: return parseFloat(tl[0]) * 3600 + parseFloat(tl[1]) * 60 + parseFloat(tl[2]); } }