音乐歌词滚动
今天完成了一个简单的音乐播放器,记录一下完成歌词滚动的思路。 歌曲的数据结构:
[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的数据结构如下
dom结构如下
// 当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')
}