这个是效果图
这是利用audio做的一个歌词随音乐而动的html页面。
这个简单的音频播放器是用ajax请求本地服务器的lrc文件,lrc文件就是歌词文件。
歌词滚动的基本思想
把歌词放在盒子里面,通过audio的timeupdata事件监听音乐的播放,并获取当前播放的时间currentTime,然后通过当前播放的时间于歌词的时间匹配,匹配到的话就把歌词盒子往上移动。
第一步:通过ajax获取本地歌词
//用ajax请求数据
function createXHR(){
if(typeof(XMLHttpRequest)!='undefined'){
return new XMLHttpRequest;
}else if(typeof(ActiveXObject)!='undefined'){
var xhrArr=['Microsoft.XMLHTTP','MSXML2.XMLHTTP.6.0','MSXML2.XMLHTTP.5.0','MSXML2.XMLHTTP.4.0','MSXML2.XMLHTTP.3.0','MSXML2.XMLHTTP.2.0'];
var len=xhrArr.length,xhr;
for(var i=0;i<length;i++){
try{
xhr=new ActiveXObject(xhrArr[i]);
break;
}catch(ex){
}
return xhr;
}
}else{
throw new Error('no XHR')
}
}
//创建XMLHttpRequest对象
var xhr=createXHR(),data=null;
//创建HTTP请求
xhr.open('get','mz.lrc');
//发送请求
xhr.send(null);
//响应XMLHttpRequest对象状态变化的函数,onreadystatechange在readystatechange属性发生改变时触发
xhr.onreadystatechange=function(){
//异步调用成功,相应内容解析完成,可在客户端调用
if(xhr.readyState===4){
if((xhr.status>=200&&xhr.status<300)||xhr.status==304){//304表示请求资源没有被修改,可以使用缓存
//获得服务器返回数据
data=xhr.responseText;
//渲染数据到页面中
renderDataToDom(data);
}
}
}
第二部:处理歌词
歌词的格式是这样的
我们要转换的格式
这里第108秒跑到了前面,所以我把时间排好序,又放到了一个数组中,用这个数组来滚动音乐。
function renderDataToDom(data){
//将歌词中的时间格式转换成多少秒
var pattern=/\[(\d{2}):(\d{2})\.(\d{2})\](.*)/g;
var result=pattern.exec(data);
while(result!=null){
var time=parseInt(result[1])*60+parseInt(result[2])+parseFloat('0.'+result[3]);
if(result[4]){
lrcObj[time]=result[4];
}
result=pattern.exec(data);
}
//转换后的格式,有的顺序可能不对,转换顺序按从小到大
var i=0;
var fragment=document.createDocumentFragment();
var li=null,sortObj={};
for(var key in lrcObj){
keysArr[i]=parseFloat(key);
i++;
}
keysArr.sort((a,b)=>a-b);
for(var i=0;i<keysArr.length;i++){
sortObj[keysArr[i]]=lrcObj[keysArr[i]];
/* console.log(''+keysArr[i]+lrcObj[keysArr[i]]);*/
li=document.createElement("li");
li.appendChild(document.createTextNode(sortObj[keysArr[i]]));
fragment.appendChild(li);
}
//sortObj虽然是排完序后的对象,但顺序还是和之前一样,所以在排序的时候就把li加载好,匹配歌词的时候用时间来就行了。
//将歌词添加到lyricsBox中
lyricsBox.appendChild(fragment);
//var len=Object.keys(lrcObj).length;//获取这个对象的长度
}
第三步、创建audio对象,实现歌词随音乐而动
//创建audio对象
var audio=new Audio;
var isPlay=false;
var nowTime;
audio.src='芒种.mp3';
audio.loop=true;
audioPlay();
function audioPlay(){
//播放、暂停
var play=document.getElementsByClassName('play')[0];
console.log(keysArr);
play.onclick=function(){
if(isPlay){
this.style.backgroundImage='url("img/pause.png")';
isPlay=false;
audio.pause();
}else{
this.style.backgroundImage='url("img/play.png")';
isPlay=true;
audio.play();
}
}
}
//歌词虽页面而动
var m=0;//第几句,贯穿全剧
var preTime=0;
audio.ontimeupdate=function(){
nowTime=this.currentTime;
nowTime=parseFloat(nowTime.toFixed(2));
console.log(nowTime);
console.log(preTime);
//检测是否一首歌播放完
if(nowTime<preTime){
document.getElementsByClassName('lyricsBox')[0].children[0].children[0].children[m-1].style.color='#fff';
document.getElementsByClassName('lyricsBox')[0].children[0].children[0].children[m-1].style.fontSize='18px';
console.log(1111);
m=0;
preTime=0;
}else{
preTime=nowTime;
}
if(nowTime>=(keysArr[m]-0.25)){
console.log(typeof(keysArr[m]))
if(m>=1){
document.getElementsByClassName('lyricsBox')[0].children[0].children[0].children[m].previousElementSibling.style.color='#fff';
document.getElementsByClassName('lyricsBox')[0].children[0].children[0].children[m].previousElementSibling.style.fontSize='18px';
}
document.getElementsByClassName('lyricsBox')[0].children[0].children[0].children[m].style.color='red';
document.getElementsByClassName('lyricsBox')[0].children[0].children[0].children[m].style.fontSize='22px';
m++;
document.getElementsByClassName('lyricsBox')[0].children[0].style.top=-33*(m-4)+'px';
}
}
这里让当前的时间和之前做的时间数组匹配用了一个方法,先声明一个全局变量,并赋值为0,作为记录播放到第几局的一个变量,同时也是时间数组的下标,因为时间数组里面,下边就是第几句,对应的值就是第几句播放时的时间,当现在的时间大于时间数组的时间的时候,让该句歌词颜色变红并且字体变大,然后m++,然后让歌词盒子向上移动一个li的高度。这里歌词盒子有个初始top,这样做为了让当前的歌词显示在屏幕的中间。
当前歌词变完色的同时,上一句歌词还要变回原样,所以在上面那一步操作之前,还要把当前m对应的歌词样式变回原样,还要注意的是这个m要大于一。
遇到的问题及解决
解决循环(loop)为true的时候,onended这个事件是不能用的问题
还有就是,当循环(loop)为true的时候,onended这个事件是不能用的,所以我加了一个if语句,来判断这首歌是否播放完。做法就是,声明一个pretime的变量,赋初值为0,意思就是当前timeupdata时间监听的currentTime的上一个currentTime,比较preTime和currentTime,正常情况下currentTime是肯定大于preTime的,但当第一次播放结束时currentTime是小于preTime的,所以就用这个来判断音乐的循环播放。
如果在子盒子上加个margin-top,这个属性会作用到父盒子上,解决方法就是给子盒子加个边框
获取音频的currentTime属性,其实得到的是一个字符串,在比较的时候不知道这个问题,每次在判断是否播放完的时候的第十秒就出错了,然后把它转换成数字就行了。