兔年给儿歌小白兔写一个歌词播放器

1,931 阅读2分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

效果展示

2023-01-26-13-29-47.gif

码上掘金地址

实现思路分析

image.png

  • 上边是简单的audio元素, 关键事件 @timeupdate, 约每秒触发一次
<audio @timeupdate="change_video"></audio>
  • 下边就是vue循环的一个列表
  <div class="container">
    <div class="list">
      <div v-for="(item,index) in list" class="li">
        {{item.words}}
      </div>
    </div>
  </div>
  • 可以从.lrc文件中获取
const lrc = `[00:00:00] 小白兔白又白 - 甜筒星球
[00:03:00] 词:佚名
[00:04:00] 曲:佚名
[00:07:00] 小白兔
[00:08:00] 白又白
[00:09:00] 两只耳朵竖起来
[00:11:00] 爱吃萝卜爱吃菜
[00:14:00] 蹦蹦跳跳 真可爱
[00:26:00] 小白兔
[00:27:00] 白又白
[00:28:00] 两只耳朵竖起来
[00:31:00] 爱吃萝卜爱吃菜
[00:33:00] 蹦蹦跳跳 真可爱
[00:45:00] 小白兔
[00:46:00] 白又白
[00:47:00] 两只耳朵竖起来
[00:50:00] 爱吃萝卜爱吃菜
[00:52:00] 蹦蹦跳跳 真可爱`
  • 处理成可用的数组格式,时间格式还原为秒数
[
    {
        time: 14
        words: "蹦蹦跳跳 真可爱"
    }
    // ...
]
lrc.split('\n').map(i=>{
    let s = i.split(']')
    let times = s[0].substring(1).split(':')
    return {
        time: times[0] * 60 + +times[1],
        words: s[1]
    }
})
  • 记录当前播放进度
change_video(val){
    console.log(this.$refs.my_audio.currentTime)
    let index = this.list.findIndex(i=>i.time >= this.$refs.my_audio.currentTime)
    this.index_now = index > 1 ? index - 1 : 0
}
  • 显示播放当前行高亮 :class="{active: index == index_now}"
  • 重点是计算元素.list的偏移,偏移用css style="transform: translateY(-220px)", 改变值即可实现偏移
  <div class="container">
    <div class="list" :style="{transform: computed_y}">
      <div v-for="(item,index) in list" class="li" :class="{active: index == index_now}">
        {{item.words}}
      </div>
    </div>
  </div>
computed: {
  computed_y(){
      let top = this.index_now * 30 - 250
      if(top < 0){
          top = 0
      }
      return `translateY(-${top}px)`
  }
}
  • 还有一个过渡属性 transition: 0.3
transition: 0.3s;

浏览器兼容性

image.png

QA: 为什么位移优先选择使用transform, 而不是margin-top?

改变transform、opacity这种,只需要在GPU内部基于绘制好的纹理进行变换或混合,不会引起重排和重绘。