歌词跟随播放进度高亮显示,高亮部分一直在页面中间位置,歌词可拖动

//歌词组件LyricsDisplay.vue
<template>
  <div class="lyric-container" ref="lyricsList" :class="{ noLyric: !lyrics.length }">
    <div v-for="(line, index) in lyrics" :key="index" ref="lyricLine" :class="{ active: index === currentIndex }">
      {{ line.text }}
    </div>
    <div class="empty-lyric" v-if="!lyrics.length">
      当前歌曲无歌词
    </div>
  </div>
</template>

<script>

export default {
  props: {
    lyrics: { type: Array, required: true }, // 已经解析好的歌词对象数组
    currentTime: { type: Number, required: true }, // 当前播放时间(秒)
  },
  computed: {
    currentIndex () {
      const currentIndexInRange = this.lyrics.findIndex((item, index) => {
        if (index < this.lyrics.length - 1) { // 防止数组越界
          return item.time <= this.currentTime && this.lyrics[index + 1].time > this.currentTime;
        }
        return false; // 如果已经是最后一项,返回false,后续不会再检查
      });

      // 如果没有找到符合条件的歌词,返回-1或其他默认值
      return currentIndexInRange !== -1 ? currentIndexInRange : -1;
    },
  },
  watch: {
    currentIndex (newVal) {
      this.$nextTick(() => {
        if (newVal > -1) {
          // 获取当前歌词项对应的DOM元素
          const currentLyricElement = this.$refs.lyricLine[newVal];
          // 计算容器高度和当前元素高度
          const containerHeight = this.$refs.lyricsList.clientHeight;
          const elementHeight = currentLyricElement.clientHeight;

          // 计算需要滚动到的偏移量,使元素位于容器中间
          const scrollTop = currentLyricElement.offsetTop - this.$refs.lyricsList.offsetTop - (containerHeight / 2 - elementHeight / 2);
          // 平滑滚动到计算出的偏移量
          this.$refs.lyricsList.scrollTop = scrollTop;
        }
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.lyric-container {
  overflow-y: scroll;
  width: 80%;
  height: 230px;
  margin: 0 auto;
  color: rgba(255, 255, 255, 0.3);
  font-size: 14px;
  line-height: 34px;
  text-align: center;

  .empty-lyric {
    font-size: 16px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
}

.noLyric {
  position: relative;
}

.lyric-container::-webkit-scrollbar {
  display: none;
}

.active {
  color: #ffffff;
  font-size: 16px;
}
</style>
//解析lrc歌词lyric-parser.js
export function parseLyric(lrcContent) {
  return lrcContent.split('\n').reduce((lyrics, line) => {
    const timeMatch = /\[(\d{2}:\d{2}(\.\d{2,3})?)\]/.exec(line);
    if (timeMatch) {
      const timeStr = timeMatch[1];
      const parts = timeStr.split(':').map(parseFloat);
      const time = parts[0] * 60 + (parts[1] + (parts[2] || 0) / 1000);
      const lyric = line.replace(timeMatch[0], '').trim();
      lyrics.push({ time, text: lyric });
    }
    return lyrics;
  }, []);
}
使用:
<template>
  <div>
<audio ref="audio" @timeupdate="updateTime"></audio>
    <lyrics-display :lyrics="parsedLyrics" :currentTime="currentTime"></lyrics-display>
  </div>
</template>
<script>
import LyricsDisplay from '@/components/LyricsDisplay.vue';
import { parseLyric } from '@/utils/lyric-parser.js';
export default {
  components: {
    LyricsDisplay,
  },
  data () {
    return {
      currentTime: 0,
      parsedLyrics: [],
    };
  },
  methods: {
    updateTime (e) {
      this.currentTime = e.target.currentTime;
    },
    async function fetchAndParseLyric(url) {
      try {
        // 异步请求歌词文件
        const response = await fetch(url);
        const lrcContent = await response.text();

        // 使用parseLyric函数解析歌词
        const parsedLyrics = parseLyric(lrcContent);

        // 返回解析后的歌词对象数组
        return parsedLyrics;
      } catch (error) {
        console.error('Failed to fetch or parse lyric:', error);
        return null;
      }
    },
  },
  mounted(){
    // 调用函数解析歌词
    this.fetchAndParseLyric('https://axxx.txt')
      .then(parsedLyrics => {
        console.log(parsedLyrics);
        // 处理解析后的歌词数据
        this.parsedLyrics=parsedLyrics
     });
  }
}

 

posted @ 2024-03-29 14:10  chicidol  阅读(109)  评论(0)    收藏  举报