Skip to content

QdabuliuQ/cloudMusic-App

Repository files navigation

网易云音乐

基于 Vue.js 开发的高仿项目。

安装运行(安装运行前请确定已安装node环境)

  • 环境安装:npm install
  • 启动服务:npm run dev
  • 发布代码:npm run build

目前以实现的功能:

  • 用户登录
  • 用户关注列表/最近播放歌曲
  • 用户基本信息/歌单/歌曲/昵称/头像...
  • 获取用户的歌单基本信息/歌曲
  • 播放歌曲/歌曲封面旋转/歌曲歌词实时滚动
  • 拖动进度条跳转歌曲播放时间
  • 歌曲评论/上拉加载更多评论
  • 歌曲分享面板
  • 首页轮播图
  • 歌曲MV播放
  • 进度条实时更新
  • 时间实时更新
  • MV评论加载
  • 更多MV推荐
  • 我的电台
  • 我的电台首页
  • 电台分类
  • 电台分类列表
  • 电台详情
  • 电台播放
  • 电台评论
  • 电台封面简介动画切换
  • 电台播放时间实时更新/跳转
  • 电台排行
  • 个人信息界面/个人基本信息/歌单/动态/关注粉丝列表
  • 歌手信息界面/歌曲/简介/专辑/MV/动态/关注粉丝列表
  • 首页
  • 歌单广场
  • 更多音乐
  • 更多专辑
  • 搜索(已有功能:搜索建议,热搜列表,搜索结果分类,搜索建议跳转)
  • 视频/视频分类/分类切换/视频播放/视频全屏播放(全屏模式下暂时无法拖动进度条跳转)
  • 发表评论(歌曲、mv、歌单、专辑、电台、视频、动态、热评墙)以上都支持
  • 热评墙
  • 热门话题
  • 左侧菜单

项目要点:

  • 1、歌曲播放界面进度条的实时更新,需要获取到歌曲的时间,计算进度条在每一秒中需要移动多少PX。
  • 2、歌曲歌词界面,歌词的实时滚动,通过API请求获取歌词数据,再通过正则表达式分别筛选出歌词和每一行歌词的时间,当歌词播放到一个时间点会进行判断,并进行歌词滚动。
this.$store.state.navMusicDom.addEventListener("timeupdate", () => {
  // 判断有没有歌词
  if (this.lyricText.length !== 0) {
    // 判断歌曲时间在歌词区间
    if (
      this.$store.state.navMusicDom.currentTime >=
        this.songLyric[this.index] &&
      this.$store.state.navMusicDom.currentTime <=
        this.songLyric[this.index + 1]
    ) {
      var huiche = /^\n/; // 正则匹配回车符
      if (huiche.test(this.lyricText[this.index])) {
      } else {
        if (document.getElementsByClassName("active")[0] !== undefined) {
          if (
            document.getElementsByClassName("itemSpan")[this.zindex]
              .offsetTop >
            document.getElementsByClassName("LyricListContent")[0]
              .clientHeight /
              2
          ) {
            this.yscroll =
              document.getElementsByClassName("itemSpan")[this.zindex]
                .offsetTop -
              document.getElementsByClassName("LyricListContent")[0]
                .clientHeight /
                2 +
              document.getElementsByClassName("itemSpan")[this.zindex]
                .clientHeight /
                2;
            this.$refs.list.style.transform =
              "translateY(" + -this.yscroll + "px)";
            this.$refs.list.style.transition = "0.3s linear";
          }
        }
        this.zindex++;
      }
      this.index++; // 索引++
      this.activeIndex++; // 歌词索引++
    }
  }
});

在拖动进度条的时候,歌曲也需同步滚动,通过给 video 标签绑定 onseeked 事件,来进行滚动。

