当前位置:首页 > VUE

vue实现歌词同步

2026-02-18 10:56:43VUE

实现歌词同步的基本思路

歌词同步的核心是将歌词文本与音频播放时间轴匹配,动态高亮当前播放的歌词行。通常需要解析歌词文件(如LRC格式),监听音频时间变化,并计算当前应显示的歌词行。

解析歌词文件

LRC格式的歌词包含时间标签和文本内容,例如:

[00:00.00]歌曲名
[00:01.50]第一行歌词
[00:04.20]第二行歌词

解析时可将歌词转换为对象数组,包含时间(秒)和文本:

vue实现歌词同步

function parseLrc(lrcText) {
  return lrcText.split('\n').filter(line => {
    const timeTag = line.match(/^\[(\d{2}):(\d{2})\.(\d{2})\]/);
    return timeTag && line.length > 10;
  }).map(line => {
    const parts = line.match(/^\[(\d{2}):(\d{2})\.(\d{2})\](.*)/);
    const min = parseInt(parts[1]);
    const sec = parseInt(parts[2]);
    const ms = parseInt(parts[3]);
    const text = parts[4].trim();
    return {
      time: min * 60 + sec + ms / 100,
      text
    };
  });
}

监听音频播放进度

使用HTML5的<audio>元素或第三方音频库(如howler.js)获取当前播放时间:

data() {
  return {
    currentTime: 0,
    audio: null
  }
},
mounted() {
  this.audio = new Audio('song.mp3');
  this.audio.addEventListener('timeupdate', () => {
    this.currentTime = this.audio.currentTime;
  });
}

计算当前歌词行

根据当前播放时间找到对应的歌词行:

vue实现歌词同步

computed: {
  currentLineIndex() {
    const { lyrics } = this;
    for (let i = 0; i < lyrics.length; i++) {
      if (this.currentTime < lyrics[i].time) {
        return i - 1;
      }
    }
    return lyrics.length - 1;
  }
}

渲染歌词列表

在模板中渲染歌词列表,并高亮当前行:

<div class="lyrics-container">
  <div 
    v-for="(line, index) in lyrics"
    :key="index"
    :class="{ 'active': index === currentLineIndex }"
  >
    {{ line.text }}
  </div>
</div>

添加平滑滚动效果

通过CSS和JavaScript实现歌词自动居中滚动:

watch: {
  currentLineIndex(newVal) {
    const container = this.$refs.lyricsContainer;
    const activeLine = container.children[newVal];
    if (activeLine) {
      container.scrollTo({
        top: activeLine.offsetTop - container.offsetHeight / 2,
        behavior: 'smooth'
      });
    }
  }
}

扩展功能建议

  • 双语歌词支持:解析双语LRC文件,中英文对照显示
  • 逐字高亮:进一步拆分每行歌词为单字,实现卡拉OK式效果
  • 动态背景:根据歌词情绪变化背景颜色或动画
  • 播放控制:添加进度条拖动、播放速度调节等功能

完整组件示例

<template>
  <div>
    <audio ref="audio" controls :src="audioUrl"></audio>
    <div class="lyrics-container" ref="lyricsContainer">
      <div
        v-for="(line, index) in lyrics"
        :key="index"
        :class="{ 'active': index === currentLineIndex }"
      >
        {{ line.text }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      audioUrl: 'path/to/audio.mp3',
      lrcText: `[00:01.50]第一行歌词
                [00:04.20]第二行歌词`,
      currentTime: 0
    };
  },
  computed: {
    lyrics() {
      return this.parseLrc(this.lrcText);
    },
    currentLineIndex() {
      for (let i = 0; i < this.lyrics.length; i++) {
        if (this.currentTime < this.lyrics[i].time) {
          return i - 1;
        }
      }
      return this.lyrics.length - 1;
    }
  },
  mounted() {
    const audio = this.$refs.audio;
    audio.addEventListener('timeupdate', () => {
      this.currentTime = audio.currentTime;
    });
  },
  methods: {
    parseLrc(lrcText) {
      return lrcText.split('\n').filter(line => {
        const timeTag = line.match(/^\[(\d{2}):(\d{2})\.(\d{2})\]/);
        return timeTag && line.length > 10;
      }).map(line => {
        const parts = line.match(/^\[(\d{2}):(\d{2})\.(\d{2})\](.*)/);
        const min = parseInt(parts[1]);
        const sec = parseInt(parts[2]);
        const ms = parseInt(parts[3]);
        const text = parts[4].trim();
        return {
          time: min * 60 + sec + ms / 100,
          text
        };
      });
    }
  },
  watch: {
    currentLineIndex(newVal) {
      const container = this.$refs.lyricsContainer;
      const activeLine = container.children[newVal];
      if (activeLine) {
        container.scrollTo({
          top: activeLine.offsetTop - container.offsetHeight / 2,
          behavior: 'smooth'
        });
      }
    }
  }
};
</script>

<style>
.lyrics-container {
  height: 300px;
  overflow-y: auto;
  text-align: center;
}
.lyrics-container div {
  padding: 10px;
  transition: all 0.3s ease;
}
.lyrics-container .active {
  color: #42b983;
  font-weight: bold;
  transform: scale(1.05);
}
</style>

标签: 歌词同步vue
分享给朋友:

相关文章

vue实现滑动

vue实现滑动

Vue 实现滑动效果 使用 CSS 过渡和动画 通过 Vue 的 transition 组件结合 CSS 过渡或动画实现滑动效果。适用于简单的元素入场/离场滑动。 <template>…

vue实现拖放

vue实现拖放

Vue 实现拖放功能 Vue 中实现拖放功能通常可以通过 HTML5 的原生拖放 API 或第三方库(如 vuedraggable)来实现。以下是两种方法的详细说明。 使用 HTML5 原生拖放…

vue实现高亮

vue实现高亮

Vue 实现文本高亮的方法 在 Vue 中实现文本高亮可以通过多种方式完成,以下是几种常见的方法: 方法一:使用 v-html 指令 通过 v-html 指令可以动态插入 HTML 内容,将需…

vue实现通知

vue实现通知

Vue 实现通知功能的方法 使用 Vue 实现通知功能可以通过多种方式实现,以下是一些常见的方法: 使用 Vue 的全局事件总线 在 Vue 中可以通过事件总线实现通知功能。创建一个全局事件总线,然…

vue实现数组

vue实现数组

Vue 实现数组操作的方法 在 Vue 中,数组的操作需要特别注意响应式的问题。Vue 提供了一些方法来确保数组的变动能够触发视图更新。 响应式数组方法 Vue 对以下数组方法进行了封装,确保它们…

vue实现toast

vue实现toast

Vue 实现 Toast 的方法 使用第三方库(推荐) 对于快速实现 Toast 功能,推荐使用成熟的第三方库如 vue-toastification 或 vant 的 Toast 组件。 安装 v…