Vue3+TypeScript实现网易云音乐WebApp(解析歌词,并实现自行匹配滚动)

前言

最终实现效果gif:

歌曲播放控制的录制.gif

这篇文章实现了gif里的其他功能

1. 实现思路

  1. 解析歌词 拿到歌词数组 ->
lyricArr = [
    {time: 0, lyric: '给我你的爱', uid: 111222},
    {time: 1.2, lyric: '可以不可以', uid: 222333},
    {time: 4.45, lyric: '啊WDNMD', uid: 333444},
]
  1. 渲染歌词dom ->
<span class='' data-index='0'>给我你的爱</span>
<span class='' data-index='1'>可以不可以</span>
<span class='' data-index='2'>啊WDNMD</span>
  1. 通过audio标签的timeupdate钩子,可以拿到歌曲的实时播放时间currentTime
  2. 定义Idx,初始值为0,全局的索引(当前高亮歌词的索引,也就是歌词数组的索引)
  3. timeupdate钩子函数中,当currentTime大于lyricArr[Idx].time,获取到当前歌词dom绑定的data-index属性的索引index,又当indexIdx相等时,把Idx的值赋值给lyricIndex。PS:lyricIndex是专门用来与标签里的索引(data-index)匹配,判断是否需要添加高亮样式。当Idx的值大于lyricArr的长度,就return false, 代码不再执行了。
  4. 定义全局的scrollHeight,用来滚动的。利用lyricArr[Idx].offsetHeight来获取当前匹配的歌词dom的高度,然后使用scrollHeight += lyricArr[Idx].offsetHeight来进行累加。最后使用js改变transform:translateY()属性即可实现自行滚动

2. 解析歌词

2.1 接口返回的歌词样式

