Vue网易音乐歌词处理滚动高亮

1,018 阅读5分钟

Vue网易音乐歌词处理滚动高亮

对于网易歌词数据我们要先几个步骤处理歌词,并解析拿到我们所需要的数据,因为播放器是全局的,数据就存放再vuex里,拿取数据也方便,其中要对vuex要用一定的了解,vuex中的数据,方法都是通过映射拿到

Gitee项目地址

效果图,部分功能还未完善

QQ截图20230330145033.png

歌词处理

  1. 获取歌词

  2. 处理歌词,拿到想要的数据

  3. 渲染

    (1)调用网易的歌词api用axios获取数据,因为歌词获取异步,我就把调用写在了vuex中,后续调用更方便,实现复用性

    调用formatLyr方法,参数是获取的歌词并把方法返回解析后的歌词存储到state中,下方的数据处理借鉴于黑马程序员

    actions:{
    async songlyric (context) {
        //lyricAPI 是封装的axios
          const { data: res } = await lyricAPI({ id: context.state.playList[context.state.playListIndex].id })
          context.commit('updatelyric', await this.dispatch('formatLyr', res.lrc.lyric))
        }
    }
    

    (2)定义方法处理歌词 ,处理歌曲时间

       actions:{
    formatLyr (context, lyricStr) {
          // 可以看network观察歌词数据是一个大字符串, 进行拆分.
          const reg = /\[.+?\]/g //
          const timeArr = lyricStr.match(reg) // 匹配所有[]字符串以及里面的一切内容, 返回数组
          // console.log(timeArr) // ["[00:00.000]", "[00:01.000]", ......]
          const contentArr = lyricStr.split(/\[.+?\]/).slice(1) // 按照[]拆分歌词字符串, 返回一个数组(下标为0位置元素不要,后面的留下所以截取)
          // console.log(contentArr)
          const lyricObj = {} // 保存歌词的对象, key是秒, value是显示的歌词
          const songSet = []
          timeArr.forEach((item, index) => {
            // 拆分[00:00.000]这个格式字符串, 把分钟数字取出, 转换成秒
            const ms = item.split(':')[0].split('')[2] * 60
            // 拆分[00:00.000]这个格式字符串, 把十位的秒拿出来, 如果是0, 去拿下一位数字, 否则直接用2位的值
            const ss = item.split(':')[1].split('.')[0].split('')[0] === '0' ? item.split(':')[1].split('.')[0].split('')[1] : item.split(':')[1].split('.')[0]
            // 秒数作为key, 对应歌词作为value
            lyricObj[ms + Number(ss)] = [contentArr[index], ms + Number(ss)]
            songSet.push(ms + Number(ss))
            // lyricObj[item] = ms + Number(ss)
            // lyricObj.push({ms + Number(ss)})
          })
          // 返回得到的歌词对象(可以打印看看
          context.commit('updateCurLyric', lyricObj[0][0])
          context.commit('updateSongTime', songSet)
          return lyricObj
        },
            // 格式化播放时长  歌曲时间处理,获取的时间是毫秒
        formatDt (context, time) {
          const dt = time / 1000
          let m = parseInt(dt / 60)
          let s = parseInt(dt % 60)
          m = m >= 10 ? m : (m = '0' + m)
          s = s >= 10 ? s : (s = '0' + s)
          context.commit('updateSongFore', m + ':' + s)
        }
        }
    

    (3)下面是在定义在state的变量

    lyric: {}, // 歌词枚举对象(需要在js拿到歌词写代码处理后, 按照格式保存到这个对象)
    curLyric: '', // 当前显示哪句歌词
    lastLy: '', // 记录当前播放歌词
    currenTime: '', // 当前播放时间
    songTime: '', // 歌曲歌词对应时间
    songFore: '' // 歌曲时长

(4)定义在mutations中的方法改变state

mutations:{
    updatelyric (state, val) {
      state.lyric = val
    },
    updateCurLyric (state, val) {
      state.curLyric = val
    },
    updateLastLy (state, val) {
      state.lastLy = val
    },
    updateCurrenTime (state, val) {
      state.currenTime = val
    },
    updateSongTime (state, val) {
      state.songTime = val
    },
    updateSongFore (state, val) {
      state.songFore = val
    }
}

