vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词

演示

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第1张图片
需要注意:引入vue.js与lyric-parser.js
lyric-parser.js地址:https://github.com/ustbhuangyi/lyric-parser/blob/master/src/index.js
因为是网易云的歌词,与这个库解析的歌词不适配,有个地方改一下就行了
在这里插入图片描述

代码

DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>

<body>
<div id="app">
    <audio ref="audioRef" autoplay @canplay='canplay' @timeupdate='update'>audio>
    <button @click="play">播放button>
    <button @click="pause">暂停button>
    <div class="progress-wrapper">
        <span class="time time-l">{{formatTime(currentTime)}}span>
        <div class="progress-bar-wrapper">
            <cpn :progress=progress
                 @progress-changing="onProgressChanging"
                 @progress-changed='progressChanged'>
            cpn>
        div>
        <span class="time time-l">{{formatTime(duration)}}span>
    div>
    <div class="lyric-container" ref="lyricContainer">
        <div class="cotnent" ref="lyricListRef">
            <p class="text" v-for="(line, index) in currentLyric.lines" :class="{'current': currentLineNum ===index}">
                {{line.txt}}
            p>
        div>
    div>


div>


<template id="myCpn">

    <div class="progress-bar">
        
        <div class="bar-inner" @click="clickProgress">
            
            <div class="progress" :style='progressStyle' ref="progress">
            div>
            
            <div class="progress-btn-wrapper" :style='btnStyle' @touchstart.preventDefault='onTouchStart'
                 @touchmove.preventDefault='onTouchMove' @touchend.preventDefault='onTouchEnd'>
                <div class="progress-btn">div>
            div>
        div>
    div>
template>