lyric: "[00:00.000] 作词 : 马思唯 Masiwei/朴宰范 Jay Park/HARIKIRI↵[00:00.930] 作曲 : HARIKIRI/马思唯 Masiwei/朴宰范 Jay Park↵[00:01.860] 编曲 : HARIKIRI↵[00:02.790] 制作人 : HARIKIRI↵[00:03.720](Masiwei)↵[00:03.725]HARIKIRI on the beat, yeah↵[00:07.429]Prince Charming↵[00:09.033]Look, check this out↵[00:10.812]↵[00:13.975](Masiwei)↵[00:13.976]我不敢对你保证↵[00:15.442]玫瑰花她会⼀直盛开↵[00:17.398]今天晚上⽉亮会不会在↵[00:18.968]丘⽐特什么时候才会到来↵[00:21.117]我不敢对你保证↵[00:22.633]我能⼀直富有我能⼀直年轻↵[00:24.634]⼀直拥有魔⼒充满活⼒↵[00:26.171]不管遇到什么难题我都能够⼀直开⼼↵[00:28.775]↵[00:29.500](Masiwei)↵[00:29.501]there’s only one thing↵[00:31.257]that I can promise you↵[00:33.122]that I can promise you↵[00:34.888]yeah uh↵[00:36.660]there’s only one thing↵[00:38.476]that I can promise you↵[00:40.209](Masiwei & Jay Park)↵[00:40.210]I love you↵[00:42.556]↵[00:42.689](Jay Park)↵[00:42.690]I’ll give it all up for you↵[00:45.861]If you wanted to↵[00:47.515]It’d be nothing to me↵[00:49.797]널 가질수 있다면↵[00:53.037]다가와~~~↵[00:56.792]我期待baby 我期待↵[01:00.130]快点回到我身边↵[01:02.451]别等待 我依赖baby↵[01:05.892]我太依赖你↵[01:07.675]习惯有你在别离开↵[01:11.527]I love you, I promise↵[01:13.342]I’ll treat you, like a goddess↵[01:15.227]The sun don’t shine so bright↵[01:16.673]but I’ll still be by your side↵[01:18.749]I want to, I’m honest↵[01:20.525]Girl you so, damn flawless↵[01:22.423]你让我迷上了你的每⼀⾯↵[01:26.423]↵[01:26.742](Masiwei)↵[01:26.743]there’s only one thing↵[01:28.592]that I can promise you↵[01:30.392]that I can promise you↵[01:32.327]yeah uh↵[01:33.959]there’s only one thing↵[01:35.698]that I can promise you↵[01:37.583](Masiwei & Jay Park)↵[01:37.584]I love you↵[01:39.302]↵[01:39.385](Masiwei)↵[01:39.386]你不⽤去染头发↵[01:41.109]快点准备好⾛吧↵[01:42.883]我已经在你家楼下↵[01:44.682]让我做你的欧巴↵[01:46.516]对我来说你太神奇↵[01:48.166]别再犹豫 ride with me↵[01:49.974]虽然不懂 do rei mi↵[01:51.790]你让我想唱rnb↵[01:53.721]I be 在街⻆ 在夜店 追寻你的轨迹↵[01:57.593]不⽤为我做任何改变↵[01:59.219]在我眼⾥你最美丽↵[02:01.207]塞纳河边和我散步↵[02:02.824]baby让我做你的莎⼠⽐亚↵[02:04.607]对你说过的话 那都绝对算数 把你变成诗歌写在笔下↵[02:08.257]↵[02:10.009]서울 안에서↵[02:11.660]소주 한잔해↵[02:13.475]다시 출발해↵[02:15.326]저 꿈나라로↵[02:17.008]우리 함께 구름위로↵[02:18.687]我要带你去宇宙↵[02:20.428]baby we can fly↵[02:23.070]↵[02:23.078](Jay Park)↵[02:23.079]I love you, I promise↵[02:25.012]I’ll treat you, like a goddess↵[02:26.830]The sun don’t shine so bright↵[02:28.323]but I’ll still be by your side↵[02:30.300]I want to, I’m honest↵[02:32.218]Girl you so, damn flawless↵[02:33.999]你让我迷上了你的每⼀⾯↵[02:37.640]↵[02:38.381](Masiwei)↵[02:38.382]there’s only one thing↵[02:40.177]that I can promise you↵[02:42.060]that I can promise you↵[02:44.940]uh↵[02:45.639]there’s only one thing↵[02:47.440]that I can promise you↵[02:49.179](Masiwei & Jay Park)↵[02:49.180]I love you↵[02:51.982]↵[02:52.815]there’s only one thing↵[02:54.597]that I can promise you↵[02:56.348]that I can promise you↵[02:59.108]uh↵[02:59.868]there’s only one thing↵[03:01.707]that I can promise you↵[03:03.556](Masiwei & Jay Park)↵[03:03.557]I love you↵[03:06.307]↵[03:07.045]there’s only one thing↵[03:08.775]that I can promise you↵[03:10.711]that I can promise you↵[03:13.523]uh↵[03:14.057]there’s only one thing↵[03:16.091]that I can promise you↵[03:17.781](Masiwei & Jay Park)↵[03:17.782]I love you↵[03:20.784]↵[03:21.418] 配唱制作人 : HARIKIRI↵[03:22.052] 吉他 : Joey “Iki” Lee↵[03:22.686] 录音工程 : HARIKIRI↵[03:23.320] 录音室 : HARIKIRI Studio↵[03:23.954] 人声处理 : HARIKIRI↵[03:24.588] 混⾳⼯程师:HeadAche / HARIKIRI ↵[03:25.222] 混音助理:Jayda Love ↵[03:25.856] 混音室:Sidechain Studio / HARIKIRI Studio ↵[03:26.490] 母带处理:HeadAche↵[03:27.124] 母带处理室:Sidechain Studio ↵[03:27.758] 制作公司:HARIKIRI Music Ltd (UK)↵"

就是一行字符串 都没有给你换行的那种~

2.2 处理歌词,生成歌词数组

// src/typings/index.ts

export interface ILyric {
  time: number,
  lyric: string,
  uid: number
}
// src/utils/formatAxiosRes.ts

interface IReturnLyric {
  lyric: ILyric[],
  tlyric?: ILyric[]
}

