js歌词逐字滚动效果
@Null - 滨
先上效果图
目录结构:
歌词文本music.txt
将酷狗歌词KRC用“酷狗歌词(krc)加解密工具”进行解密后可以得到这种歌词文本
HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 网页图标 -->
<link rel="icon" href="data:image/png;base64,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">
<meta charset="UTF-8">
<title>Music</title>
<!-- 引入css文件 -->
<link rel="stylesheet" href="./code.css">
</head>
<body>
<canvas id="canvas">浏览器不支持Canvas,请升级浏览器!</canvas>
<audio controls id="myAudio" preload src="./music/music.mp3">当前浏览器不支持播放音频</audio>
<div class="music">
<div class="songInfo">
<p id="songName"></p>
<p id="singerName"></p>
</div>
<div class="player">
<img class="animation" id="img" src="./images/cover.jpg">
<a class="btn" href="javascript:" id="btn" onclick="play_pause()"></a>
</div>
<div class="lrc_box">
<div class="lrc" id="lrc">
<p class="blank"></p>
</div>
</div>
</div>
<div class="control">
<p><a class="btn" href="javascript:" id="btn2" onclick="play_pause()"></a></p>
<div class="time_progressBar">
<div class="time">
<p class="currentTime" id="currentTime">00:00</p>
<p>/</p>
<p class="duration" id="duration">00:00</p>
</div>
<div class="progressBar" id="progressBar">
<p class="progress" id="progress"></p>
</div>
</div>
<div class="volume" id="volume">
<div class="volumeBox" id="volumeBox">
<div class="volumeBar" id="volumeBar">
<p class="currentVolume" id="currentVolume"></p>
</div>
<div class="volumeText">
<span id="currentVolumeText">0</span><span>%</span>
</div>
</div>
<div id="volumeBtn">
<!-- 音量按钮svg图标 -->
<svg class="svg" height="26" viewBox="0 0 1080 1080" width="26">
<path class="path"
d="M541.2 132.3c-7.2-3.3-15.2-5-23.3-5-13.9 0-26.8 5.1-36.5 14.4L280.6 312.4h-158c-11.5 0-23.9 3.9-33.6 10.4h-4l-8.7 9.2c-7.5 7.9-12.2 20.8-12.2 33.8v290.7c0 15 6.7 31.4 16.9 41.6 10.2 10.3 26.6 16.9 41.6 16.9h158.2l200.7 165.6c8.4 7.9 23.3 16 35.6 16 5.6 0 16.5 0 27.1-8 10-4.2 17.8-10.4 23.1-18.6 5.7-8.6 8.4-19.1 8.4-32V184c0-12.9-2.8-23.4-8.4-32-5.8-9-14.6-15.6-26.1-19.7z m-24.8 57.4v642.9L310.2 662.5l-8.2-6.8v0.1H123.3V371.7h179l214.1-182z"
id="volumeIcon"></path>
<path class="path"
d="M752.7 376.7c-23.8-27.4-48.4-40.2-53.7-42.1h-1.6l-1.9-1.3c-3.2-2.2-7.4-3.3-12-3.3-11.9 0-24.9 7.6-27.8 16.4l-0.3 1-0.6 0.9c-3.8 5.8-4.3 14.2-1.4 22.7 2.9 8.4 8.6 15.1 14.6 17.1l0.6 0.2 0.6 0.3c0.7 0.4 17.2 9.5 33.6 29.7 15.1 18.5 33.1 50.3 33.1 96.7 0 96.6-54.4 128.5-60.7 131.9-13.8 9.4-23 27.9-14.6 40.4l0.3 0.4 0.2 0.5c4.3 8.7 18.9 18.6 27.3 18.6 5.5 0 8.4-0.1 11.7-3.8l2.2-2.2H704.7c6.2-2.3 28.7-15.6 50.6-44.2 20.6-26.9 45.1-74.4 45.1-147.4 0-64.2-25.9-107.5-47.7-132.5z"
id="volume_1_icon"
style="display:inline-block"></path>
<path class="path"
d="M899.3 300c-20.9-32.7-46.5-61.1-75.9-84.2l-0.1-0.1c-1.3-1.1-2.2-1.8-2.9-2.4-4.5-2.8-9.6-4.3-15.1-4.3-10.3 0-21.6 5.3-31.1 14.5l-0.1 0.1c-8.4 13.5-3.9 35.9 9 45.2l0.1 0.1c6.1 2.5 14.7 9.7 20.8 15.4 9.4 8.7 23.6 23.7 38.1 45.6 24.3 36.8 53.3 99.7 53.3 190.8 0 91-27.4 153.9-50.3 190.6-13.7 22-27.2 36.9-36.1 45.6-8.6 8.3-15.3 13.5-20.1 15.5l-0.1 0.1c-13.9 9.8-17.2 28.6-8.1 46.7 3.8 7.7 17.2 12.9 27.3 12.9 3.8 0 13.9 0 17.4-3.5 0.7-0.7 1.5-1.4 3.3-2.8 28.1-22.9 52.6-51 72.7-83.8 38.8-63.2 58.5-137.6 58.5-221.4 0-83.4-20.4-157.6-60.6-220.6z"
id="volume_2_icon"
style="display:none"></path>
<path class="path"
d="m670.797714 328.2c10.532571 0 20.918857 3.876571 28.891429 11.776l119.149714 117.467429 119.222857-117.394286a40.96 40.96 0 0 1 57.782857 0 39.789714 39.789714 0 0 1-0.146285 56.978286l-119.003429 117.394285 119.003429 117.394286a39.789714 39.789714 0 0 1 0 56.905143 41.106286 41.106286 0 0 1-57.709715 0l-119.149714-117.321143-119.222857 117.248a41.106286 41.106286 0 0 1-57.636572 0 39.789714 39.789714 0 0 1 0-56.978286l119.003429-117.321142-119.003429-117.394286a39.789714 39.789714 0 0 1 0-56.978286 40.813714 40.813714 0 0 1 28.818286-11.776z"
id="volume_mute_icon"
style="display: none"></path>
</svg>
</div>
</div>
</div>
</body>
<!-- 引入js文件 -->
<script type="text/javascript" src="./code.js"></script>
</html>
js代码
let duration = 0;
let percentage = null;
let currentVolume = 0;
let muted = false;
let timer = {startPlay: null, playing: null, warp: null, peachBlossom: null};
onload = function () {
Window.myAudio = document.getElementById("myAudio");
myAudio.volume = 0.5;
currentVolume = myAudio.volume;
setVolume(currentVolume);
}
let currentTime = document.getElementById("currentTime");
let progress = document.getElementById("progress");
// 音频播放结束后触发的事件
myAudio.addEventListener('ended', function () {
clearInterval(timer.playing);
clearInterval(timer.peachBlossom);
peachBlossom.disappear();
img.classList.remove("animation");
btn.style.opacity = "1";
btn2.classList.remove("btn_change");
currentTime.innerText = "00:00";
progress.style.width = "0";
lrc.style.transform = "translateY(0px)";
del_backgroundImage(lyric.length - 1);
})
myAudio.addEventListener("timeupdate", function () {
//监听音频播放的实时时间事件
let timeDisplay = Math.round(myAudio.currentTime * 100) / 100;
// 判断是否正在修改播放进度(正在修改进度时,不改变进度条样式)
if (!changeProgress) {
progress.style.width = Math.round(timeDisplay / percentage * 100) / 100 + "%";
}
//用秒数来显示当前播放进度
timeDisplay = Math.floor(timeDisplay);
//分钟
let minutes = parseInt(timeDisplay / 60);
if (minutes < 10) {
minutes = "0" + minutes;
}
//秒
let seconds = Math.round(timeDisplay % 60);
if (seconds < 10) {
seconds = "0" + seconds;
}
currentTime.innerText = minutes + ":" + seconds;
})
class PeachBlossom {
constructor(width,height,petalNumber) {
this.peachBlossom = new Image();
this.peachBlossom.src = "./images/peachBlossom.png";
let canvas = document.getElementById("canvas");
canvas.width = width;
canvas.height = height;
this.ctx = canvas.getContext("2d");
this.width = width;
this.height = height;
this.petalNumber = petalNumber;
let that = this;
this.peachBlossom.onload = function () {
that.drawWidth = that.peachBlossom.width / 10; // 50
that.drawHeight = that.peachBlossom.height / 10; // 50
that.initialize();
}
}
// 更新canvas
update(){
// 清空画布
this.ctx.clearRect(0,0,this.width,this.height);
// for循环更新所有花瓣坐标
for (let i = 0; i < 100; i++) {
this.petal[i].showX += this.petal[i].angle;
this.petal[i].showY += this.petal[i].speed;
// 判断当前花瓣是否超出屏幕高度,如果超出屏幕高度再重新随机选择一个花瓣,显示位置也随机
if(this.petal[i].showY > this.height+this.drawHeight){
let index = Math.floor(Math.random()*(this.petalNumber-1)+1);
this.petal[i].drawX = (index % 10) * this.drawWidth;
this.petal[i].drawY = Math.floor(index / 10) * this.drawHeight;
this.petal[i].showX = Math.floor(Math.random()*(this.width-1)+1);
this.petal[i].showY = -(Math.random() * this.height);
this.petal[i].speed = (Math.random()*(13-7+1)+7) / 10;
this.petal[i].angle = Math.random();
if(Math.round(Math.random()*10)%2){
this.petal[i].angle = -this.petal[i].angle;
}
}
// 渲染更新后的花瓣
this.ctx.drawImage(this.peachBlossom,this.petal[i].drawX,this.petal[i].drawY,this.drawWidth,this.drawHeight,this.petal[i].showX,this.petal[i].showY,this.drawWidth,this.drawHeight)
}
}
// 花瓣渐渐消失
disappear(){
let that = this;
let transparency = 1;
let interval = setInterval(function () {
transparency -= 0.01;
// 这里里的this指向的是Window,所以要把Class类的this指向赋值给that
that.ctx.clearRect(0,0,that.width,that.height);
that.ctx.globalAlpha = transparency;
for (let i = 0; i < that.petal.length; i++) {
that.petal[i].showX += that.petal[i].angle;
that.petal[i].showY += that.petal[i].speed;
that.ctx.drawImage(that.peachBlossom,that.petal[i].drawX,that.petal[i].drawY,that.drawWidth,that.drawHeight,that.petal[i].showX,that.petal[i].showY,that.drawWidth,that.drawHeight)
}
if (transparency < 0){
// 已消失,重置canvas的数据
that.ctx.clearRect(0,0,that.width,that.height);
that.ctx.globalAlpha = 1;
// 重置数据
that.initialize();
// 清除当前定时器
clearInterval(interval);
}
},18)
}
initialize(){
this.petal = [];
for (let i = 0; i < 100; i++) {
let obj = {};
let index = Math.floor(Math.random()*(this.petalNumber-1)+1);
// 剪切图片的X坐标
obj.drawX = (index % 10) * this.drawWidth;
// 剪切图片的Y坐标
obj.drawY = Math.floor(index / 10) * this.drawHeight;
// 显示在屏幕的X坐标
obj.showX = Math.floor(Math.random()*(this.width-1)+1);
// 显示在屏幕的Y坐标
obj.showY = -(Math.random() * this.height);
// 飘落速度
obj.speed = (Math.random()*(13-7+1)+7) / 10;
// 飘落倾斜角度
obj.angle = Math.random();
if (obj.angle > 0.5){
obj.angle = Math.random();
}
if(Math.round(Math.random()*10)%2){
obj.angle = -obj.angle
}
this.petal.push(obj)
}
}
}
// 初始化对象
let peachBlossom = new PeachBlossom(window.screen.width,window.screen.height,100);
// 获取id为img的元素
let img = document.getElementById("img");
let lrc = document.getElementById("lrc");
function playMusic() {
if (duration <= 0) {
// 设置音频总时长
setDuration(myAudio.duration);
}
// 获取当前音频播放时间
let currentTime = myAudio.currentTime;
if (currentTime == myAudio.duration) {
img.classList.add("animation");
myAudio.currentTime = 0;
document.getElementById("lrc").style.transform = "translateY(0px)";
} else if (currentTime > 0) {
currentTime = currentTime.toFixed(3);
let arr = currentTime.split(".");
currentTime = (Number(arr[0]) * 1000) + Number(arr[1]);
}
// 定时更新、渲染canvas的数据
timer.peachBlossom = setInterval(function () {
peachBlossom.update()
},18)
img.style.animationPlayState = "running";
// 当前是播放状态,隐藏播放按钮
btn.style.opacity = "0";
// 给第二个播放按钮添加类名以修改样式
btn2.classList.add("btn_change");
if (currentTime < lyric[0][0][0]) {
// 当前未开始渲染歌词
document.getElementById("line_0").classList.add("currentLine");
myAudio.play();
let start = new Date().getTime()
// 未需要渲染歌词,(定时器)等待需要渲染歌词再执行playing函数
timer.startPlay = setTimeout(function () {
playing(0, 0, 0)
}, lyric[0][0][0] - currentTime)
} else if (currentTime > lyric[lyric.length - 1][0][0]) {
// 渲染最后一行歌词
let lastLine = lyric.length - 1;
let translate = -(lastLine * 40 - 80);
document.getElementById("line_" + lastLine).classList.add("currentLine");
for (let i = 0; i < lyric[lastLine].length; i++) {
document.getElementById("word_" + lastLine + "_" + i).style.backgroundImage = "-webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(255,255,255,0) 100%), -webkit-linear-gradient(left, #f00 100%, #000 0%)";
if (currentTime < lyric[lastLine][i][0] + lyric[lastLine][i][1]) {
// 调整歌词,等待换行
adjustLyricProgress(currentTime, lastLine, i, translate);
break;
} else if (i === lyric[lastLine].length - 1) {
// 歌词全部渲染完毕(已唱完)
document.getElementById("lrc").style.transform = "translateY(" + translate + "px)";
myAudio.play();
}
}
} else {
let translate = 0;
for (let i = 1; i <= lyric.length; i++) {
if (currentTime < lyric[i][0][0]) {
if (i > 2) {
translate = -((i-3) * 40);
}
// 给当前行歌词添加类名以调整当前行歌词样式
document.getElementById("line_" + (i - 1)).classList.add("currentLine");
for (let j = 0; j < lyric[i - 1].length; j++) {
document.getElementById("word_" + (i - 1) + "_" + j).style.backgroundImage = "-webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(255,255,255,0) 100%), -webkit-linear-gradient(left, #f00 100%, #000 0%)";
// 判断当前是否是当前行歌词的最后一个字
if (j === lyric[i - 1].length - 1) {
// 判断当前行歌词最后一个字是否渲染完成
if (currentTime < lyric[i - 1][j][0] + lyric[i - 1][j][1]) {
// 当前行最后一字未完成,调整歌词
adjustLyricProgress(currentTime, i - 1, j, translate);
} else {
// 当前行最后一字已完成
myAudio.play();
lrc.style.transform = "translateY(" + translate + "px)";
timer.startPlay = setTimeout(function () {
document.getElementById("line_" + (i - 1)).classList.remove("currentLine");
del_backgroundImage(i - 1);
document.getElementById("line_" + i).classList.add("currentLine");
// 当前行已结束,调整歌词
translate -= 40;
lrc.style.transform = "translateY(" + translate + "px)";
playing(i, 0, translate);
}, lyric[i][0][0] - currentTime)
}
} else if (currentTime < lyric[i - 1][j + 1][0]) {
adjustLyricProgress(currentTime, i - 1, j, translate);
break;
}
}
break;
}
}
}
}
function pauseMusic() {
// 暂停,清楚所有定时器
clearInterval(timer.peachBlossom);
clearInterval(timer.playing);
clearTimeout(timer.startPlay);
clearTimeout(timer.warp);
// 显示播放按钮
btn.style.opacity = "1";
// 删除第二个播放按钮类名以修改样式
btn2.classList.remove("btn_change");
// 暂停动画
img.style.animationPlayState = "paused";
// 暂停音频
myAudio.pause();
}
function playing(lineNumber, wordNumber, translate = 0) {
clearInterval(timer.peachBlossom);
// 获取当前字的元素
let element = document.getElementById("word_" + lineNumber + "_" + wordNumber);
// 定时器间隔时间
let intervalTime = 18;
// 当前字的时长
let duration = lyric[lineNumber][wordNumber][1];
// 当前字已播放时间
let pastTime = 0;
// 获取当前行的元素
let currentLine = document.getElementById("line_" + lineNumber);
// 开始时间
let startTime = new Date().getTime();
timer.playing = setInterval(function () {
// 定时执行里面代码
pastTime += intervalTime;
// 设置样式
element.style.backgroundImage = "-webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(255,255,255,0) 100%), -webkit-linear-gradient(left, #f00 " + ((pastTime / duration).toFixed(2) * 100) + "%, #000 0%)";
// 判断当前字是否已渲染完成
if (pastTime >= duration) {
// 当前字已播放完成,下一字
wordNumber++;
// 判断当前行是否已播放完成
if (wordNumber === lyric[lineNumber].length) {
// 下一行
lineNumber++;
if (lineNumber === lyric.length) {
// 最后一行播放完成,播放结束
timer.peachBlossom = setInterval(function () {
peachBlossom.update()
},intervalTime)
// clearInterval(timer.playing);
// 播放结束
return;
} else {
// 下一行
// 计算换行的时间
let warpIntervalTime = lyric[lineNumber][0][0] - (lyric[lineNumber - 1][wordNumber - 1][0] + lyric[lineNumber - 1][wordNumber - 1][1]);
wordNumber = 0
if (warpIntervalTime > 5) {
clearInterval(timer.playing);
timer.peachBlossom = setInterval(function () {
peachBlossom.update()
},intervalTime);
timer.warp = setTimeout(() => {
if (lineNumber > 2 && lineNumber < lyric.length - 2) {
// 歌词元素继续向上移动
translate -= 40;
// 设置歌词向上移动
lrc.style.transform = "translateY(" + translate + "px)";
}
// 清除当前行的class类名
currentLine.classList.remove("currentLine");
// 重新获取当前行
currentLine = document.getElementById("line_" + lineNumber);
// 给当前行设置class类名
currentLine.classList.add("currentLine");
// 继续渲染歌词
clearInterval(timer.peachBlossom);
playing(lineNumber, 0, translate);
// 清除上一行已渲染的歌词样式
del_backgroundImage(lineNumber - 1);
}, warpIntervalTime)
return;
}
// 继续下一行
if (lineNumber > 2 && lineNumber < lyric.length - 2) {
// 歌词元素继续向上移动
translate -= 40;
// 设置歌词向上移动
lrc.style.transform = "translateY(" + translate + "px)";
}
// 清除当前行的class类名
currentLine.classList.remove("currentLine");
// 重新获取当前行
currentLine = document.getElementById("line_" + lineNumber);
// 给当前行设置class类名
currentLine.classList.add("currentLine");
// 清除上一行已渲染的歌词样式
del_backgroundImage(lineNumber - 1);
}
}
// 重新下一个字的元素
element = document.getElementById("word_" + lineNumber + "_" + wordNumber);
// 获取时间戳
let endTime = new Date().getTime();
// 上一个字执行结束时间减去上一个字开始执行时间,再减去上一个字的时长得出误差时间
let errorTime = (endTime - startTime) - duration;
// 将结束时间作为下一个字开始执行的时间
startTime = endTime;
// 重新计算当前字的时长(当前字时长减去定时器误差)
duration = lyric[lineNumber][wordNumber][1] - errorTime;
// 当前字已播放时间设为0
pastTime = 0;
}
peachBlossom.update();
}, intervalTime)
}
let progressBar = document.getElementById("progressBar");
let body = document.getElementsByTagName("body")[0];
let changeProgress = false;
// 监听元素鼠标按下事件
progressBar.onmousedown = function (event) {
// 判断是否为鼠标左键 0为鼠标左键,1为鼠标中键,2为鼠标右键
if (event.button === 0) {
changeProgress = true;
// 修改进度(样式)
progress.style.width = ((Math.floor(((event.clientX - progressBar.getBoundingClientRect().left) / progressBar.getBoundingClientRect().width) * 10000) / 100)) + "%";
// 监听鼠标移动事件
body.onmousemove = function (event) {
let progressPercentage = Math.floor(((event.clientX - progressBar.getBoundingClientRect().left) / progressBar.getBoundingClientRect().width) * 10000) / 100;
if (progressPercentage < 0) {
progressPercentage = 0;
} else if (progressPercentage > 100) {
progressPercentage = 100;
}
progress.style.width = progressPercentage + "%";
}
}
}
// 监听鼠标松开事件
body.onmouseup = function (event) {
// 判断当前是否在拖动播放进度条
if (changeProgress) {
changeProgress = false;
body.onmousemove = null;
myAudio.currentTime = myAudio.duration / 100 * parseFloat(progress.style.width);
let currentLine = document.getElementsByClassName("currentLine")[0];
if (currentLine) {
del_backgroundImage(currentLine.getAttribute("data-lineNumber"))
currentLine.classList.remove("currentLine");
}
// 修改了播放进度,先暂停再播放
pauseMusic();
playMusic();
}
}
// 根据id获取元素
let volume = document.getElementById("volume");
let currentVolumeElement = document.getElementById("currentVolume");
let currentVolumeText = document.getElementById("currentVolumeText");
let volumeBox = document.getElementById("volumeBox");
let volumeBar = document.getElementById("volumeBar");
// 监听volumeBar元素的鼠标按下事件
volumeBar.onmousedown = function (event) {
currentVolume = Math.round((1 - ((event.clientY - volumeBar.getBoundingClientRect().top) / volumeBar.getBoundingClientRect().height)) * 100) / 100
setVolume(currentVolume);
if (muted) {
// 更改静音状态
muted = false;
// 更改音频的静音状态
myAudio.muted = false;
}
// 监听volumeBox元素的鼠标移动事件
volumeBox.onmousemove = function (event) {
currentVolume = Math.round(100 - (event.clientY - volumeBar.getBoundingClientRect().top)) / 100;
if (currentVolume < 0) {
currentVolume = 0;
} else if (currentVolume > 1) {
currentVolume = 1
}
setVolume(currentVolume);
}
}
// 监听volume元素的鼠标松开事件
volume.onmouseup = function () {
// 鼠标松开音量元素的区域,取消监听volumeBox元素的鼠标移动
volumeBox.onmousemove = null;
}
// 监听volume元素的鼠标移出事件
volume.onmouseleave = function () {
// 鼠标移出音量元素的区域,取消监听volumeBox元素的鼠标移动
volumeBox.onmousemove = null;
}
// 监听volume元素的鼠标滚动事件
volume.onmousewheel = function (event) {
// 参数deltaY为100时,鼠标滚轮向下滚动;参数deltaY为-100时,鼠标滚轮向上滚动
if (event.deltaY > 0) {
if (muted) {
// 当前已是静音,不能减音量。return返回
return;
}
currentVolume -= 0.05;
} else {
if (muted) {
// 当前是静音,所以直接把音量加到5
currentVolume = 0.05;
// 更改静音状态
muted = false;
// 更改音频的静音状态
myAudio.muted = muted;
} else {
// 当前非静音,所以在原音量的基础上加5
currentVolume += 0.05;
}
}
setVolume(currentVolume);
}
// 根据id获取元素
let volumeBtn = document.getElementById("volumeBtn");
let volume_1_icon = document.getElementById("volume_1_icon");
let volume_2_icon = document.getElementById("volume_2_icon");
let volume_mute_icon = document.getElementById("volume_mute_icon");
// 监听volumeBtn(音量按钮)元素的鼠标点击事件
volumeBtn.onclick = function (event) {
muted = !muted;
myAudio.muted = muted;
if (muted) {
// 设置为静音
setVolume(0, false)
} else {
setVolume(myAudio.volume, false)
}
}
// 设置音量
function setVolume(currentVolume, changeVolume = true) {
if (currentVolume > 1) {
currentVolume = 1;
} else if (currentVolume <= 0) {
currentVolume = 0;
volume_1_icon.style.display = "none";
volume_2_icon.style.display = "none";
volume_mute_icon.style.display = "inline-block";
} else {
currentVolume = Math.round(currentVolume * 100) / 100;
}
if (currentVolume > 0.33) {
volume_1_icon.style.display = "inline-block";
volume_2_icon.style.display = "inline-block";
volume_mute_icon.style.display = "none";
} else if (currentVolume > 0) {
volume_1_icon.style.display = "inline-block";
volume_2_icon.style.display = "none";
volume_mute_icon.style.display = "none";
}
if (changeVolume) {
myAudio.volume = currentVolume;
}
currentVolumeElement.style.height = (currentVolume * 100) + "px";
currentVolumeText.innerText = parseInt(currentVolume * 100);
}
let lyric = getLrc();
let btn = document.getElementById("btn");
let btn2 = document.getElementById("btn2");
// 监听播放、播放的按钮的点击事件
function play_pause() {
// 根据音频的播放状态执行对应函数
if (myAudio.paused) {
playMusic();
} else {
pauseMusic();
}
}
// 设置进度条的时间
function setDuration(time) {
duration = time;
percentage = Math.round(duration) / 100;
let minutes = "00:";
if (duration >= 60) {
minutes = parseInt(duration / 60);
if (minutes < 10) {
minutes = "0" + minutes + ":"
}
}
let seconds = parseInt(duration % 60);
if (seconds < 10) {
seconds = "0" + seconds
}
document.getElementById("duration").innerText = minutes + seconds;
}
// 调整歌词进度
function adjustLyricProgress(currentTime, lineNumber, wordNumber, translate) {
lrc.style.transform = "translateY(" + translate + "px)";
let pastTime = currentTime - lyric[lineNumber][wordNumber][0];
// 获取当前字的元素
let element = document.getElementById("word_" + lineNumber + "_" + wordNumber);
// 定时器间隔时间
let intervalTime = 18;
// 当前字的时长
let duration = lyric[lineNumber][wordNumber][1];
if (duration % intervalTime > intervalTime / 2) {
duration += duration % intervalTime;
} else {
duration -= duration % intervalTime;
}
element.style.backgroundImage = "-webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(255,255,255,0) 100%), -webkit-linear-gradient(left, #f00 " + ((pastTime / duration).toFixed(2) * 100) + "%, #000 0%)";
myAudio.play();
timer.playing = setInterval(function () {
pastTime += intervalTime;
element.style.backgroundImage = "-webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(255,255,255,0) 100%), -webkit-linear-gradient(left, #f00 " + ((pastTime / duration).toFixed(2) * 100) + "%, #000 0%)";
if (pastTime >= duration) {
// 当前字结束
clearInterval(timer.playing);
if (wordNumber < lyric[lineNumber].length - 1) {
playing(lineNumber, wordNumber + 1, translate);
} else if (lineNumber < lyric.length - 1) {
// 判断是否最后一行,如果不是最后一行则继续执行
translate -= 40;
lrc.style.transform = "translateY(" + translate + "px)";
timer.startPlay = setTimeout(function () {
document.getElementById("line_" + lineNumber).classList.remove("currentLine");
del_backgroundImage(lineNumber);
document.getElementById("line_" + (lineNumber + 1)).classList.add("currentLine");
playing(lineNumber + 1, 0, translate);
}, lyric[lineNumber + 1][0][0] - (lyric[lineNumber][wordNumber][0] + lyric[lineNumber][wordNumber][1]))
}
}
}, intervalTime)
}
// 清楚上一行渲染的歌词样式
function del_backgroundImage(lineNumber) {
for (let i = 0; i < lyric[lineNumber].length; i++) {
document.getElementById("word_" + lineNumber + "_" + i).style.backgroundImage = "-webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0) 100%), -webkit-linear-gradient(left, #000 0%, #000 0%)";
}
}
// 从txt文件获取歌词数据
function getLrc() {
let txt = "";
let ajax = new XMLHttpRequest();
ajax.open("GET", "./music/music.txt", false);
ajax.onreadystatechange = function () {
txt = ajax.responseText;
};
ajax.send(null);
// 将字符串以回车符(\n)分割成每一行文本为一个数组元素
let arr = txt.split("\n");
// 歌词内容
let lyric = [];
let lrcElement = document.getElementById("lrc")
// 有些不是歌词,所以lineNumber从0开始,判断是歌词行,再加1。这样动态添加的元素的id和class类名序号能跟数组索引对上
let lineNumber = 0;
for (let i = 0; i < arr.length; i++) {
// 删除每行文本全部的\r符号
arr[i] = arr[i].replace(/[\r]/g, "");
// 如果当前行文本存在"<"和">"符号,说明该行文本是歌词
if (arr[i].indexOf("<") > -1 && arr[i].indexOf(">") > -1) {
lrcElement.innerHTML += "<p id=line_" + lineNumber + " data-lineNumber=" + lineNumber + "></p>";
// 每行歌词是一个一维数组元素,每个字是一个二维数组(包含"开始时间","时长","文字")
// 每一行的开始时间(转为数值类型)
let startTime = Number(arr[i].slice(1, arr[i].indexOf(",")));
// 截取改行文本从"]"符号开始,一直到最后的文本
arr[i] = arr[i].slice(arr[i].indexOf("]") + 1);
// 每一行的数据
let line = [];
let nextTime = 0;
let lineElement = document.getElementById("line_" + lineNumber)
while (arr[i]) {
let str = arr[i].slice(0, arr[i].indexOf(">") + 1);
let time = Number(str.slice(str.indexOf(",") + 1, str.lastIndexOf(",")));
nextTime += time;
arr[i] = arr[i].slice(str.length);
let word = "";
let index = arr[i].indexOf(String(nextTime));
if (index > -1) {
word = arr[i].slice(0, index - 1);
} else {
word = arr[i];
}
lineElement.innerHTML += "<span id=word_" + lineNumber + "_" + line.length + " class=word_" + lineNumber + "_" + line.length + ">" + word + "</span>";
arr[i] = arr[i].slice(word.length);
line.push([startTime, time, word]);
startTime += time;
}
lyric.push(line);
lineNumber++;
} else if (arr[i].indexOf("total:") > -1) {
let total = arr[i].slice(7, arr[i].indexOf("]"))
if (total) {
setDuration(total / 1000);
}
} else if (arr[i].indexOf("ti:") > -1) {
// 歌名
let songName = arr[i].slice(4, arr[i].indexOf("]"));
document.getElementById('songName').innerText = "歌名:" + songName;
} else if (arr[i].indexOf("ar:") > -1) {
// 歌手
let singerName = arr[i].slice(4, arr[i].indexOf("]"));
document.getElementById('singerName').innerText = "歌手:" + singerName;
}
}
return lyric;
}
css代码
body {
user-select: none;
overflow: hidden;
}
#canvas {
position: absolute;
}
audio {
display: none;
}
.music {
margin: 20px auto 0;
text-align: center;
font-family: "楷体", "楷体_GB2312";
cursor: default;
}
.music .songInfo p {
display: inline-block;
margin: 0 50px;
}
.player {
position: relative;
margin: 50px auto;
width: 200px;
height: 200px;
background-color: #333;
border-radius: 50%;
text-align: center;
}
.player img {
margin: 30px;
width: 140px;
height: 140px;
border-radius: 50%;
}
.player .animation {
animation: rotate 8s linear infinite;
animation-play-state: paused;
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg)
}
}
.btn {
display: inline-block;
width: 40px;
height: 40px;
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid #fff;
border-radius: 50%;
margin: -20px;
position: absolute;
top: 50%;
left: 50%;
}
.btn:after {
content: "";
display: inline-block;
border: 12px solid transparent;
border-left-color: #fff;
border-radius: 15%;
position: absolute;
top: 8px;
left: 16px;
}
.music .songInfo {
font-size: 28px;
}
.music .lrc_box {
height: 362px;
overflow: hidden;
}
.lrc_box .lrc {
/* 歌词向上移动的过渡动画 */
transition: transform 200ms;
}
.lrc_box .lrc .blank {
height: 80px;
}
.lrc_box .lrc p {
line-height: 40px;
font-size: 26px;
padding: 0;
margin: 0;
/* 文字放大和缩小的过渡动画 */
transition: font-size 500ms;
position: relative;
}
.lrc_box .lrc p > span {
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0) 100%), -webkit-linear-gradient(left, #000 0%, #000 0%);
}
.lrc_box .lrc .currentLine {
font-size: 30px;
}
.control {
width: 102%;
height: 50px;
background-image: linear-gradient(#f0f0f0, #5c8bfc, #005cec, #051959);
position: fixed;
left: -2%;
bottom: 2px;
display: flex;
justify-content: space-evenly;
cursor: default;
padding: 20px;
}
.control>p{
position: relative;
top: -10px;
}
.control .btn {
width: 30px;
height: 30px;
left: 20px;
top: 24px;
}
.control .btn:after {
border: 10px solid transparent;
border-left-color: #fff;
border-radius: 15%;
top: 5px;
left: 12px;
}
.control .btn:before {
content: "";
width: 14px;
height: 14px;
border-style: double;
border-width: 0 0 0 12px;
border-color: #fff;
position: absolute;
top: 8px;
left: 9px;
display: none;
}
.control .btn_change:after {
display: none;
}
.control .btn_change:before {
display: inline-block;
}
.control .time_progressBar {
width: 50%;
display: flex;
align-items: center;
}
.control .time {
display: flex;
margin-right: 8px;
}
.control .time p {
margin-right: 3px;
}
.control .progressBar {
background-color: #fff;
width: 100%;
height: 6px;
border-radius: 30px;
cursor: pointer;
}
.control .progressBar .progress {
background-color: #0010ce;
width: 0;
height: 6px;
border-radius: 30px;
position: relative;
margin: 0;
}
.control .progressBar .progress:after {
content: "";
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #fff;
position: absolute;
right: -8px;
top: 50%;
margin: -6px 0;
}
.progressBar:hover .progress:after {
opacity: 1 !important;
}
.progressBar:active .progress:after {
opacity: 1 !important;
}
.volume {
width: 66px;
position: relative;
top: 12px;
cursor: pointer;
text-align: center;
}
.path {
fill: #fff;
}
.volume:hover .path {
fill: #00f;
}
.volume:hover .volumeBox {
display: flex;
}
.volumeBox {
width: 66px;
background-color: #f0f0f0;
position: absolute;
bottom: 66px;
left: 0;
border-radius: 6px;
/* 改这里为display:none隐藏 */
display: none;
flex-direction: column;
align-items: center;
padding: 22px 0;
}
.volumeBox:after {
z-index: 9999;
content: "";
display: block;
width: 0;
height: 0;
border: 12px solid transparent;
border-top-color: #f0f0f0;
position: absolute;
left: 17px;
bottom: -24px;
}
.volumeBar {
width: 4px;
height: 100px;
border-radius: 30px;
margin: 0;
background-color: #ccc;
cursor: pointer;
transform: rotateZ(180deg);
}
.currentVolume {
background-color: #2182e8;
width: 4px;
height: 0%;
border-radius: 30px;
position: relative;
margin: 0;
}
.currentVolume:after {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #2182e8;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -4px;
}
.volumeText {
margin: 10px 0 0 0;
color: #999;
}
注意:由于浏览器默认是不允许本地“跨域”请求,所以直接打开html文件,无法加载txt歌词文件,所以需要用到IDE编辑器打开(大部分IDE会默认创建本地服务器)或者通过网络请求加载txt文件!