|
|
楼主 |
发表于 2025-12-24 10:22:26
|
显示全部楼层
二、JavaScript 逻辑(实现歌词与音频同步)
核心逻辑是:解析歌词数据、监听音频的时间更新事件、匹配对应时间的歌词并高亮显示,同时实现歌词的滚动效果。
javascript
运行
// 1. 歌词数据(就是之前转换后的歌词数组)
const lyricData = [
{ time: 4, text: "时间是一片沉默的海" },
{ time: 45, text: "思念是漂泊的孤雁" },
{ time: 52, text: "回忆是无边的草原" },
{ time: 59, text: "每片白云都是远方" },
{ time: 63, text: "思念飘来汇成的海" },
{ time: 67, text: "在风中独自独白" },
{ time: 73, text: "时光是一片沉默的海" },
{ time: 77, text: "往事是雪地的洁白" },
{ time: 80, text: "每朵梨花都是一个" },
{ time: 84, text: "来不及说出的对白" },
{ time: 88, text: "月光下轻轻散开" },
{ time: 95, text: "等待是无声的钟摆" },
{ time: 101, text: "重逢是融雪的春天" },
{ time: 108, text: "每粒沙砾都是一个" },
{ time: 112, text: "被晚风遗忘的誓言" },
{ time: 116, text: "在掌心渐渐飘散" },
{ time: 123, text: "时光是一片沉默的海" },
{ time: 126, text: "往事是雪地的洁白" },
{ time: 129, text: "每朵梨花都是一个" },
{ time: 132, text: "来不及说出的对白" },
{ time: 137, text: "月光下轻轻散开" },
{ time: 156, text: "从草原走到雪山" },
{ time: 159, text: "从白天走到黑夜" },
{ time: 163, text: "我遇到了盛开的雪莲" },
{ time: 165, text: "就放下了执念" },
{ time: 171, text: "放下了执念" },
{ time: 178, text: "时间是一片沉默的海" },
{ time: 181, text: "往事是时光的留白" },
{ time: 184, text: "每朵梨花都是一个" },
{ time: 188, text: "来不及说出的对白" },
{ time: 192, text: "月光下轻轻散开" },
{ time: 198, text: "你是漫天的星辰" },
{ time: 205, text: "我是沉默的孤雁" },
{ time: 211, text: "每道流星都是一个" },
{ time: 214, text: "无法言说的心愿" },
{ time: 220, text: "在夜空点亮黑暗" }
];
// 2. 获取DOM元素
const audio = document.getElementById('audio');
const lyricWrapper = document.getElementById('lyricWrapper');
const lyricContainer = document.getElementById('lyricContainer');
// 3. 初始化歌词:将歌词数据渲染到页面
function initLyric() {
lyricData.forEach((item, index) => {
const lyricItem = document.createElement('div');
lyricItem.className = 'lyric-item';
lyricItem.dataset.time = item.time; // 存储歌词时间,用于匹配
lyricItem.dataset.index = index; // 存储歌词索引
lyricItem.innerText = item.text;
lyricWrapper.appendChild(lyricItem);
});
}
// 4. 歌词滚动和高亮逻辑
function updateLyric(currentTime) {
const lyricItems = document.querySelectorAll('.lyric-item');
let activeIndex = -1;
// 遍历歌词,找到当前时间对应的歌词索引
for (let i = 0; i < lyricItems.length; i++) {
const itemTime = parseFloat(lyricItems.dataset.time);
// 下一句歌词的时间(如果是最后一句,设为无穷大)
const nextItemTime = i < lyricItems.length - 1 ? parseFloat(lyricItems[i + 1].dataset.time) : Infinity;
// 当前时间在当前歌词和下一句歌词之间,即为当前要显示的歌词
if (currentTime >= itemTime && currentTime < nextItemTime) {
activeIndex = i;
break;
}
}
// 如果没有找到对应歌词,移除所有高亮
if (activeIndex === -1) {
lyricItems.forEach(item => item.classList.remove('active'));
return;
}
// 高亮当前歌词,移除其他歌词的高亮
lyricItems.forEach((item, index) => {
if (index === activeIndex) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
// 歌词滚动:让当前高亮的歌词居中显示
const activeItem = lyricItems[activeIndex];
const containerHeight = lyricContainer.clientHeight;
const itemHeight = activeItem.offsetHeight;
const scrollTop = activeItem.offsetTop - (containerHeight / 2) + (itemHeight / 2);
// 设置歌词容器的滚动位置(使用transform实现平滑滚动)
lyricWrapper.style.transform = `translateY(-${scrollTop}px)`;
}
// 5. 初始化并绑定事件
window.onload = function() {
initLyric(); // 渲染歌词
// 监听音频的时间更新事件(每100ms触发一次,更新歌词)
audio.addEventListener('timeupdate', function() {
const currentTime = Math.floor(audio.currentTime); // 取整,匹配歌词的秒数
updateLyric(currentTime);
});
// 音频播放结束后,重置歌词
audio.addEventListener('ended', function() {
lyricWrapper.style.transform = 'translateY(0px)';
const lyricItems = document.querySelectorAll('.lyric-item');
lyricItems.forEach(item => item.classList.remove('active'));
});
};
三、使用说明
替换音频文件:将代码中的 your-audio-file.mp3 替换为你的音频文件地址(支持本地文件或网络音频链接)。
歌词扩展:如果需要添加更多歌词,只需要在 lyricData 数组中按照格式添加对应的 time(秒数)和 text(歌词内容)即可。
样式自定义:可以通过修改 CSS 中的样式(如颜色、字体、背景、歌词容器高度等),适配你的页面风格。
四、功能说明
音频播放时,歌词会根据当前播放时间自动高亮对应行。
歌词区域会自动滚动,使当前高亮的歌词居中显示。
音频播放结束后,歌词会重置到顶部,移除所有高亮。
适配移动端和 PC 端,响应式布局。
|
|