Html5+Css3实现类似网易云音乐的移动版播放器

[置顶] Html5+Css3实现类似网易云音乐的移动版播放器

分类:

目录(?)[+]

Demo

Demo链接如下(支持Chrome与Safari,其它浏览器未测试)并非太完善,主体功能已实现,其它功能有时间在加上吧。

http://followyourheart.sinaapp.com/Demos/musicPlayer/Index.html

↑↑↑↑↑↑↑↑↑↑狠狠戳上面链接看效果。↑↑↑↑↑↑↑↑↑↑

网易云音乐的网页版是没有移动页面的,我突发奇想,大不了我来做一个玩玩:

1.音乐资源的获取

这本身是算不上什么技术的啊,这Demo本身就是仿网易云音乐,资源最好来自网易了,自己琢磨出来的API,应该不算官方public出来的。
上面这个地址打开后会得到一串JSON,用过网易云音乐的应该知道,音乐一般以各种歌单形式程序,只要在网页版的云音乐了点开某个歌单,在地址栏中我们可以拿到歌单的id,通过构造上面的地址,就可以拿到歌单的所有信息,真的,是所有!
这个API本身也没必要解释了,大家可以在Console中通过JSON.parse()方法来转成Object查看具体内容吧,这样清晰一些。Demo中我已经在控制台写了一个对象,这就是上面地址得到的Object,详情见Console。



当然,你想仅仅通过前端来获取这个JSON串是行不通的,为什么?当然是跨域的问题了,ajax是不能直接去网易的服务器上拿数据的。解决这个问题最简单高效的方案就是自己写个代理了,因为代码少功能单一,直接就用PHP了。虽然本人不懂PHP的,但是Java 、.NET 、Python之类的Web项目还是写过一些的,所以稍微查了查语法写了几句PHP代码,直接可以做代理使用了:
  1. <?php    
  2.     $searchUrl = 'http://music.163.com/api/playlist/detail?id=';    
  3.     if(!empty($_GET['id']))    
  4.     {    
  5.         $searchUrl .= $_GET['id'];   
  6.     }  
  7.     header('Content-Type:application/json;charset=UTF-8');  
  8.     echo file_get_contents($searchUrl);    
  9. ?>   
主要做的事情就是在服务器端访问163的这个api,得到数据后返回给客户端,因为服务器端与客户端页面属于同一个域,跨域问题就解决了。顺便附上客户端请求的代码。
[javascript] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. $(function () {  
  2.     var url = './playlist.php?id=173934373';  
  3.     $.ajax({  
  4.         url: url,  
  5.         type: 'GET',  
  6.         dataType: 'json',  
  7.         success: function (data) {  
  8.             console.log(data);  
  9.             ctx.playList = data.result.tracks;  
  10.             ctx.init();  
  11.         },  
  12.         error: function (msg) {  
  13.             alert(msg);  
  14.         }  
  15.   
  16.     });  
  17. });  

2.UI资源

最没品的是这个了,直接下载云音乐的安卓apk文件,解压,GET到所有图片.

3.页面效果实现

3.1 虚化背景

为了更好的将图片融入到背景中,我还是选用div+backgroud的结构,选image标签会有调整大小这类问题。这里涉及到模糊的问题,各浏览器兼容情况不一,很难做到都适应。当然,用canvas是大一统了,但是考虑到前面说的image做背景的位置大小调整问题与canvas中图片跨域问题(能解决,又写个代理嘛),所以直接采用css来实现模糊了。具体代码如下:
  1. .bg {  
  2.     backgroundurl(../resource/images/demo.jpg) center;  
  3.     positionabsolute;  
  4.     top: 0px;  
  5.     left: 0px;  
  6.     height110%;  
  7.     width110%;  
  8.     margin-5%;  
  9.     background-size: cover;  
  10.     filter: url(blur.svg#blur);  
  11.     -webkit-filter: blur(15px) brightness(0.6);  
  12.     -moz-filter: blur(15px) brightness(0.6);  
  13.     filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius=10, MakeShadow=false); /* IE6~IE9 */  
  14.     z-index-1;  
  15. }  
-webkit-filter:blur(15px) brightness(0.6) 主要是这句,其它的主要为兼容考虑的,但是IE11和Edge貌似不领情,但苹果及安卓机器自带的浏览器都没问题,那就行啦,本来就是做的移动页面嘛,记得背景图大小设置为cover,位置为center。
关于模糊在各浏览器上的支持情况可参考张鑫旭大神的博客http://www.zhangxinxu.com/wordpress/2013/11/css-svg-image-blur/