export const formatMusicLyrics = (lyric?: string, tlyric?: string):IReturnLyric => {
  if (lyric === '') {
    return { lyric: [{ time: 0, lyric: '这个地方没有歌词!', uid: 520520 }] }
  }
  const lyricObjArr: ILyric[] = [] // 最终生成的歌词数组

  // 将歌曲字符串变成数组,数组每一项就是当前歌词信息
  const lineLyric:any = lyric?.split(/\n/)

  // 匹配中括号里正则的
  const regTime = /\d{2}:\d{2}.\d{2,3}/

  // 循环遍历歌曲数组
  for (let i = 0; i < lineLyric?.length; i++) {
    if (lineLyric[i] === '') continue
    const time:number = formatLyricTime(lineLyric[i].match(regTime)[0])

    if (lineLyric[i].split(']')[1] !== '') {
      lyricObjArr.push({
        time: time,
        lyric: lineLyric[i].split(']')[1],
        uid: parseInt(Math.random().toString().slice(-6))
      })
    }
  }
  console.log(lyricObjArr)

  return {
    lyric: lyricObjArr
  }
}

const formatLyricTime = (time: string) => {
  const regMin = /.*:/
  const regSec = /:.*\./
  const regMs = /\./

  const min = parseInt((time.match(regMin) as any)[0].slice(0, 2))
  let sec = parseInt((time.match(regSec) as any)[0].slice(1, 3))
  const ms = time.slice((time.match(regMs) as any).index + 1, (time.match(regMs) as any).index + 3)
  if (min !== 0) {
    sec += min * 60
  }
  return Number(sec + '.' + ms)
}

2.3 最终生成的歌词数组

歌词数组.png
这里我只格式化了原歌词,翻译歌词是没有格式化的。如果有需要的伙伴可照着操作。

3. 实现歌词滚动

3.1 dom结构

<div ref="lyricDiv" class="lyrics">
    <ul ref="lyric" style="transition: .3s">
      <span
        v-for='(item, index) in playingMusic.lyric'
        :key="item.uid"
        :class="{acting: lyricIndex === index}"
        :data-index='index'
      >
        {{item.lyric.trim()}}
      </span>
    </ul>
</div>
      
<audio @timeupdate="handleTimeUpdate"></audio>

3.2 TS控制

setup() {

    const lyric = ref<any>(null) // dom -  包含歌词标签的ul,高度很高,主要用于控制transform
    const lyricDiv = ref<any>(null) // dom - 高度固定的外层div,主要用于html结构
    const lyricIndex = ref<number>(0) // 当前高亮歌词的索引,与span标签里的index进行比较,判断是否高亮
    let scrollHeight: number = 0 // 歌词区域要滚动的高度
    let lyricHeight: number = 0 // 歌词区域最外层区域高度,也就是lyricDiv的高度
    let flag: boolean = true // 判断当前高亮的索引是否已经超过了歌词数组的长度

    // 处理歌曲播放进程
    const handleTimeUpdate = (e:any): void => {
        const { currentTime } = e.target
        handleLyricTransform(currentTime)
    }
    
    // 歌词滚动
    const handleLyricTransform = (currentTime: number): void => {
      const item: ILyric = playingMusic.lyric[Idx]

      if (flag && currentTime > item.time) {
        // 实时获取dom,不然在上一首/下一首切换时,dom不会更新 
        lyricDomArr = lyric.value.querySelectorAll('span')
        
        // 拿到当前正在播放的歌词span的索引
        const index = parseInt(lyricDomArr[Idx].dataset.index)

        if (Idx === index) {
          lyricIndex.value = Idx
          Idx += 1
          if (Idx >= lyricDomArr.length) {
            flag = false
            return
          }

          scrollHeight += lyricDomArr[Idx].offsetHeight

          if (lyric.value) {
            lyric.value.style.transform = `translateY(${lyricHeight - scrollHeight}px)`
          }
        }
      }
    }
    
    onMounted(() => {
      // 获取span标签数组
      lyricDomArr = lyric.value.querySelectorAll('span')
      
      // 获取固定歌词区域高度的一半 用来让高亮歌词始终居中
      lyricHeight = lyricDiv.value.offsetHeight / 2
    })
    
    return {
        lyric,
        lyricDiv,
        lyricIndex,
        handleTimeUpdate
    }
    
}

4. 注意点

在切换歌曲后要记得重置lyrictransform:translateY()属性为0Idx0scrollHeight0flagtrue

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值