数据拿到后可以打印看看解析后的歌词,是以对象存储,key就是每句歌词对应的时间,后面高亮更加方便查询

歌词.png

渲染歌词

:class="{ acter: (currenTime >= item[1]) }" 给已播放的歌词添加样式,当前播放时间大于或等于这句歌词的时间

:style="{ 'font-size': (currenTime >= item[1] && currenTime < songTime[(Number((Object.keys(lyric)).indexOf(i))) + 1]) || curLyric[1] === item[1] ? '.5rem' : '' }" 这是当前正在播放的歌词添加字体变大样式 因为拿到的歌词有些时间对应的歌词为空,所以还要添加判断

    <div v-show="show" class="songLyric" @click="show = !show">
      <p v-for="(item, i) in lyric" :key="i" :indeX=i :class="{ acter: (currenTime >= item[1]) }"
        :style="{ 'font-size': (currenTime >= item[1] && currenTime < songTime[(Number((Object.keys(lyric)).indexOf(i))) + 1]) || curLyric[1] === item[1] ? '.5rem' : '' }">
        {{ item[0] }}</p>
    </div>

获取当前播放时间

给audio定义一个ref,拿到当前时间并返回vuex 监听了音频播放器的timeupdate事件,以获取当前播放时间。然后,它使用当前时间作为索引来获取对应的歌词。如果当前时间对应的歌词不为空,它会将其更新为当前歌词。如果当前时间对应的歌词为空,它会将上一个非空歌词更新为当前歌词。这样,就可以实现在播放过程中实时切换歌词的效果

    showLyric () {
      let curTime
      // 监听播放audio进度, 切换歌词显示
      this.$refs.audio.addEventListener('timeupdate', () => {
        // 进度
        curTime = Math.floor(this.$refs.audio.currentTime)
        this.updateCurrenTime(curTime)
        // 避免空白出现
        // console.log(this.lyric[curTime])
        if (typeof this.lyric[curTime] !== 'undefined' && this.lyric[curTime][0] !== String('\n')) {
          this.updateCurLyric(this.lyric[curTime])
          this.updateLastLy(this.curLyric)
        } else {
          this.updateCurLyric(this.lastLy)
        }
      })
    },

现在完成大概就是这样,页面布局看个人

1.png

歌词随页面随时间滚动

因为技术有限我的方法是改变父元素的scrollTOP,原则上vue是避免操作DOM的,有人的方法是设置TOP,但是设置了TOP后,就不能自己滚动页面了

歌词随时间滚动,首先就要监听我们拿到的当前播放时间,在watch中监听

因为curLyric中存储的是当前播放的歌词还有当前时间,判断当前时间于当前歌词时间,因为有些歌曲的时间有重复,所以我们先对songTime去重,再用foeEach循环判断,这里用some应该还要好点,看个人选择。(-200 + index * 40)其中-200是刚好到屏幕中间的位置,这个因人而异不重要,index当前判断为true的下标,相当于前面已经播放了多句歌词,再*每句歌词的高度,我高度给的是四十

  watch: {
    currenTime () {
      // 先去重
      [...new Set(this.songTime)].forEach((element, index) => {
        if (element === this.curLyric[1]) {
          document.querySelector('.songLyric').scrollTop = -200 + index * 40
        }
      })
        
        //下面item是当前播放的时间转换成 00:00格式
      let m = parseInt(this.currenTime / 60)
      let s = parseInt(this.currenTime % 60)
      m = m >= 10 ? m : (m = '0' + m)
      s = s >= 10 ? s : (s = '0' + s)
      this.item = m + ':' + s
        //判断是否播放完,然后执行下一首方法
      if (this.item === this.$store.state.songFore) {
        this.nextSong()
      }
    }
  },

歌曲详情页功能基本已经实现,第一次写这个,技术有限,还请见谅,有兴趣可以去顶部看看源码