// seeked 事件 在进度发生改变的时候触发
this.$store.state.navMusicDom.addEventListener("seeked", () => {
  if (this.$store.state.navMusicDom.currentTime < 1) {
    this.index = 0; // 清空索引
    this.zindex = 0;
    this.activeIndex = 0;
    this.yscroll = 0; // 清空滚动距离
    this.$refs.list.style.transform = "translateY(0px)"; // 滚动回顶部
  } else {
    // for 循环遍历歌词数组  songLyric歌词时间数组
    for (let i = 0; i < this.songLyric.length; i++) {
      // 判断滚动到哪个歌词区间
      if (
        // 获取当前video播放到的currentTime
        // 遍历歌词时间列表  找出所对应的时间段
        this.$store.state.navMusicDom.currentTime >= this.songLyric[i] &&
        this.$store.state.navMusicDom.currentTime < this.songLyric[i + 1] &&
        // 判断歌词是否滚动到了中间  滚动到了中间才会进行歌词跳转
        document.getElementsByClassName("itemSpan")[i].offsetTop > document.getElementsByClassName("LyricListContent")[0].clientHeight / 2
      ) {
        // 获取目标歌词距离顶部的距离  -  LyricListContent 元素的中间线
        let scroll = document.getElementsByClassName("itemSpan")[i - 2].offsetTop - document.getElementsByClassName("LyricListContent")[0].clientHeight / 2
        // 移动到中间线的位置后加上  目标歌词元素的高度 / 2 使其居中
        this.yscroll = scroll + document.getElementsByClassName("itemSpan")[i - 2].clientHeight / 2
        // 赋值给滚动元素
        this.$refs.list.style.transform =
          "translateY(" + -this.yscroll + "px)";
        this.activeIndex = i;  // 高亮索引
        this.index = i;
        this.zindex = i;  // 自动滚动索引
        break;
      }
    }
  }
});
  • 3、MV播放界面的推荐内容,当点击推荐内容的时候需要进行跳转,可以通过 beforeRouteUpdate 来监听路由变化
beforeRouteUpdate(to, from, next) {
  if (to.fullPath != from.fullPath) {
    this.mvDetail = {}; // 清空mv数据
    this.simiMv = []; // 清空相关视频
    this.commentList = []; // 清空评论内容
    this.CommentLength = 0; // 重置评论数量
    this.showDetail = false;
    next();
    this.MvDetail(); // 获取mv信息
    this.getMvUrl(); // 获取mvurl
    this.getCommentList(); // 获取mv评论
  }
},
export function debounce(func, delay){
  let timer = null;
  // ...args 表示可以接受多个实参  并保存到数组中
  // return 返回一个函数
  return function(...args){
    // 清除上一次的延时调用
    if(timer) clearTimeout(timer)
    // 开启延时调用
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}
  • 5、点击搜索建议的时候,也需要重新刷新 SearchDetail 组件内容,可以通过给 SearchDetail 组件绑定 key属性,当点击搜索建议列表的某一项的时候,更新 key 属性的值刷新组件。
<div class="SearchDetail" :key="i"></div>
  • 6、在完成该项目也遇到一些从没有遇到的错误,例如:Invalid default value for prop “xxxxx“,在进行父子组件传值的时候报了这样的一个错。原因很简单,设置默认值的时候需要以函数的形式作为返回值 return
// 正确写法
List: {
type: Array,
default: ()=>[]
}
// 错误写法
List: {
type: Array,
default:[]
  • 6、在开发歌曲播放页面功能的时候,必须要 APP.js 中exclude属性添加进去该组件,保证每次切换歌曲的时候不会一直显示第一次点击的页面,但在退出歌曲播放页面组件的时候该页面会自动销毁,不会继续播放。所以在 APP.js 根组件中引入了 Audio 标签,在根组件中进行播放,并通过 VUEX 进行保存相关数据,不会被销毁。
  • 7、视频全屏模式本质上是创建一个新的组件,并通过 CSS3 transform:translate 旋转整个DIV达到横屏播效果,在引入滑动组件的时候由于整个DIV元素旋转导致拖拽进度条的时候会出错。全屏模式下暂不支持拖拽进度条。
  • 8、父组件向子组件传值用 prop(属性传递),子组件向父组件传值用 $emit(事件传递),在兄弟组件传值的时候可以使用事件总线。
// 创建事件总结总线
Vue.prototype.$EventBus = new Vue()  // 在全局中创建

// 兄弟组件一:
this.$EventBus.$emit("decresed", () => {})  // $emit 发送事件总线,监听 decresed 频道,也可以在回调函数中传递参数

// 兄弟组件二:
this.$EventBus.$on("decresed", () => {})  // $on 接收事件总线,监听 decresed 频道。

About

基于Vue2开发的网易云音乐移动端

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published