当前位置:首页 > VUE

vue实现歌词同步

2026-02-18 10:56:43VUE

实现歌词同步的基本思路

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

解析歌词文件

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

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

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

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;
  });
}

计算当前歌词行

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

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实现歌词自动居中滚动:

vue实现歌词同步

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 实现按钮刷新功能 在 Vue 中实现按钮刷新功能可以通过多种方式实现,具体取决于刷新需求是局部刷新还是全局刷新。以下是几种常见的方法: 使用 window.location.reload()…

vue实现视频会议

vue实现视频会议

使用 Vue 实现视频会议 技术选型 Vue.js 作为前端框架,结合 WebRTC 技术实现实时音视频通信。常用的库包括: peerjs:简化 WebRTC 的点对点连接。 socket.io:用…

vue实现数组

vue实现数组

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

vue 实现流程

vue 实现流程

Vue 实现流程 Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。以下是 Vue 实现的基本流程: 安装 Vue.js 通过 CDN 引入或使用 npm/yarn 安装:…

vue 实现脚本

vue 实现脚本

Vue 实现脚本的方法 Vue.js 提供了多种方式来实现脚本功能,包括组件内脚本、混入(Mixins)、插件(Plugins)以及自定义指令等。以下是常见的实现方式: 组件内脚本 在 Vue 单文…

vue实现通讯

vue实现通讯

Vue 组件通讯方法 父子组件通讯 父组件向子组件传递数据通过 props,子组件向父组件传递数据通过 $emit 事件。 父组件模板: <child-component :message=…