<script src="../../js/vue.js">script>
<script type="module">
  import Lyric from "./lyricParser.js"

  let audioEl;
  let lyricEl;
  let lyricListEl = null
  audioEl = null
  lyricEl = null
  const progressBtnWidth = 16
  // 子组件
  const cpn = {
    template: "#myCpn",
    props: {
      progress: {
        type: Number,
        default: 0
      }
    },
    data() {
      return {
        offset: 0
      }
    },
    mounted() {

    },
    created() {
      this.touch = {}
    },
    computed: {
      progressStyle() {
        return `width: ${this.offset}px`
      },
      btnStyle() {
        // console.log('fds');
        return `transform: translate3d(${this.offset}px,0,0)`
      },
    },
    watch: {
      progress(newProgress) {
        // 进度条宽度
        const barWidth = this.$el.clientWidth - progressBtnWidth
        this.offset = barWidth * newProgress
      },

    },
    methods: {
      onTouchStart(e) {
        // console.log(e);
        this.touch.x1 = e.changedTouches[0].clientX
        // 黄色进度条初始宽度
        this.touch.beginWidth = this.$refs.progress.clientWidth
        console.log(this.touch);
      },
      onTouchMove(e) {
        // console.log(e);
        // x偏移量
        const delta = e.changedTouches[0].clientX - this.touch.x1
        // 之前的width+这次拖动增加的偏移量=应有的黄条长度
        const tempWidth = this.touch.beginWidth + delta
        // 再拿到barWidth
        const barWidth = this.$el.clientWidth - progressBtnWidth
        // 黄条长度/barwidth = progress 现在应该有的进度
        const progress = tempWidth / barWidth
        this.offset = barWidth * progress
        this.$emit('progress-changing', progress)

      },
      onTouchEnd(e) {
        // console.log(e);
        const barWidth = this.$el.clientWidth - progressBtnWidth
        const progress = this.$refs.progress.clientWidth / barWidth
        this.$emit('progress-changed', progress)
      },
      // 点击进度条
      clickProgress(e) {
        console.log('getBoundingClientRect', this.$el.getBoundingClientRect());
        const rect = this.$el.getBoundingClientRect()
        // 黄条应有的宽度
        const offsetWidth = e.pageX - rect.x
        const barWidth = this.$el.clientWidth - progressBtnWidth
        // const progress = offsetWidth/barWidth
        const progress = Math.min(1, Math.max(offsetWidth / barWidth, 0))
        this.$emit('progress-changed', progress)
        // console.log(offsetWidth)
      }
    },
  }
  const app = new Vue({
    el: "#app",
    data: {
      content: 'fdasdf',
      // 天外来物
      src: 'https://music.163.com/song/media/outer/url?id=1463165983.mp3',
      currentTime: 0,
      duration: 0,
      isplay: false,
      progressChanging: false,
      lyric: "[00:00.000] 作词 : 薛之谦\n[00:01.000] 作曲 : 罗小黑\n[00:02.000] 编曲 : 周以力\n[00:03.000] 制作人 : 周以力/郑伟\n[00:18.042]你降落的 太突然了\n[00:24.240]我刚好呢 又路过了\n[00:32.325]机会难得 又主观觉得\n[00:38.725]想明抢 又碰不得\n[00:46.157]你带来了 我的快乐\n[00:53.157]让这世界 有点颜色\n[01:00.444]我好想指责 你太随意了\n[01:06.991]宝物该有人捧着  你是不是我的\n[01:17.333]你像 天外来物一样 求之不得\n[01:24.269]你在世俗里的名字 不重要了\n[01:31.516]正好 我隐藏的人格是锲而不舍\n[01:38.299]直到蜂拥而至的人都透明了\n[01:45.943]我在 不近又不远处\n[01:49.925]用明天换你 靠近我\n[02:07.554]你占领了 我的快乐\n[02:14.406]和这世界 已没有瓜葛\n[02:21.639]任事物干渴  都褪去颜色\n[02:28.340]只有你是天蓝色  我开始找你了\n[02:40.569]会像 天外来物一样 失而复得\n[02:47.769]你在世俗里的名字  被人用了\n[02:54.662]反正 我隐藏的人格是锲而不舍\n[03:01.730]直到蜂拥而至的人都透明了\n[03:08.793]我在 不近又不远处\n[03:12.651]用明天换你 靠近我\n[03:18.765]你就像 天外来物一样 求之不得\n[03:26.717]我在世俗里的描写被取笑了\n[03:33.681]反正我隐藏的人格是非你不可\n[03:40.352]直到别有用心的人都透明了\n[03:47.895]我在 不近又不远处\n[03:52.044]用明天换你 靠近我\n[03:54.560] 吉他 : 张淞\n[03:57.076] 大提琴 : 郎莹\n[03:59.592] 鼓 : 褚伟明\n[04:02.108] 贝斯 : 努而德柯\n[04:04.624] 人声录音 : 郑伟 夏之炜 吴身宝\n[04:07.140] 人声编辑 : 郑伟\n[04:09.656] 人声录音室 : 上海广播大厦200studio\n[04:12.172] 乐器录音棚 : soundhub studio\n[04:14.688] 混音 : 全相彦 @OK master studio\n[04:17.204] 母带 : 全相彦 @OK master studio\n",
      currentLyric: '',
      // 当前播放行数  用来设置class
      currentLineNum: 0
    },
    components: {
      cpn
    },
    mounted() {
      // 解析歌词
      this.currentLyric = new Lyric(this.lyric, this.handleLyric)
      this.$nextTick(() => {
        audioEl = this.$refs.audioRef
        audioEl.src = this.src
        audioEl.volume = 0.1
        console.log(this.$refs.audioRef)
        // 默认暂停
        audioEl.pause()
        lyricEl = this.$refs.lyricContainer
        lyricListEl = this.$refs.lyricListRef
        console.log("lyricListEl", lyricListEl)
        // this.currentLyric = new Lyric(this.lyric, this.handleLyric)
      })
      // console.log(window)
    },
    computed: {
      progress() {
        return this.currentTime / this.duration
        console.log("progress", this.currentTime / this.duration);
      },
    },

    methods: {
      play() {
        audioEl.play()
        this.isplay = true
        this.playLyric()
      },
      pause() {
        console.log("pause")
        audioEl.pause()
        this.isplay = false
        // console.log();
        this.stopLyric()
      },
      canplay(e) {
        // console.log(123456);
        // console.log(e);
        console.log("canplay")
        this.duration = e.target.duration

      },
      update(e) {
        if (!this.progressChanging) {
          this.currentTime = e.target.currentTime
        }
        // console.log(this.currentTime)
      },
      onProgressChanging(e) {
        // console.log("onProgressChanging", e);
        this.progressChanging = true
        // 实时修改currentTime值
        this.currentTime = this.duration * e
        console.log(this.currentTime)
        this.stopLyric()
      },
      progressChanged(e) {
        this.stopLyric()
        // console.log(e);
        this.progressChanging = false

        audioEl.currentTime = this.currentTime = this.duration * e
        if (!this.isplay) {
          console.log("------");
          audioEl.play()
        }
        // this.currentLyric.play(0)
        // this.currentLyric.stop()
        this.playLyric()
      },

      // -----------歌词有关-------------
      // 歌词滚动条
      handleLyric({lineNum, txt}) {
        // console.log(this.currentLyric)
        // console.log(lineNum, txt)
        this.currentLineNum = lineNum
        if (!lyricListEl) {
          return
        }
        if (lineNum > 3) {
          const lineEl = lyricListEl.children[0]
          // 拿到行的高度
          const lineHeight= lineEl.clientHeight
          // 滚动到哪里呢  到一行高度*当前的行-3行
          lyricEl.scrollTo({
            top: lineHeight*(lineNum-3),
            left: 0,
            behavior: 'smooth'
          });
        }
      },
      // 测试滚动功能
      // startScroll() {
      //   // console.log(1)
      //   setInterval(() => {
      //     lyricEl.scrollTop += 42
      //   }, 1000)
      // },
      // 播放歌词
      playLyric() {
        // clearTimeout()
        console.log(this.currentLyric.lines)
        console.log("播放了")
        console.log(this.currentLyric)
        this.currentLyric.seek(this.currentTime * 1000)
      },
      stopLyric() {
        console.log("暂停了")
        if (this.currentLyric) {
          this.currentLyric.stop()
        }
      },
      formatTime(interval) {
        // interval 向下取整
        interval = interval | 0
        // 不足两位的话就向前填充一个0
        let minute = ((interval / 60 | 0) + '')
        let second = ((interval % 60 | 0) + '')
        let len = minute.length
        for (; len < 2; len++) {
          minute = '0' + minute
        }
        len = second.length
        for (; len < 2; len++) {
          second = '0' + second
        }
        return `${minute}:${second}`
      }
    },
  })