3.2 唱片机

唱片机动画可以借助css3来实现,具体动画效果如下:


  1. @keyframes rotate-disk {  
  2.     100% {  
  3.         ransform: rotateZ(360deg)  
  4.     }  
  5. }  
  6.   
  7. @keyframes rotate-needle-pause {  
  8.     100% {  
  9.         transform: rotateZ(-20deg);  
  10.     }  
  11. }  
  12.   
  13. @keyframes rotate-needle-resume {  
  14.     0% {  
  15.         transform: rotateZ(-20deg);  
  16.     }  
  17.     100% {  
  18.         transform: rotateZ(0deg);  
  19.     }  
  20. }  

这是三段动画的定义,第一个rotate-disk是下面音乐碟的动画,后两个是磁针的动画。这几段定义很好理解,磁盘就是顺时针转360度,磁针就是从-20度转向0度跟0度转向-20度。
在来定义几个css class,方便js通过add/remove class来实现动画的启用与停用。
  1. .pause-needle {  
  2.     animation: rotate-needle-pause 0.51 normal linear;  
  3.     animation-fill-mode: forwards;  
  4. }  
  5.   
  6. .resume-needle {  
  7.     animation: rotate-needle-resume 0.51 normal linear;  
  8.     animation-fill-mode: forwards;  
  9. }  
由于磁针有两个动画,所以就需要定义两个class用于切换,其中animation-fill-mode很重要,这个表示动画结束后元素的状态是停留在开始还是结束,即属性规定动画在播放之前或之后,其动画效果是否可见。可以设置为none | forwards | backwards | both,看名字就知道具体效果了吧。这里设置为forwards就是为了让磁针转到相应位置后能保持该状态。由于磁针的旋转中心不在中心点上,所以要定义transform-origin。
  1. .play-needle {  
  2.     positionabsolute;  
  3.     top: -23px;  
  4.     left: 50%;  
  5.     margin0px -12px;  
  6.     z-index10;  
  7.     width100px;  
  8.     transform-origin: 20px 20px;  
  9. }  
我们从demo中可以看出磁针上一部分是隐藏的,所以我们将top设置为负数了,同时需要在上级元素中设置overflow:hidden来隐藏超出的这部分,这样子Demo中既能隐藏磁针的上端,又能保持顶部title部分透明,这是一个小技巧了。


对于转盘的动画,就不用专门定义动画的class了,直接将其写在它本身的class中:
  1. animation: rotate-disk 20s infinite normal linear;  
  2. animation-play-state: paused;  
可以看出我专门将animation-play-state专门拿出来定义,这是为了能很好的控制动画暂停与播放,通过js代码直接改变这个style值就行了,默认设为暂停吧。这里涉及到很多animation的基础知识,不是很理解的可以参考http://www.w3school.com.cn/css3/css3_animation.asp

思考:
    对于磁针这种只有两种状态,中间只是经过了一个过渡,有必要定义两个key-frame跟两个额外的class吗?对于解决这类问题,又有什么更好的方案呢?这里就要提到CSS3 中的transition属性,它用来给元素应用上过渡效果的。这个属性定义非常简洁,比如:transition:width 1s就是指元素的width产生的变化应用上时长1s的过渡效果。所以上面的效果完全可以用下面的代码来代替,而且不容易出错,在具体的js逻辑中,只需要切换css class便可以改变状态并带有过渡效果:
  1. .play-needle {  
  2.     positionabsolute;  
  3.     top: -23px;  
  4.     left: 50%;  
  5.     margin0px -12px;  
  6.     z-index10;  
  7.     width100px;  
  8.     transform-origin: 20px 20px;  
  9.     transition: transform 0.8s;  
  10. }  
  11.   
  12. .pause-needle {  
  13.     transform: rotateZ(-25deg);  
  14. }  
  15.   
  16. .resume-needle {  
  17.     transform: rotateZ(0deg);  
  18. }  
对于转盘来说,它并不是简单两种状态或者几种状态的切换,属于一直在运行的动画,所以转盘用animation很合适,然而磁针用transition更合适。


具体的页面结构,定位之类的,直接在demo上开启开发者工具查看吧,都是基础的绝对定位了,没有什么技术难度。


3.3播放进度



