原生js解决音乐歌词滚动效果

129 阅读1分钟

音乐歌词滚动

今天完成了一个简单的音乐播放器,记录一下完成歌词滚动的思路。 歌曲的数据结构:

[00:01.75]真的吗
[00:04.50]演唱:莫文蔚
[00:08.23]
[00:32.43]离别的话我不想要
[00:36.80]现在清楚听见
[00:40.85]而你可以不用回答
[00:44.72]什么都不要说
[00:48.68]我的笑容没有快乐
[00:52.59]是否看得出来
[00:56.57]昏黄的天别告诉我
[01:00.48]快下起雨了
[01:03.69]
[01:08.27]清晨的风贴在脸上
[01:12.23]吹干落下的泪
[01:16.28]一整夜的思绪难眠
[01:20.14]等待你的回音

解决思路

首先我们拿到是是字符串,我们需要将它转换为数组,它的时间可以和audio.currentTime做比较,来判断是否需要滚动。我们还需要给歌词所在的DOM节点增加一个 data-time='歌词时间',方便我们知道当前的歌词是哪个。

歌词滚动,当前歌词(距离顶部位置 - 歌词列表区域的高度)大于0则滚动减去的距离

代码解析

//lyricsArr存放歌词,lyricIndex是当前歌词下标
 this.lyricsArr = []
 this.lyricIndex = -1
setLyrics(lyrics) {
    this.lyricIndex = 0
    // 所有歌词添加到fragment中,再一次性推送到dom中
    let fragment = document.createDocumentFragment()
    let lyricsArr = []
    this.lyricsArr = lyricsArr
    lyrics.split(/\n/)
      .filter(str => str.match(/\[.+?\]/))
      .forEach(line => {
        let str = line.replace(/\[.+?\]/g, '')
        line.match(/\[.+?\]/g).forEach(t => {
          t = t.replace(/[\[\]]/g, '')
          let milliseconds = parseInt(t.slice(0,2))*60*1000 + parseInt(t.slice(3,5))*1000 + parseInt(t.slice(6))
          lyricsArr.push([milliseconds, str])
        })
      })
      lyricsArr.filter(line => line[1].trim() !== '')
       .sort((v1, v2) => v1[0] > v2[0] ? 1 : -1)
       .forEach(line => {
        let node = document.createElement('p')
        node.setAttribute('data-time', line[0])
        node.innerText = line[1]
        fragment.appendChild(node)
       })
       this.$('.panel-lyrics .container').innerHTML = ''
       this.$('.panel-lyrics .container').appendChild(fragment)
  }

lyricsArr的数据结构如下 image.png

dom结构如下 image.png

// 当currentTime更新时会触发timeupdate事件
this.audio.ontimeupdate = function() {
      self.locateLyric()
    }
// 判断歌词是否需要滚动
locateLyric() {
    if (this.lyricIndex === this.lyricsArr.length - 1) return
    let currentTime = this.audio.currentTime * 1000
    let nextLineTime = this.lyricsArr[this.lyricIndex + 1][0]

    if (currentTime > nextLineTime && this.lyricIndex < this.lyricsArr.length - 1) {
      this.lyricIndex++
      let node = this.$(`[data-time='${this.lyricsArr[this.lyricIndex][0]}']`)
      // 歌词滚动
      if (node) this.setLyricToCenter(node)
      // 歌词挂到主界面
      this.$$('.panels .panel-effect .lyrics p')[0].innerText = this.lyricsArr[this.lyricIndex][1]
      this.$$('.panels .panel-effect .lyrics p')[1].innerText = this.lyricsArr[this.lyricIndex+1] ? this.lyricsArr[this.lyricIndex+1][1] : ''
    }
  }
 // 歌词滚动,当前歌词(距离顶部位置 - 歌词列表区域的高度)大于0则滚动减去的距离
  setLyricToCenter(node) {
    let translateY = node.offsetTop - this.$('.panel-lyrics').offsetHeight / 2
    translateY = translateY > 0 ? translateY : 0
    this.$('.panel-lyrics .container').style.transform = `translateY(-${translateY}px)`
    this.$$('.panel-lyrics p').forEach(node => node.classList.remove('current'))
    node.classList.add('current')
  }