script>
body>
<style>
    #app {
        width: 100%;
    }

    .progress-wrapper {
        display: flex;
        width: 80%;
        padding: 10px 0;
        align-items: center;
        margin: 0 auto;
    }

    .time {
        width: 40px;
        flex: 0 0 40px;
        font-size: 8px;
        margin: 0 auto;
        padding: 0 8px;
    }

    .time-l {
        text-align: left;
    }

    .time-l {
        text-align: right;
    }

    .progress-bar-wrapper {
        flex: 1;
    }

    /* 子组件样式 */
    .progress-bar {
        height: 30px;
    }

    .bar-inner {
        position: relative;
        top: 11px;
        height: 8px;
        background-color: rgba(87, 82, 82, 0.062);
        border-radius: 5px;
    }

    .progress {
        position: absolute;
        height: 100%;
        background-color: rgb(238, 238, 136);
    }

    .progress-btn-wrapper {
        position: absolute;
        left: -8px;
        top: -11px;
        width: 30px;
        height: 30px;
    }

    .progress-btn {
        position: relative;
        top: 7px;
        left: 7px;
        box-sizing: border-box;
        width: 16px;
        height: 16px;
        border: 3px solid rgb(189, 189, 218);
        border-radius: 50%;
        background: rgb(123, 192, 212);
    }

    .lyric-container {
        padding: 0px;
        width: 300px;
        height: 300px;
        background-color: antiquewhite;
        overflow: scroll;

    }

    ::-webkit-scrollbar {
        width: 0px;
        height: 0px;

    }

    p {
        display: block;
        margin: 0;
        padding: 10px;
        text-align: center;
        box-sizing: border-box;
    }

    .current {
        color: white;
    }
style>

html>

解说

来看一下

在音乐加载完之后(不应该在这里实例化,后面有改)

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第2张图片

handleLyric 给currentLine赋值 用来高亮

在这里插入图片描述

点击暂停和播放

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第3张图片

播放音乐中

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第4张图片

playLyric

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第5张图片

页面

在这里插入图片描述

但是会发现有时候高亮会有延迟,这是因为lyric-parser的问题

修改方法

https://github.com/ustbhuangyi/lyric-parser/issues/11

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第6张图片

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第7张图片

这样就ok了

但是发现个bug

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第8张图片

它为什么会跳到前面

试了一下,不拖动的话是没有问题的,但是一旦拖动或者点击再点暂停,暂停功能就会失效

为什么呢 因为如果拖动的话canplay是会触发的,所以可能创建了很多个实例,开了很多定时器(可以看lyric-parse的源码),上一个还没被销毁就又创建了一个,导致上一个还没有关掉 ,所以不应该在canplay里面实例化,还有这里是只有一首歌,如果有多首歌进行切换的时候记得先stop一下,要不然还是会有这个问题

再来整理一下
实例化
vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第9张图片

playLyric与stopLyric
vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第10张图片

用到playLyric和stopLyric的地方

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第11张图片

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第12张图片

这样高亮功能就完成了,接下来完成自动滚动

希望的效果:前5行,不动,接下来第六行:当前减去前五行保证一直在中间的位置

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第13张图片

获取外层滚动的盒子el,和滚动盒子里面内容的el

获取外层滚动盒子的el是为了能够滚动,获取里面的内容是为了拿到里面的子元素,获取一行的dom,计算出它的高度

我又改了一下样式,再来整理一下这个功能,因为p标签它自动会加上margin ,上下的margin还是重叠的,获取height的时候还不会加上margin的值,很麻烦,所以吧margin去掉了,换成了padding
vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第14张图片

web-api scrollTo的用法

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo

vue歌词随着歌曲播放滚动demo 使用lyric-parser库解析歌词_第15张图片

你可能感兴趣的:(vue,demo,自定义组件,vue.js,javascript)