上图是demo中的进度条效果图,前后两端是时间,这个不用介绍。中间的进度条是分为三种的,最底下一层是总进度,深灰色表示;其次是缓冲进度,即歌曲加载的进度,为浅灰色;最上层是播放进度,用红色表示。其实还是一个进度的小球,这个小球我们可以让它固定在红色进度的右端,这样就可以跟随红色进度移动了。
HTML结构如下:
  1. <div class="process" id="process">  
  2.     <span id="currentTime">00:00</span>  
  3.         <div class="process-bar">  
  4.             <div class="rdy"></div>  
  5.             <div class="cur">  
  6.                 <span id="processBtn" class="process-btn c-btn"></span>  
  7.             </div>  
  8.         </div>  
  9.     <span id="totalTime">00:00</span>  
  10. </div>  
具体的css方面没什么特点,调整位置即可
  1. .process {  
  2.     width350px;  
  3.     height50px;  
  4.     positionabsolute;  
  5.     bottom: 100px;  
  6.     margin0px -175px;  
  7.     left: 50%;  
  8.     font-size12px;  
  9.     font-familyArialHelveticasans-serif;  
  10. }  
  11.   
  12. .process .process-bar {  
  13.     positionabsolute;  
  14.     left: 36px;  
  15.     width280px;  
  16.     margin-top5px;  
  17.     background-color#615D5C;  
  18. }  
  19.   
  20. .process-bar .rdy {  
  21.     background-color#B1ADAC;  
  22.     height2px;  
  23.   
  24. }  
  25.   
  26. .process-bar .cur {  
  27.     background-color#FB0D0D;  
  28.     height2px;  
  29.     positionabsolute;  
  30.     top: 0;  
  31.     left: 0;  
  32. }  
  33.   
  34. .cur .process-btn {  
  35.     cursorpointer;  
  36.     background-imageurl(../resource/images/process_btn.png);  
  37.     background-size: cover;  
  38.     positionabsolute;  
  39.     top: -9px;  
  40.     right: -13px;  
  41.     width22px;  
  42.     height22px;  
  43.     margin-left-11px;  
  44. }  

关于进度条,有一个重要的特点,就是可以手动调整进度,操作方式为鼠标拖动或者手指在触摸屏上拖动。
先贴上一段js代码,主要是给进度小球应用上拖动功能的。
[javascript] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ctx.initProcessBtn = function ($btn) {  
  2.     var moveFun = function () {  
  3.             var e = event;  
  4.             e.preventDefault();  
  5.             if (ctx.processBtnState) {  
  6.                 var moveX = (e.clientX || e.touches[0].clientX) - ctx.originX,  
  7.                     newWidth = ctx.$curBar.width() + moveX;  
  8.                 if (newWidth > ctx.$processBar.width() || newWidth < 0) {  
  9.                     ctx.processBtnState = 0;  
  10.                 } else {  
  11.                     ctx.$curBar.width(newWidth);  
  12.                 }  
  13.                 ctx.originX = (e.clientX || e.touches[0].clientX);  
  14.             }  
  15.         },  
  16.         startFun = function () {  
  17.             var e = event;  
  18.             ctx.processBtnState = 1;  
  19.             ctx.originX = (e.clientX || e.touches[0].clientX);  
  20.         },  
  21.         endFun = function () {  
  22.             if (ctx.processBtnState) {  
  23.                 ctx.player.currentTime = ctx.$curBar.width() / ctx.$processBar.width() * ctx.player.duration;  
  24.                 ctx.processBtnState = 0;  
  25.                 ctx.updateProcess();  
  26.             }  
  27.         };  
  28.     $btn.on('mousedown', startFun);  
  29.     $btn.on('touchstart', startFun);  
  30.     $("body").on('mouseup', endFun);  
  31.     $("body").on('touchend', endFun);  
  32.     $("#process").on('mousemove', moveFun);  
  33.     $("#process").on('touchmove', moveFun);  
  34. }  


可以看出逻辑并不是很复杂,这里我定义了一个标识位processBtnState,主要表示当前是否处于拖动中。主要逻辑是,当鼠标左键按下时,processBtnState为1,松开时为0。鼠标在进度条区间移动时,如果processBtnState为1,则同时更新进度条的位置。主要从拖动开始记录开始位置,拖动过程计算鼠标偏移量,从而设置进度条偏移量。停止拖动时,就可以进行播放器的更新了。由于上面是代码片段,部分调用的字段或方法未展示出来,请自行查看源码(鼠标右键查看源代码)。

4.播放逻辑

