最近,通过一个星期的时间,完成了一个仿照网易云音乐的自制音乐网页播放器,这个播放器实现了以下的功能:
1.后台有上传歌曲、编辑歌曲功能。
2.前端页面自动更新播放热度高的歌曲
3.在线听歌、查看歌词。且配有相应的播放动画。
以下是该应用的预览链接,直接点击预览链接即可打开。
该应用主要是使用了jQuery以及MVC模块化的思想来完成的,因此在介绍这个应用的制作思路和流程之前,我想重新总结一下对模块化和MVC的理解。
何为模块化
我的理解中,模块化是通过MVC的V,也就是View来划分的,把页面中看得见的区域进行功能划分,每一个功能不同的区域就是一个分开的模块,模块之间是通过命名空间或者说事件中心eventHub来进行联系的,这种联系方式的好处就是可以任意地跨模块进行信息的交流,页面中的任意模块都能与另一任意模块进行交流,只要它们绑定同样的事件就可以了。但缺点也很明显,就是事件中心是全局环境下的事件中心,如果一个事件触发了两个模块来发布相同的事件,那就会不可避免地产生冲突,这个时候只能通过改变其中一个模块的发布事件的事件名来消除这种冲突,这显然并不是一种理想的解决办法,因为本质上他们就是相同的事件,给相同的事件不同的命名的做法并不恰当,因此这也是我认为的MVC模块交流方式的缺点。
以下是eventHub的代码:
window.eventHub={
events:{},
emit(eventName,data){
for(let key in this.events){
if (key===eventName){
let fnList=this.events[key]
fnList.map((fn)=>{
fn.call(undefined,data)
})
}
}
},
on(eventName,fn){
if (this.events[eventName]===undefined){
this.events[eventName]=[]
}
this.events[eventName].push(fn)
}
}
何为MVC
其次MVC有三个部分,分别为M(Model),V(View)和C(Controller),通过面向对象编程的思想,我们可以把这三个部分变成对象,然后在对象中进行相对应的操作。
前面讲到了View就是当前模块所代表的看得见的功能划分部分,View一般需要你指定这个部分在HTML对应的元素的ID,例如我把一个歌曲列表当作一个模块,那么一般这个列表会在HTML以一个ul标签的形式表示,那么我们在使用MVC时,就要给View指定这个ul标签的ID作为这个MVC模块所要操控的区域的ID。一般在对象View里还有属性template来表示这个功能区域的HTML模版,然后通过View对象里面你设置的函数去改变这个template,例如增减标签的类名,例如把之后Model对象里面的数据data内容放入标签中等等,然后通过render()
函数来更新功能区的HTML代码,使页面视图发生相对应的改变。总的来说,View要做的事情,就是改变网页的用户视觉,去把代码改变的内容以直观的方式呈现在网页中,需要切记的是每个MCV模块中View所代表的区域之间是不能互相交叉的,因此在进行模块化时要明确好每个模块的职责。
以下是一个简单的View对象的代码例子:
let view={
el:'#功能区域',
template:`
<div>HTML模版</div>
`,
render(){
$(this.el).html(this.template)
}
}
然后是Model所代表的是当前模块所包含的数据以及操作数据的方法,继续以歌单模块为例的话,那么歌单中的每个歌曲的ID、以及ID对应的歌曲名字和歌手就是我们所需要数据data,这些数据会存放在这个歌单模块的Model中,每当模块需要存储、获取或者更新数据,都要呼叫Model模块并由Model模块来执行这些操作。需要记住,每个模块的Model并不需要储存整个应用的所有数据,而是只需储存这个模块所对应的必须要的数据即可,例如歌单模块因为需要让用户知道每一首歌是什么,因此需要歌名以及歌手的数据。而因为要和其他模块进行交流,例如让其他模块知道用户是否点击了某首歌,因此还需要储存歌曲的ID,以方便之后歌单模块通过事件中心把点击的歌曲的ID数据传递给其他的模块。歌单模块并不需要存储歌词、歌曲封面这些数据,是因为这个模块并不展示和操作这些数据,而其他模块可以通过歌单模块传来的ID去获取这些数据。这篇文章介绍的音乐播放器的应用就是Model配合LeanCloud数据库和七牛数据库实现的。
以下是一个简单的Model对象的代码例子:
let model={
data:{},
fetch(){......},
save(){......},
update(){......}
}
最后是Controller,以前我对这部分的职能了解的并不是很清楚,现在我开始略有体会了,Controller代表的是控制当前模块在不同的时刻所进行的操作,比如,Controller对象里一般都会有init方法、bindEvents方法和bindEventHub方法。init方法意思就是在模块初始化的时候,需要做些什么,因此我们会在init方法里面初始化view、初始化model,进行事件绑定,进行事件发布订阅中心的事件订阅等等,这些就是我们在模块初始化时要做的事情。然后在元素触发事件时模块需要做什么,在其他模块发布事件后模块需要做什么,都分别反映在了Controller的bindEvents方法和bindEventHub方法中,因此Controller就像一个控制塔,有条不紊地在合适的时候处理着合适的事情,是统筹Model和View的中心。
以下是一个简单的Controller对象的代码例子:
let controller={
init(view,model){
this.view=view
this.model=model
this.bindEvents()
this.bindEventHub()
},
bindEvents(){......},
bindEventHub(){......}
}
总的来说,MVC就是一种代码的组织思想,View代表功能区视图管理着与直观内容有关的变化,Model则作为数据中心管理着该视图的所有数据,Controller则作为控制中心管控着View和Model的运作时机和运作方式。
相信你看完以上我对模块化和MVC的理解之后,会帮助你更好地梳理接下来我要介绍的音乐播放器的思路。因为这个应用涉及的代码很多,所以我只能介绍重要的思路,以及会在最后说一下在制作过程中遇到的几个坑以及解决这个坑的思路的做法。
网易云音乐的制作思路
当你有了制作某个应用的想法,你第一件要做的事情是什么,不是直接写代码,而是分析这个应用,我们可以通过以下三个图例来进行分析:
1.用例图(use cases)
分析当你身为用户或者应用管理员的时候,使用应用的时候需要什么的页面,页面需要怎么样的功能,这就是用例图会表达出来的内容。
例如音乐播放器这个应用,身为普通用户的话,我们可以查看首页、查看歌单页和歌曲页,歌曲页里面可以听歌、可以暂停以及可以查看歌词,我们还可以搜歌,可以搜歌来搜出歌手和歌曲名等等。
通过这些分析,你就会了解到你当前要制作的这个应用,他需要怎么样的功能以及每个功能应该出现在哪一个页面当中。
2.线框图(也叫草图,stretch)
线框图要展示的,就是你要制作的应用中,每个页面功能区的布局,也就是线框图会告诉你这个应用含有多少个页面,每个页面里有着哪些功能区,以及功能区的大体位置也能在上面体现出来。
3.系统架构图
系统架构图展示的是在该应用中,前端页面、后端页面以及数据库中使用的是什么工具,例如音乐播放器中,前端页面使用的是jQuery,后端页面使用的是LeanCloud提供的API,数据库使用的是LeanCloud和七牛,以及这三者之前的交互方式,例如前端页面和后端页面的交互方式是通过AJAX来进行的。
好了,进行完了上面的分析,我们对这个音乐播放器的应用就有了大概的认识,作为普通用户和管理员两种不同的角色,我们应当设计两个页面,管理页面供管理员去管理音乐播放器中的音乐信息,管理页面应当提供上传歌曲、编辑歌曲以及删除歌曲的功能,每首歌曲管理员应当有权限去设置歌曲的歌名、歌手、封面、歌曲链接以及歌词信息,这样,管理员就能通过这个页面去管理用户页面中展示的歌曲了。管理页面具体要如何实现,我在这里就不具体叙述了,只需你熟练掌握MVC的基本操作,然后能够读懂七牛文档以及LeanCloud文档后使用相关的API,就可以轻松地把这个后台管理页面做出来了。这里给大家展示一下一个最丑陋的后台管理系统以及它的模块化划分:
那么用户呢,用户页面要怎么设计和制作呢,准确来说,用户页面应该有三个:音乐播放器首页、歌单页、歌曲播放页,但这次应用因为时间冲忙,因此没有制作歌单页,所以我们把关注点放在首页和歌曲播放页就好,首先应该首页应该有如下功能:歌曲名和歌手名字的展示,歌单名和封面的展示,这些内容全都通过LeanCloud的API来获取即可,因此也不详细地去说了。
除了首页,还有一个歌曲播放页,这个页面的话设计到歌曲的封面,歌词,歌名。我来考读者一个问题,这个歌曲播放页如何才能获取到这些数据和信息呢?如果你想的是在LeanCloud数据库里遍历来查找那就不对了,正确的做法应该是,用户在首页中点击了想听的歌曲,之后调整到歌曲播放页面,把该页面的url后面的查询参数设置成你点击的歌曲的ID,那么我们在播放歌曲页面中只需要通过捕获url上面的这个查询参数,即可获得该歌曲的ID,之后再用这个ID在LeanCloud上获取对应的数据展示在页面即可,这样歌曲的封面、歌词、歌名以及歌手等全部信息我们都能获得得到。
关于音乐播放器的样式问题,这是需要读者自己去花时间去寻找优秀的设计模板,并进写模仿、修改和编写才能得到的内容,因此就不在这里进行阐述了。
目前我所制作的样式效果大致如下:
音乐播放器首页.png
歌曲播放页面.png
关于歌单页面的构想:
歌单页面由于时间不够充裕的关系,因此没有去进行制作,但是我在这边是有我自己的构想的,首先要制作歌单页面首先你得有一个对应的歌单后台管理页面,这个才是歌单页面的重点,歌单页面只需拿到歌单的ID,就可以在LeanCloud获取到对应的歌单信息了。因此怎么给歌单录入信息,也就是怎么设计歌单的后台管理页面才是重点。因此我觉得首先由于应用的限制,我们可以限定仅有六个歌单,只需反复对这些歌单进行编辑即可,也就是说这样删除了歌单的添加和删减功能,简化了歌单管理页面,而在歌单管理页面中,我们仅需要进行的操作,就是创建一个歌曲列表,列表里是目前数据库中已上传的歌曲,管理员可以对这些歌曲进行选择,之后把选择了的歌曲添加到对应的歌单即可。之后再设置好歌单的标题、封面和描述,就可以完成对一个歌单的管理了。关于歌单页,之后有时间我会一并补充到自己的播放器中,敬请期待吧。
最后想说一下在这次作品的制作中遇到的一些坑,以及解决这些坑的办法:
1.由于使用了移动端不支持的ES6语法导致的BUG
在进行音乐作品播放器的制作过程中,我在一些地方使用了ES6的新语法
...
,这个语法的表示的是当前对象的所有属性,如:
let attributes={name:"xzb",age:18}
let obj={id:1,...attributes}
console.log(obj) // {id:1,name:"xzb",age:18}
这个语法真的相当方便,但是当你在移动端使用它的时候,悲剧了....语法错误,移动端不支持它吖!!!
后面只能使用Object.assign方法来代替它了。
解决办法:使用Object.assign()
2.无法对移动端进行调试
第二个坑是由第一个坑衍生出来的,在发现第一个坑之后,我的第一个反应是,十分无奈,为什么这么说呢?哥,你在PC端出错,我还能通过控制台来看看出错的地方在哪,你在移动端出错,我.....
好吧,只能靠万能的互联网了,在一番资料的查询之后,我得到了想要的解决办法,有以下四个:
(1)通过alert()来进行检验
虽然说移动端没有控制台,但移动端还是可以alert的吖,因此我们只要在我们认为出错的地方的前后进行alert(),若发现前面的alert运行了,后面的alert没有运行,那么恭喜你,你的猜测是正确的,出错的地方就是此处。通过这种办法我们就可以在移动端知道自己出错的地方了。
(2)通过全局的onerror来进行检验
通过第一种方法我们的确可以在不断的尝试下知道出错的地方,但是这样效率太低下了,于是我们有第二种办法,通过监听全局的error来显示错误,以及显示错误的出处。代码如下:
<script>
window.onerror=function(message,file,row){
alert(message,file,row)
}
</script>
onerror接受四个参数,第一个是出错信息,第二个出错文件,第三个是出错的行数,第四个是出错的列数,由于列数在此处对我没什么太大的作用,因此在上述代码中我把它省略了。
(3)自己手写一个console函数法
自己在页面上写一块console的区域,然后把console的值直接显示在区域内即可,具体代码如下:
<div id="consoleOutput" style="......"></div>
<script>
window.console={
log(x){
let p=document.createElement('p')
p.innerText=x
consoleOutput.appendChild('p')
}
}
</script>
(4)直接引入腾讯制作的vConsole库
直接引入腾讯制作的vConsole库,就可以在移动端拥有一个console了,但是需要记住在调试完之后记得删掉这些调试工具,以免出现在用户使用的页面中。
3.由于IOS的移动端不支持animation-play-state语句而导致的BUG
在我对移动端中的所有报错都进行了修复之后,我在歌曲播放页面又发现了一个新的BUG,那就是在点击光盘进行播放的时候,光盘没有如我代码所写那样进行播放,百思不得其解之下,我发现网上有许多人和我出现了类似的BUG,于是乎我就去寻找出现这种现象的原因,以及解决的办法,最后我发现原来是IOS不支持animation-play-state语句而导致的,而在安卓的移动端上则不会出现这样的BUG。
如何是好,没有了animation-play-state,我就无法去控制CSS3动画的播放和暂停,如果单纯的用animation:none;
来控制,就会出现动画每次都重头开始进行的错误效果,后来我也尝试了animation-fill-mode: forwards;
来尝试让动画每次停在最后的状态,但由于歌曲的暂停是随机的,而不是由动画是否播放完毕来决定是否暂停的,因此这个方法也是行不通的,怎么办好。我不断的尝试,不断地搜索资料,最后我看到了一个网上的解决方案:给光盘所在元素添加一个父元素,当每次点击光盘暂停歌曲播放时,用父元素记录一下光盘每次暂停时transform的值,并让父元素的transform也等于这个值,若transform本来就有值,那就在transform后面更新这个值,就可以完成歌曲暂停,光盘动画暂停,歌曲继续开始,光盘动画继续开始的效果。
具体的实现代码如下:
recordTransform(){
let coverTransform=this.view.$el.find('.coverWrapper').css('transform')
let coverWrapperTransform=this.view.$el.find('.coverWrapperParent').css('transform')
let transformDeg=coverWrapperTransform==='none'?coverTransform:coverTransform.concat(' ',coverWrapperTransform)
this.view.$el.find('.coverWrapperParent').css('transform',transformDeg)
}
4.由于IOS微信不支持webp格式图片所引起的BUG
由于我的音乐播放器中的歌单用的是webp文件的封面图片,这个格式的图片是谷歌开发的一种旨在加快图片加载速度的图片格式。WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。
可惜IOS上的微信就是不支持这种格式的图片,因此没有办法,我最后解决这个BUG的方案就是把webp图片全部转换成了JPG然后重新上传至应用中。
解决办法:更换应用图片格式
好了,写了那么多,终于算是介绍完了如何仿照网易云音乐自制一个音乐网页播放器,当我做完这个应用的时候,我觉得自己对MVC的理解和使用都更为熟练了,接下来我会专心地研究和Vue相关的应用,并继续给大家带来完成后的心得和感想,希望能对你们有所帮助~
最后的最后,说一下目前打算写但还没写的两篇博客:(1)JSONP的安全问题你了解过吗?(2)Cookie、Session、localStorage、sessionStorage、Cache-Control、Expires以及ETag是什么?
网友评论