从0制作一个web端网易云

2,227 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

前言

很多小伙伴刚学完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,提供音乐数据,下面是我下载的版本,可以对应我的版本安装

image.png

我们将网易云音乐 NodeJS 版 API下载下来到本地,终端node app.js运行,看到这一行代表运行成功了

image.png

首页

首页我们使用elementui组件库的container布局容器,采用这一种布局:

image.png

有相应的代码供我们使用,我们复制过来,给.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头部设置图标和搜索框,具体样式可以按自己想法来做,但是必须要设置一个搜索框用于搜索音乐

image.png

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,首页制作完成,大概样子就是这样了:

image.png

功能页

我们将左边菜单的页面路由嵌套在首页里面,在页面右边的main区域里显示出菜单的页面,然后就可以实现菜单之间的页面跳转了

image.png

image.png

接下来就准备开始发现音乐菜单页的制作,发现音乐菜单页主要为我们呈现轮播图,一部分的推荐歌单,最新音乐和最新mv,我们可以从NodeJS 版 API的文档找到我们需要请求数据的地址,使用axios发送请求获取轮播图获取数据,渲染在elemenui的 轮播图组件上

image.png

//获取轮播图数据
getBanner().then((res) => {
    console.log(res);
    if (res.code != 200) return 
    this.banners = res.banners;
});

image.png

image.png

其他功能根据不同的接口获取数据渲染到页面上就行了,其他的页面也是同理

image.png

image.png

image.png

image.png

搜索页面

image.png

播放音乐

获取音乐url我们使用/song/url接口,根据传入的id获取音乐的url地址,将获取到的url地址保存到vuex里,在audio标签里动态绑定src为保存在vuex里的url即可播放音乐

image.png

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

image.png

说说实现的思路,其实每句歌词之间都有一个\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
  }
}

处理后的歌词

image.png

歌词滚动

歌词滚动我们需要用到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);//让当前播放歌词固定在中间
          }
        }
    }
},

17615979-b672-46b9-b67c-8a9fbdbc0b43.gif

最后

由于时间有限只做了基本的播放暂停。循环随机顺序播放,登录等等很多功能都没有实现,有兴趣的小伙伴可以自己尝试实现,源码我放在了gitee里,欢迎来访🤞🤞🤞