由于是基于Html5的Audio标签来进行音乐播放的,所以没有什么难度。
  1. <span style="font-family:Courier New;font-size:14px;"><audio id="player" style="display:none"></audio></span>  
当然创建audio的方式很多,直接new Audio()或者document.createElement('audio'),也可以像我那样在页面中写一个隐藏的tag。这个Demo中主要用到的api是有以下几个
buffered 返回表示音频已缓冲部分的 TimeRanges 对象。
duration 返回音频的长度(以秒计)。
currentTime 设置或返回音频中的当前播放位置(以秒计)。


buffered返回一个TimeRanges对象,demo中主要用来获取已经缓冲的时间,从而更新缓冲进度条。
[javascript] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var buffer = ctx.player.buffered,  
  2.         bufferTime = buffer.length > 0 ? buffer.end(buffer.length - 1) : 0,  
  3.         duration = ctx.player.duration,  
  4.         currentTime = ctx.player.currentTime;  

这里一段定义主要是获取当前播放器的各类时间,以秒为单位。ctx.player为我定义的变量,其实指向的就是audio对象,ctx.player.buffered为TimeRanges对象,所以我们通过end(lastIndex)方法拿到已缓冲的总时间。
duration为音乐的总时间,currentTime为当前播放的时间。这些信息需要及时更新到进度条中,可以用setInterval来实现。

play() 开始播放音频。
pause() 暂停当前播放的音频。

这两个方法没什么好介绍,最核心的两个方法。当然,在点击播放按钮时除了触发play()方法,同时还需要做一些页面处理,比如讲上面提到的转盘 animation-play-state设置为running,将磁针的css class切换为resume-needle,从而实现一个视觉上开始播放的效果。暂停的话就将这些设置成相反的即可。

src 设置或返回音频的 src 属性的值。

设置音频源,这个在网易返回的json中会有的,之前代码提到过,
ctx.playList = data.result.tracks;
这个播放列表中每一项就是一首歌的所有信息,比如ctx.playList[0].mp3Url就是第一首歌的地址,把它直接赋值给audio的src属性就行了,这个主要在下一首上一首中有用到,同时需要ctx.playList[0].album.picUrl得到图片信息更新到页面上的转盘跟背景。为了方便,我代码中的ctx.currentSong就是存储当前播放歌曲的json信息。

ended 返回音频的播放是否已结束。

这个事件在音乐播放结束后触发,用于音乐结束后自动加载下一首:
[javascript] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-family:Courier New;font-size:14px;">ctx.player.addEventListener('ended', ctx.next);</span>  
ctx.next就是点击下一首时触发的方法,也就是说,当一首歌播放结束就让它播放下一首。当然这个逻辑也不能绝对,如果加入了循环设定(单曲循环、顺序播放、列表循环)的功能后,逻辑可能就要发生改变,暂时采用的就是列表循环了。

用到的API就这几个了,更多的API请参考http://www.w3school.com.cn/jsref/dom_obj_audio.asp
如果想对音频做高级的设置,比如画个频谱图,做个均衡器之类的,就不能用audio这个现成的播放器了,需要用到AudioContext类,这个功能非常强大,可能后面会专门写一篇博客来做些介绍。

5.源代码

本来是准备贴博客里,后来觉得还是托管到github吧,以下是github地址,有不太明白的请在留言中回复即可。

其中用到JQuery,纯粹为了用一些功能函数,毕竟原生的写起来比较麻烦。个人觉得ajax方法很方便,我以前也用Xhr实现过跟JQuery ajax方法差不多的一个函数,要考虑的东西挺多,代码量也不少,所以个人比较推荐用JQuery.但是使用JQuery需要多注意一些规范,比如我代码中将很多JQuery对象存在了ctx中,这些元素都是要频繁调用的,这样就不用多次通过选择器去得到某个元素,从代码清晰及性能方面考虑都要注意这点。链式调用也是JQuery的特点,所以尽量使用它来做到代码体积减小,简洁易读。
写代码尽量使用命名空间,这就是ctx这个对象的作用。虽然这个播放器属于一个页面,而不是一个插件,如果是插件,就需要将方法都封装到类里面。放命名空间主要为了不会污染其他函数或变量,不会跟其他代码造成冲突,用命名空间是写js一个需要养成的习惯。
大家可以通过开发者模式进行Demo的研究,后续很多新特性会加入。


欢迎大家有什么想法就在下面留言,大家一起成长。
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值