前言
很多小伙伴刚学完 vue 却没项目拿来练习?今天带你们制作一个属于自己的网易云音乐,通过练习加深自己的掌握,提高技术能力
准备工作
我们需要用到 vuex,vue-router,axios,better-scroll,element-ui,less,vue-lazyload,node.js,NeteaseCloundMusicApi;axios 用来请求数据,better-scroll 用来歌词滚动,element-ui 组件库加快开发进度,less 方便 css 代码编写,vue-lazyload 用于图片懒加载,node.js 搭建的本地测试服务器,NeteaseCloundMusicApi网易云音乐 NodeJS 版 API,提供音乐数据,下面是我下载的版本,可以对应我的版本安装
我们将网易云音乐 NodeJS 版 API 下载下来到本地,终端 node app.js 运行,看到这一行代表运行成功了
首页
首页我们使用 elementui 组件库的 container 布局容器,采用这一种布局:
有相应的代码供我们使用,我们复制过来,给.el-container 设置一个 100vh 的高度
<el-container> <el-header>Header</el-header> <el-container> <el-aside width="200px">Aside</el-aside> <el-main>Main</el-main> </el-container> </el-container>
复制代码
el-header 头部设置图标和搜索框,具体样式可以按自己想法来做,但是必须要设置一个搜索框用于搜索音乐
el-aside 里加入导航菜单组件,设置 default-active 绑定当前激活的菜单的页面,设置 router 使用 vue-router 的模式以 index 作为 path 进行路由跳转,给每个菜单设置 index 绑定路由实现点击菜单切换页面。
<el-menu :default-active="path"//默认激活页面 background-color="#EDEDED" text-color="#939393" active-text-color="#DD6D60" :router="true"//开启路由模式><el-menu-item index="/discovery"> <!-- index路由的地址 --> <i class="iconfont icon-yinle1"></i> <span slot="title">发现音乐</span></el-menu-item><el-menu-item index="/playlists"> <i class="iconfont icon-danlieliebiao"></i> <span slot="title">推荐歌单</span></el-menu-item><el-menu-item index="/songs"> <i class="iconfont icon-yinle"></i> <span slot="title">最新音乐</span></el-menu-item><el-menu-item index="/mvs"> <i class="iconfont icon-24gf-playSquare"></i> <span slot="title">最新MV</span></el-menu-item></el-menu>
复制代码
最后给底部添加一个 audio 标签用于播放音乐
<div class="player"> <!-- 歌曲图片 --> <div class="bg"> <div v-if="this.songX.url" @click="showlyc"> <span class="iconfont icon-xiangshang2 top"></span> <img :src="this.songX.url" alt="" /> </div> </div> <!-- audio --> <audio id="music" ref="audio" @play="play" @pause="pause" @timeupdate="timeupdate" @ended="ended" :src="idd" controls autoplay ></audio></div>
复制代码
ok,首页制作完成,大概样子就是这样了:
功能页
我们将左边菜单的页面路由嵌套在首页里面,在页面右边的 main 区域里显示出菜单的页面,然后就可以实现菜单之间的页面跳转了
接下来就准备开始发现音乐菜单页的制作,发现音乐菜单页主要为我们呈现轮播图,一部分的推荐歌单,最新音乐和最新 mv,我们可以从 NodeJS 版 API 的文档找到我们需要请求数据的地址,使用 axios 发送请求获取轮播图获取数据,渲染在 elemenui 的 轮播图组件上
//获取轮播图数据getBanner().then((res) => { console.log(res); if (res.code != 200) return this.banners = res.banners;});
复制代码
其他功能根据不同的接口获取数据渲染到页面上就行了,其他的页面也是同理
搜索页面
播放音乐
获取音乐 url 我们使用/song/url接口,根据传入的 id 获取音乐的 url 地址,将获取到的 url 地址保存到 vuex 里,在 audio 标签里动态绑定 src 为保存在 vuex 里的 url 即可播放音乐
getSongUrl(id).then((res) => { // console.log(res); if (res.code != 200) return let url=res.data[0].url this.$store.state.url=url });
复制代码
歌词处理
获取音乐 url 的同时也要调用/lyric接口获取歌词,但是获取到的歌词不能直接渲染,我们可以看到获取到的歌词数据是一个字符串,里面保存着每个时间对应的歌词,我们要将时间和歌词分开,而且时间要转为 number
说说实现的思路,其实每句歌词之间都有一个\n换行符,我们使用 split('\n')将字符串分割成一个数组
let lycc = res.lrc.lyric; //获取歌词列表let lyclist = lycc.split("\n");//以换行来分割
复制代码
然后观察时间的格式,都是[xx:xx:xx]或[xx:xx:xxx]的格式,我们可以使用正则来对数组每一项进行匹配,然后使用 splice(1,-1)方法去掉[]这两个符号,再用 split(':')分割数组,将数组每一项转为 number 后相加,就得到了时间
let re = /\[\d{2}:\d{2}\.\d{2,3}\]/; //匹配时间let lyc=[]for (let i in lyclist) { if (lyclist[i]) { let date = lyclist[i].match(re); //匹配时间 console.log(date); date = date[0].slice(1, -1); //去除【】 let timelist = date.split(":"); //以:分割 let m = timelist[0];//分 let s = timelist[1];//秒 let time = parseFloat(m) * 60 + parseFloat(s); //计算时间 }}
复制代码
最后使用 replace()方法匹配正则取出歌词,将歌词和时间以数组的方式保存到一个数组里,存到 vuex 里
for (let i in lyclist) { if (lyclist[i]) { let date = lyclist[i].match(re); //匹配时间 console.log(date); date = date[0].slice(1, -1); //去除【】 let timelist = date.split(":"); //以:分割 let m = timelist[0]; let s = timelist[1]; let time = parseFloat(m) * 60 + parseFloat(s); //计算时间 let lrcitem = lyclist[i].replace(re, ""); //获取歌词 lyc.push([time, lrcitem]); this.$store.state.lyc=lyc }}
复制代码
处理后的歌词
歌词滚动
歌词滚动我们需要用到 better-scroll,better-scroll 最常见的应用场景是列表滚动,better-scroll 是作用在外层 wrapper 容器上的,滚动的部分是 content 元素。这里要注意的是,better-scroll 只处理容器(wrapper)的第一个子元素(content)的滚动,其它的元素都会被忽略
<div class="wrapper" ref="wrapper"> <ul class="content"> <li>...</li> <li>...</li> ... </ul> <!-- 这里可以放一些其它的 DOM,但不会影响滚动 --></div>
复制代码
打开歌词的时候延时 100 毫秒后创建 bScroll 对象
<!-- 歌词滚动 --><div class="wrapper" ref="scrolls"> <div class="contents"> <div :class="['items', { item_actice: lycindex == index }]" v-for="(item, index) in lycs" :key="index" > {{ item[1] }} </div> </div></div>
//歌词显示showlyc() { this.showl = !this.showl; setTimeout(() => { this.scroll = new BScroll(document.querySelector(".wrapper")); // console.log(this.scroll); }, 100);},
复制代码
创建完 bScroll 对象后怎么滚动呢?刚刚的处理完的歌词数组里每一句歌词都有对应的时间,我们根据当前播放的时间和数组里的时间进行匹配,当前播放时间大于当前歌词对应的时间小于下一句歌词对应的时间时使用 bScroll 对象里的scrollTo方法进行滚动
动态绑定 audio 的 timeupdate 事件,每触发一次就判定是否进行滚动
timeupdate(e) { let currentTime = e.target.currentTime; let lyc = this.lycs; for (let i = 0; i < lyc.length; i++) { if (lyc[i][0] < currentTime && currentTime < lyc[i + 1][0]) { this.lycindex = i; if (this.scroll) { this.$refs.scrolls.scrollTo(0, (this.lycindex - 6) * 40);//让当前播放歌词固定在中间 } } }},
复制代码
最后
由于时间有限只做了基本的播放暂停。循环随机顺序播放,登录等等很多功能都没有实现,有兴趣的小伙伴可以自己尝试实现,源码我放在了gitee里,欢迎来访🤞🤞🤞
评论