一、观前说明:
1.本人为新手,很多地方可能写得不好,欢迎指正。
2.本人仍在学习CSS中,在本篇中若有写得不好的地方,欢迎指正。
3.本人尚未系统性的学习过JS(还没学到),在这里用到的JS全是靠以前学其他语言积累下的基础,因此在很多地方也会写得不够好,欢迎指正。
4.因HTML部分和CSS部分较为简单,本篇文章会更注重于讲JS部分,其中一些说明我会写在代码里面,我认为这比我写在外面更好理解。
5.我是将整个播放器写完了才写的这篇文章,所以你们在看代码的时候,会看到一些提前写了的代码,比如播放暂停还没讲到修改时间间距,但在代码处已经有了。
二、最终效果
2.1、包含功能
1.播放暂停
2.歌词动画,显示当前歌词
3.拖拽歌词,调整歌词进度
4.进度条调整歌词进度
5.音量控制
2.2、图片展示
三、现在开始
1.搭建基础框架
框架图:
按照框架图,搭建框架:
其中歌词是后面由JS创建的,因此只需要有一个div包住就行
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/music.css" />
<link rel="icon" href="img/icon.jpg" />
<title>音乐播放器</title>
</head>
<body>
<!-- 音乐 -->
<audio src="audio/韩安旭 - 不在.mp3" preload=""></audio>
<!-- 背景板 -->
<div class="background">
<!-- 音乐区 -->
<div class="music-background">
<!-- 左侧图片 -->
<div class="img-back">
<img src="img/musicCover2.jpg" alt="韩安旭 - 不在" />
</div>
<!-- 右侧歌词 -->
<div class="lyrics-back">
<!-- 歌曲信息 -->
<div class="div-title">
<h1></h1>
<p style="margin-right: 100px">
专辑:
<span></span>
</p>
<p>
歌手:
<span></span>
</p>
</div>
<!-- 歌词信息 -->
<div class="lyrics-time">
<a href="#" class="goto"></a>
<a href="#" class="goto-time">00:00</a>
</div>
<div class="div-lyrics"></div>
</div>
</div>
<!-- 控制区 -->
<div class="control-back">
<!-- 进度条 -->
<div class="progress-bar">
<div class="progress-all"></div>
<div class="progress-now"></div>
</div>
<span class="time">00:00/00:00</span>
<!-- 控制按钮 -->
<div class="control">
<!-- 上一首、下一首、暂停播放 -->
<div class="control-btn">
<a href="#" class="up"></a>
<a href="#" class="play-pause" onclick="setPlay()"></a>
<a href="#" class="down"></a>
</div>
<!-- 播放模式、声音 -->
<div class="control-right">
<a href="#" class="mode"></a>
<a href="#" class="volume" onclick="setMuted()"></a>
<div class="volume-back">
<div class="volume-all"></div>
<div class="volume-now"></div>
</div>
<span class="volume-text">100</span>
</div>
</div>
</div>
</div>
<script src="js/music.js"></script>
</body>
</html>
2.CSS美化
2.1清除所有边距已经禁止选择
* {
margin: 0;
padding: 0;
user-select: none;
}
h1 {
font-weight: 500;
}
a {
color: #000;
text-decoration: none;
}
2.2设置背景样式(大盒子)
/* 背景样式,相当于body */
.background {
width: 100vw;
height: 100vh;
background-color: #fff;
}
/* 上半部分样式,即包含封面以及歌词的盒子 */
.music-background {
width: 1000px;
height: calc(100vh - 200px);
margin: 0 auto;
overflow: hidden;
}
/* 图片盒子 */
.img-back {
width: 300px;
padding: 0 50px;
margin-top: 150px;
margin-right: 50px;
overflow: hidden;
float: left;
}
/* 歌词盒子 */
.lyrics-back {
width: 550px;
height: 100%;
overflow: hidden;
float: left;
}
/* 歌曲信息盒子 */
.div-title {
width: 100%;
height: 100px;
margin-top: 150px;
}
/* 控制区域盒子 */
.control-back {
width: 100vw;
height: 80px;
bottom: 0;
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
}
2.3设置图片、字体样式
只是简简单单的调整大小边距等,甚至在歌曲信息中的文字段落标签我都没有设置字体大小,事实上这并不是一种好的习惯
.img-back img {
width: 300px;
height: 300px;
border-radius: 20px;
}
.div-title h1 {
width: 100%;
height: 50px;
}
.div-title p {
height: 50px;
display: inline-block;
color: rgba(0, 0, 0, 0.5);
}
.div-title span {
color: #000;
}
.div-lyrics p {
font-size: 15px;
line-height: 34px;
}
2.4设置控制按钮、进度条、音量控制样式
这里我的控制按钮用的都是a标签,因此需要将a标签修改为行内块标签inline-block
其中".lyrics-time"是拖拽歌词时显示的调整进度的按钮,因此在默认状态下需要隐藏
.lyrics-time a {
display: inline-block;
position: absolute;
display: none;
}
.goto {
width: 20px;
height: 20px;
margin: 7px 15px;
background: url(../img/play.png) no-repeat;
background-position: 50% 50%;
background-size: 20px;
}
.goto-time {
width: 50px;
height: 34px;
margin-left: 550px;
font-size: 13px;
line-height: 34px;
text-align: center;
position: absolute;
color: #5192fe;
}
/* 进度条 */
.progress-bar {
width: calc(100vw - 100px);
margin: 5px 0;
padding: 5px 0;
cursor: pointer;
float: left;
}
/* 当前播放进度 */
.progress-now {
width: 0;
height: 1px;
margin-top: -1px;
background-color: #5192fe;
}
/* 总播放进度 */
.progress-all {
width: 100%;
height: 1px;
background-color: #dee2e6;
}
/* 播放进度 文本 */
.time {
display: inline-block;
width: 90px;
height: 21px;
margin-left: 5px;
text-align: center;
color: rgba(128, 130, 133, 0.8);
cursor: default;
overflow: hidden;
}
/* 控制按钮 */
.control {
width: 100%;
height: 30px;
margin-top: 19px;
}
.control a {
margin: 0 5px;
display: inline-block;
}
.control a:hover {
opacity: 50%;
}
.control-btn {
width: 130px;
height: 30px;
margin: 0 auto;
}
/* 上一首 */
.up {
width: 30px;
height: 30px;
background: url(../img/up.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 暂停播放 */
.play-pause {
width: 30px;
height: 30px;
background: url(../img/play.png) no-repeat;
background-size: 30px;
background-position: 50% 50%;
}
/* 下一首 */
.down {
width: 30px;
height: 30px;
background: url(../img/down.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 右侧控制按钮 */
.control-right {
/* width: 180px; */
height: 30px;
margin-left: calc(50vw + 150px);
margin-top: -30px;
display: flex;
align-items: center;
}
/* 播放模式 */
.mode {
width: 30px;
height: 30px;
background: url(../img/sequence.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
/* 声音控制 */
.volume {
width: 30px;
height: 30px;
background: url(../img/volume.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
.volume-back {
padding: 5px 0;
cursor: pointer;
}
.volume-all {
width: 100px;
height: 2px;
background-color: #e5e5e5;
}
.volume-now {
width: 100px;
height: 2px;
margin-top: -2px;
max-width: 100px;
background-color: #5192fe;
}
.volume-text {
margin-left: 10px;
font-size: 14px;
}
3.JS部分
3.1首先是声明一些变量、数组等,因为这一部分是要多处使用的,因此在最开头就声明了
var myAduio = document.getElementsByTagName('audio')[0];
var divLyrics = document.getElementsByClassName('div-lyrics')[0];
var divTitle = document.getElementsByClassName('div-title')[0];
var lyricsTime = document.getElementsByClassName('lyrics-time')[0];
var lyricsTime_a = lyricsTime.getElementsByTagName('a');
var progressTime = document.getElementsByClassName('time')[0];
var nowLine = 0;
var lyricsMove = false;
var playState = false;
var lyrics, lyricsStyle, lyricsFirst, rollT;
var timeArray1 = new Array();
var timeArray2 = new Array();
var timeInterval = new Array();
这里面分别是
audio控件,播放音乐用的;
歌词div,到时候要在这里面创建歌词
歌曲信息div,也是在这里面修改歌曲信息的内容
拖拽歌词调整进度的div "lyricsTime"以及里面的按钮 “lyricsTime_a”
进度条右侧的时间"progressTime"
当前播放行"nowLine"
拖拽状态"lyricsMove"
播放状态"playState"
所有歌词的p标签"lyrics",第一行歌词的样式"lyricsStyle",第一行歌词的p标签"lyricsFirst",歌词滚动的计时器"rollT"
以秒数记录每一行歌词所在时间的数组"timeArray1"以及以分钟:秒数的星石记录每一行歌词所在时间的数组"timeArray2",用两种方式记录时间的原因是后面需要以这两种形式交叉使用,也可以只记录一种,在使用到另一种的时候将其算出来,但我感觉直接记录两种会更方便
记录每一行歌词时间间距的数组"timeInterval"
3.2页面加载完毕后就需要执行的方法
window.onload = function () {
initialLyrics();
lyricsStyle = getComputedStyle(lyricsFirst, null);
setLyrics(0);
setMouseEvent();
setTimeText();
};
3.3初始化歌词(创建歌词并存储一些信息)
sp、ar、ti分别为专辑、艺术家、歌名
function initialLyrics() {
let sp = divTitle.getElementsByTagName('span')[0];
let ar = divTitle.getElementsByTagName('span')[1];
let ti = divTitle.getElementsByTagName('h1')[0];
let lyricsData, timeString;
let lyricsArray = new Array();
// 清除数组,先清除数组可以保证每一次存储的信息无误
timeArray1.splice(0, timeArray1.length);
timeArray2.splice(0, timeArray2.length);
lyricsArray.splice(0, lyricsArray.length);
// 按相同格式放入歌词更换歌曲即可达到相同效果
lyricsData =
'[ar]韩安旭\n[ti]不在\n[sp]不在\n[00:00.74]韩安旭 - 不在\n[00:01.76]词:尤雅琪\n[00:02.76]曲:胜屿\n[00:15.62]我累了就紧紧锁住情绪\n[00:18.11]不再放任它堆积\n[00:22.14]我痛了就静静屏住呼吸\n[00:26.02]不给想念留余地\n[00:28.88]只是下雨时会委屈\n[00:32.80]只是想起你会哭泣\n[00:36.77]没关系 真没关系\n[00:44.28]我终于学会一个人弹琴\n[00:47.12]只是弹琴没有你\n[00:49.29]我终于学会一个人做梦\n[00:54.85]只是做梦没有你\n[00:57.73]我依旧像从前粗心\n[01:01.09]时常会忘记星期几\n[01:05.00]却始终忘不掉你看我的眼睛\n[01:11.71]穿过了熙攘的人海\n[01:15.11]想找谁能把你取代\n[01:19.62]复制你曾给过我的\n[01:21.44]那种宠爱\n[01:26.32]掏空了回忆的脑海\n[01:30.75]寂寞却狠狠扑过来\n[01:33.69]措手不及 无法躲开\n[01:41.52]我承认是我太依赖\n[01:44.92]像个不懂事的小孩\n[01:48.35]挥霍掉我们的未来\n[01:51.22]才醒过来\n[01:55.15]我承认后悔了伤害\n[01:59.06]抛开你的好我的坏\n[02:02.14]直到如今学会忍耐 你不在\n[02:26.95]我终于学会一个人弹琴\n[02:29.33]只是弹琴没有你\n[02:33.26]我终于学会一个人做梦\n[02:36.64]只是做梦没有你\n[02:39.53]我依旧像从前粗心\n[02:42.90]时常会忘记星期几\n[02:46.82]却始终忘不掉你看我的眼睛\n[02:53.62]穿过了熙攘的人海\n[02:57.09]想找谁能把你取代\n[03:00.98]复制你曾给过我的\n[03:05.43]那种宠爱\n[03:08.25]掏空了回忆的脑海\n[03:11.67]寂寞却狠狠扑过来\n[03:15.56]措手不及 无法躲开\n[03:22.49]我承认是我太依赖\n[03:26.37]像个不懂事的小孩\n[03:30.38]挥霍掉我们的未来\n[03:33.80]才醒过来\n[03:37.81]我承认后悔了伤害\n[03:41.29]抛开你的好我的坏\n[03:44.73]直到如今学会忍耐 你不在';
// 文本.split('分隔符'),用于分割文本
lyricsArray = lyricsData.split('\n');
// 添加歌曲信息
ar.innerText = lyricsArray[0].split(']')[1];
ti.innerText = lyricsArray[1].split(']')[1];
sp.innerText = lyricsArray[2].split(']')[1];
// 添加歌词
for (var i = 3; i < lyricsArray.length; i++) {
//这里i=3即我们的第一行歌词,将高度设置为100%,在页面创建完毕时添加一个上滚的动画
if (i == 3) {
divLyrics.innerHTML +=
'<p style="margin-top: 100%;color:#5192fe;">' +
lyricsArray[i].split(']')[1] +
'</p>';
} else {
divLyrics.innerHTML +=
'<p>' + lyricsArray[i].split(']')[1] + '</p>';
}
}
// 获取后续需要使用的变量
lyricsFirst = divLyrics.getElementsByTagName('p')[0];
lyrics = divLyrics.getElementsByTagName('p');
// 计算每局歌词所在的秒数
timeArray1.push(0);
for (var i = 0; i < lyrics.length; i++) {
timeString = lyricsArray[i + 3].substring(1, 9).split(':');
timeArray1.push(
parseFloat(timeString[0]) * 60 + parseFloat(timeString[1])
);
}
// 计算时间间隔,将时间从秒数改为分钟+秒数
for (var i = 0; i < timeArray1.length - 1; i++) {
timeInterval[i] = timeArray1[i + 1] - timeArray1[i];
// timeArray2数组主要是显示使用的,在秒数部分,若为个位数,十位数补零会更为美观
if (Math.floor(timeArray1[i] % 60) < 10) {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':0' +
Math.floor(timeArray1[i] % 60)
);
} else {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':' +
Math.floor(timeArray1[i] % 60)
);
}
}
}
歌词初始化完后,整个页面就可以正常显示了,接下来先写控制播放暂停的功能
3.3播放暂停
// 设置播放状态
function setPlay(state) {
var play_pause = document.getElementsByClassName('play-pause')[0];
if (state == null) {
// 如果歌曲为暂停状态,那么获取到的state则为true,将state设置为true我们就播放
state = myAduio.paused;
}
// 清除计时器,不然会出现多个计时器同时进行
clearTimeout(rollT);
if (state == true) {
myAduio.play();
play_pause.style.backgroundImage = 'url(../img/pause.png)';
playState = true;
// 开始播放的同时,同时开始设置时间进度文本,歌词滚动以及进度条位置
setTimeText();
lyricsRoll();
setProgress();
} else {
myAduio.pause();
play_pause.style.backgroundImage = 'url(../img/play.png)';
playState = false;
// 暂停后重新修改计时器时间,这里后面会详细讲
timeInterval[nowLine] = timeArray1[nowLine + 1] - myAduio.currentTime;
}
}
3.4设置时间进度文本
// 设置进度文本
function setTimeText() {
var nowTime = myAduio.currentTime;
var allTime = myAduio.duration;
// 计算时间,若为个位数,补0
if (Math.floor(nowTime % 60) < 10) {
nowTime = Math.floor(nowTime / 60) + ':0' + Math.floor(nowTime % 60);
} else {
nowTime = Math.floor(nowTime / 60) + ':' + Math.floor(nowTime % 60);
}
if (Math.floor(allTime % 60) < 10) {
allTime = Math.floor(allTime / 60) + ':0' + Math.floor(allTime % 60);
} else {
allTime = Math.floor(allTime / 60) + ':' + Math.floor(allTime % 60);
}
progressTime.innerText = nowTime + '/' + allTime;
// 每0.1秒执行一次
if (myAduio.paused == false) {
setTimeout(setTimeText, 100);
}
}
3.5设置进度条位置
// 设置进度条进度
function setProgress() {
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
let progress = Math.floor(
(myAduio.currentTime / myAduio.duration) * progress_bar.clientWidth
);
progress_now.style.width = progress + 'px';
if (myAduio.paused == false) {
setTimeout(setProgress, 100);
}
}
到这里,除了歌词滚动、调整进度等,一个最简单的音乐播放器就已经完成了
3.6设置歌词位置
// 设置歌词位置
function setLyrics(line) {
// 将当前歌词高亮,其余歌词都改为黑色
for (let i = 0; i < lyrics.length; i++) {
lyrics[i].style.color = '#000';
}
lyrics[line].style.color = '#5192fe';
// 设置动画,这里只需要改变第一行歌词的位置即可
// 这里是将第一行歌词的marginTop从当前的位置修改为当前进度歌词所在行的对应位置,因为我将p标签的行高设置为了34px,而我要将当前歌词在第五行显示,即前面要有四行空的,可以得到34*4=136px的marginTop,line * (-34) + 136即我需要向上移动多少行
// 假设我的当前歌词是在第一行显示,当前歌词在第一行(数组从零开始,所以这里在数组里是0),那么marginTop就是0,即0 * (-34),当前歌词在第二行,那么marginTop就是-34px,即1 * (-34),往后以此类推
// 同上,比如当前歌词在第一行,但是我需要将第一行歌词显示在第五行,即前面有四个空的位置,那么我第一行歌词就需要将marginTop修改到第五行的位置,那么第一行歌词的marginTop就是136px,即0 * (-34) +136
let lyrics_animation = lyrics[0].animate(
[
{
marginTop: lyricsStyle.marginTop,
},
{
marginTop: line * -34 + 136 + 'px',
},
],
{
duration: 100,
}
);
//设置监视器,在动画完成之后,修改第一行歌词的marginTop,这里不用"fill:forwards"的原因是使用后,拖拽歌词将无法使用
lyrics_animation.addEventListener(
'finish',
function () {
lyrics[0].style.marginTop = line * -34 + 136 + 'px';
},
false
);
}
设置歌词写好了,我们现在就需要考虑如何让歌词滚动起来,即让歌词自动到达当前行的位置
我想到的方法有两种
第一种方法是根据每句歌词之间的时间间隔,每过一个间隔,就触发一次方法,我这里用的就是这种方法;
第二种方法是每过很短的一段时间,就触发一次方法,就跟进度条类似;
我观察过酷狗以及QQ音乐,其中酷狗用的应该就是第一种方法,是过一个间隔触发一次的,而QQ音乐则是用的第二种方法。
我个人认为第二种方法写起来更简单,但是需要的性能更高,但是进度条、以及时间进度文本是必须使用第二种方法的(至少我想不到其他的),那么如果将进度条、时间进度文本、歌词滚动这三个方法放在一个计时器中调用,而不是像我这样分开三个计时器,哪一种是更优的方法就不得而知了,欢迎各位大佬给出自己的看法。
那么我们这里用第一种方法,首先我们先分析一下需要记录的内容(其实前面已经记录了,只是在这里才说)
第一个是要记录歌词,这个不需要多的解释,显示用的
第二个需要记录时间间隔,这个是用来设置计时器的
第三个需要记录每一行歌词所在的时间,分两个记录,一个按秒数记录,用来拖拽歌词时调整进度;另一个按分:秒记录,用于拖拽歌词时显示时间;两个数组可只记录一个,用到另一个的时候算出来就好,我为了方便就记录了两个
前面三个都是要用数组记录,第四个则是非数组,用于记录当前歌词到了哪一行,在歌词滚动中,歌词回弹等地方都会用到。
需要记录的东西我们分析完了,那么接下来在画一个逻辑图来分析计时器要如何设置,即歌词滚动要如何实现
分析完毕,那么继续
3.7歌词滚动
// 歌词滚动
// 歌词滚动的方法只需要按照时间间隔设置计时器即可
function lyricsRoll() {
rollT = setTimeout(function () {
if (nowLine < lyrics.length && myAduio.paused == false) {
if (lyricsMove == false) {
setLyrics(nowLine);
}
nowLine += 1;
lyricsRoll();
}
}, timeInterval[nowLine] * 1000);
}
// 以下代码不在这个地方,在设置播放状态处,只是放在这里做解释
// 每当我们暂停歌曲时,我们就需要将当前行到下一行即从i到i+1的时间间距,从原来的,修改为剩余的,比如本来跳到下一行需要10s,而我是在第5s的时候暂停的,那么剩余时间就是5s,但我们无法直接知道在这一行歌词我们用了多少时间,所以我们就要用下一行歌词的时间(不是间距,而是在整首歌中这一行歌词在哪一秒)减去当前的播放进度,这样得到的值就是我们需要的剩余间距,所以代码是
timeInterval[nowLine] = timeArray1[nowLine + 1] - myAduio.currentTime;
到这里,播放器的歌词滚动部分已经写好了,但现在的播放器只能播放暂停,看到歌词在哪,不能调整进度等,接下来我们就来解决这个问题
3.8歌词拖拽、音量控制、调整进度
function setMouseEvent() {
// 歌词拖拽
let lyrics_Y, line;
// 此处是调整歌词位置,以及计算用户将歌词拖拽到了哪一行
divLyrics.onmousedown = function (e) {
if (lyricsMove == false) {
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'block';
lyricsMove = true;
}
lyrics_Y = parseInt(lyricsStyle.marginTop);
document.onmousemove = function (event) {
lyricsFirst.style.marginTop =
event.clientY - (e.clientY - lyrics_Y) + 'px';
line = Math.floor(-(parseInt(lyricsStyle.marginTop) - 170) / 34);
if (line < 0) {
line = 0;
} else if (line > lyrics.length - 1) {
line = lyrics.length - 1;
}
lyricsTime_a[1].innerText = timeArray2[line];
};
document.onmouseup = function () {
// Y1的作用是判断用户是否仍处于拖拽状态,若一秒后歌词位置仍等于Y1,则判断为非拖拽状态,但这样写有一个bug就是如果只是按住鼠标不进行拖拽,则也会判断为非拖拽状态,暂时想不到解决方法
var lyrics_Y1 = parseInt(lyricsStyle.marginTop);
setTimeout(function () {
if (parseInt(lyricsStyle.marginTop) == lyrics_Y1) {
lyricsMove = false;
setLyrics(nowLine - 1);
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
}
}, 1000);
// 清除鼠标移动方法的同时也一定要清除鼠标弹起方法,不然每次点击页面都会调用这个方法
document.onmousemove = null;
document.onmouseup = null;
};
// 防止选中文字
return false;
};
// 音量控制
// 音量控制没什么好说的,需要注意的是volume的音量是从0-1,所以在设置音量时要将其除以100
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_back = document.getElementsByClassName('volume-back')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
volume_back.onmousedown = function (e) {
volume_now.style.width = e.offsetX + 'px';
myAduio.volume = e.offsetX / 100;
volume_text.innerText = volume_now.clientWidth;
volume_back.onmousemove = function (ev) {
let volume = ev.offsetX;
if (volume > 100) {
volume = 100;
}
volume_now.style.width = volume + 'px';
myAduio.volume = volume / 100;
volume_text.innerText = volume_now.clientWidth;
};
document.onmouseup = function () {
// 如果音量为0,更换静音图片,否则更换非静音图片
if (myAduio.volume == 0) {
volume_a.style.backgroundImage = 'url(../img/mute.png)';
} else {
volume_a.style.backgroundImage = 'url(../img/volume.png)';
}
volume_back.onmousemove = null;
document.onmouseup = null;
};
return false;
};
// 进度控制
// 进度控制跟音量控制一样,只需要注意算法就好
// 从之前的设置进度条进度我们可用知道,进度条的宽度=当前播放进度/歌曲总时长*进度条总长
// 所以 当前播放进度=进度条宽度/进度条总长*歌曲总时长
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
progress_bar.onmousedown = function (e) {
progress_now.style.width = e.offsetX + 'px';
myAduio.pause();
myAduio.currentTime =
(e.offsetX * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
progress_bar.onmousemove = function (ev) {
let progress = ev.offsetX;
if (progress > progress_bar.clientWidth) {
progress = progress_bar.clientWidth;
}
progress_now.style.width = progress + 'px';
myAduio.currentTime =
(progress * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
};
document.onmouseup = function () {
myAduio.play();
for (var i = 0; i < timeArray1.length; i++) {
if (myAduio.currentTime < timeArray1[i]) {
// 获取调整进度后当前处于哪一行歌词,假设我调整后的进度是1s,那么在数组中第一个大于1s的的上一个,就是当前行,所以需要-1
// nowLine是i - 1 但是设置歌词位置却是 i - 2 的原因,因为我们的timeArray1是在开头多加了一个元素的,也就是说timeArray1数组比歌词数组多了一个元素,所以要再-1,而在lyricsRoll方法中,我是先设置计时器再将nowLine+1的,所以这里的nowLine要+1,那么总体结果就是,nowLine = i -1 , 设置歌词为 i - 2
nowLine = i - 1;
setLyrics(i - 2);
timeInterval[nowLine] =
timeArray1[nowLine + 1] - myAduio.currentTime;
setPlay(true);
break;
}
}
progress_bar.onmousemove = null;
document.onmouseup = null;
};
return false;
};
// 拖拽歌词调整进度,需要注意的也只是算出用户拖到了第几行而已,在拖拽歌词事件中(在上面)我们就已经将行数算出来了,这里直接调用就可用
// 也说一下算法,因为我们是按照行来设置第一行歌词的marginTop的,那么反推过来就是
// 行数=-(第一行歌词的marginTop-136)/34
// 同样的,因为timeArray2数组多一个元素,所以显示拖拽歌词时显示的歌曲进度就要+1,我这里设置歌词位置是line - 1的原因是我在算行数的时候,将136改为了170,即我在算行数的时候就已经+1了
let goto = document.getElementsByClassName('goto')[0];
goto.onmouseup = function () {
nowLine = line;
myAduio.currentTime = timeArray1[line];
setLyrics(line - 1);
setPlay(true);
lyricsMove = false;
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
document.onmouseup = null;
};
}
3.9设置静音
这个比较简单,就不说了
// 设置静音
function setMuted() {
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
if (myAduio.muted == true) {
myAduio.muted = false;
volume_a.style.backgroundImage = 'url(../img/volume.png)';
volume_now.style.width = myAduio.volume * 100 + 'px';
volume_text.innerText = myAduio.volume * 100;
} else {
myAduio.muted = true;
volume_a.style.backgroundImage = 'url(../img/mute.png)';
volume_now.style.width = '0';
volume_text.innerText = '0';
}
}
到这里,我们的整个播放器就已经全部完成了
以下是完整代码
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/music.css" />
<link rel="icon" href="img/icon.jpg" />
<title>音乐播放器</title>
</head>
<body>
<!-- 音乐 -->
<audio src="audio/韩安旭 - 不在.mp3" preload=""></audio>
<!-- 背景板 -->
<div class="background">
<!-- 音乐区 -->
<div class="music-background">
<!-- 左侧图片 -->
<div class="img-back">
<img src="img/musicCover2.jpg" alt="韩安旭 - 不在" />
</div>
<!-- 右侧歌词 -->
<div class="lyrics-back">
<!-- 歌曲信息 -->
<div class="div-title">
<h1></h1>
<p style="margin-right: 100px">
专辑:
<span></span>
</p>
<p>
歌手:
<span></span>
</p>
</div>
<!-- 歌词信息 -->
<div class="lyrics-time">
<a href="#" class="goto"></a>
<a href="#" class="goto-time">00:00</a>
</div>
<div class="div-lyrics"></div>
</div>
</div>
<!-- 控制区 -->
<div class="control-back">
<!-- 进度条 -->
<div class="progress-bar">
<div class="progress-all"></div>
<div class="progress-now"></div>
</div>
<span class="time">00:00/00:00</span>
<!-- 控制按钮 -->
<div class="control">
<!-- 上一首、下一首、暂停播放 -->
<div class="control-btn">
<a href="#" class="up"></a>
<a href="#" class="play-pause" onclick="setPlay()"></a>
<a href="#" class="down"></a>
</div>
<!-- 播放模式、声音 -->
<div class="control-right">
<a href="#" class="mode"></a>
<a href="#" class="volume" onclick="setMuted()"></a>
<div class="volume-back">
<div class="volume-all"></div>
<div class="volume-now"></div>
</div>
<span class="volume-text">100</span>
</div>
</div>
</div>
</div>
<script src="js/music.js"></script>
</body>
</html>
CSS
* {
margin: 0;
padding: 0;
user-select: none;
}
h1 {
font-weight: 500;
}
a {
color: #000;
text-decoration: none;
}
/* 背景板 */
.background {
width: 100vw;
height: 100vh;
background-color: #fff;
}
/* 歌词封面背景 */
.music-background {
width: 1000px;
height: calc(100vh - 200px);
margin: 0 auto;
overflow: hidden;
}
/* 封面背景 */
.img-back {
width: 300px;
padding: 0 50px;
margin-top: 150px;
margin-right: 50px;
overflow: hidden;
float: left;
}
.img-back img {
width: 300px;
height: 300px;
border-radius: 20px;
}
/* 歌词背景 */
.lyrics-back {
width: 550px;
height: 100%;
overflow: hidden;
float: left;
}
/* 歌曲信息 */
.div-title {
width: 100%;
height: 100px;
margin-top: 150px;
}
.div-title h1 {
width: 100%;
height: 50px;
}
.div-title p {
height: 50px;
display: inline-block;
color: rgba(0, 0, 0, 0.5);
}
.div-title span {
color: #000;
}
.div-lyrics {
height: calc(100vh - 450px);
overflow: hidden;
position: relative;
}
.div-lyrics p {
font-size: 15px;
line-height: 34px;
}
/* 歌词进度 */
.lyrics-time {
width: 650px;
height: 34px;
margin-top: 136px;
margin-left: -50px;
position: absolute;
display: none;
}
.lyrics-time a {
display: inline-block;
position: absolute;
display: none;
}
.goto {
width: 20px;
height: 20px;
margin: 7px 15px;
background: url(../img/play.png) no-repeat;
background-position: 50% 50%;
background-size: 20px;
}
.goto-time {
width: 50px;
height: 34px;
margin-left: 550px;
font-size: 13px;
line-height: 34px;
text-align: center;
position: absolute;
color: #5192fe;
}
/* 控制背景 */
.control-back {
width: 100vw;
height: 80px;
bottom: 0;
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
}
/* 进度条 */
.progress-bar {
width: calc(100vw - 100px);
margin: 5px 0;
padding: 5px 0;
cursor: pointer;
float: left;
}
/* 当前播放进度 */
.progress-now {
width: 0;
height: 1px;
margin-top: -1px;
background-color: #5192fe;
}
/* 总播放进度 */
.progress-all {
width: 100%;
height: 1px;
background-color: #dee2e6;
}
/* 播放进度 文本 */
.time {
display: inline-block;
width: 90px;
height: 21px;
margin-left: 5px;
text-align: center;
color: rgba(128, 130, 133, 0.8);
cursor: default;
overflow: hidden;
}
/* 控制按钮 */
.control {
width: 100%;
height: 30px;
margin-top: 19px;
}
.control a {
margin: 0 5px;
display: inline-block;
}
.control a:hover {
opacity: 50%;
}
.control-btn {
width: 130px;
height: 30px;
margin: 0 auto;
}
/* 上一首 */
.up {
width: 30px;
height: 30px;
background: url(../img/up.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 暂停播放 */
.play-pause {
width: 30px;
height: 30px;
background: url(../img/play.png) no-repeat;
background-size: 30px;
background-position: 50% 50%;
}
/* 下一首 */
.down {
width: 30px;
height: 30px;
background: url(../img/down.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 右侧控制按钮 */
.control-right {
/* width: 180px; */
height: 30px;
margin-left: calc(50vw + 150px);
margin-top: -30px;
display: flex;
align-items: center;
}
/* 播放模式 */
.mode {
width: 30px;
height: 30px;
background: url(../img/sequence.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
/* 声音控制 */
.volume {
width: 30px;
height: 30px;
background: url(../img/volume.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
.volume-back {
padding: 5px 0;
cursor: pointer;
}
.volume-all {
width: 100px;
height: 2px;
background-color: #e5e5e5;
}
.volume-now {
width: 100px;
height: 2px;
margin-top: -2px;
max-width: 100px;
background-color: #5192fe;
}
.volume-text {
margin-left: 10px;
font-size: 14px;
}
JS
var myAduio = document.getElementsByTagName('audio')[0];
var divLyrics = document.getElementsByClassName('div-lyrics')[0];
var divTitle = document.getElementsByClassName('div-title')[0];
var lyricsTime = document.getElementsByClassName('lyrics-time')[0];
var lyricsTime_a = lyricsTime.getElementsByTagName('a');
var progressTime = document.getElementsByClassName('time')[0];
var nowLine = 0;
var lyricsMove = false;
var playState = false;
var lyrics, lyricsStyle, lyricsFirst, rollT;
var timeArray1 = new Array();
var timeArray2 = new Array();
var timeInterval = new Array();
window.onload = function () {
initialLyrics();
lyricsStyle = getComputedStyle(lyricsFirst, null);
setLyrics(0);
setMouseEvent();
setTimeText();
};
// 设置事件
function setMouseEvent() {
// 歌词拖拽
let lyrics_Y, line;
divLyrics.onmousedown = function (e) {
if (lyricsMove == false) {
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'block';
lyricsMove = true;
}
lyrics_Y = parseInt(lyricsStyle.marginTop);
document.onmousemove = function (event) {
lyricsFirst.style.marginTop =
event.clientY - (e.clientY - lyrics_Y) + 'px';
line = Math.floor(-(parseInt(lyricsStyle.marginTop) - 170) / 34);
if (line < 0) {
line = 0;
} else if (line > lyrics.length - 1) {
line = lyrics.length - 1;
}
lyricsTime_a[1].innerText = timeArray2[line];
};
document.onmouseup = function () {
var lyrics_Y1 = parseInt(lyricsStyle.marginTop);
setTimeout(function () {
if (parseInt(lyricsStyle.marginTop) == lyrics_Y1) {
lyricsMove = false;
setLyrics(nowLine);
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
}
}, 1000);
document.onmousemove = null;
document.onmouseup = null;
};
// 防止选中文字
return false;
};
// 音量控制
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_back = document.getElementsByClassName('volume-back')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
volume_back.onmousedown = function (e) {
volume_now.style.width = e.offsetX + 'px';
myAduio.volume = e.offsetX / 100;
volume_text.innerText = volume_now.clientWidth;
volume_back.onmousemove = function (ev) {
let volume = ev.offsetX;
if (volume > 100) {
volume = 100;
}
volume_now.style.width = volume + 'px';
myAduio.volume = volume / 100;
volume_text.innerText = volume_now.clientWidth;
};
document.onmouseup = function () {
if (myAduio.volume == 0) {
volume_a.style.backgroundImage = 'url(../img/mute.png)';
} else {
volume_a.style.backgroundImage = 'url(../img/volume.png)';
}
volume_back.onmousemove = null;
document.onmouseup = null;
};
return false;
};
// 进度控制
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
progress_bar.onmousedown = function (e) {
progress_now.style.width = e.offsetX + 'px';
myAduio.pause();
myAduio.currentTime =
(e.offsetX * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
progress_bar.onmousemove = function (ev) {
let progress = ev.offsetX;
if (progress > progress_bar.clientWidth) {
progress = progress_bar.clientWidth;
}
progress_now.style.width = progress + 'px';
myAduio.currentTime =
(progress * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
};
document.onmouseup = function () {
myAduio.play();
for (var i = 0; i < timeArray1.length; i++) {
if (myAduio.currentTime < timeArray1[i]) {
nowLine = i - 1;
setLyrics(i - 2);
timeInterval[nowLine] =
timeArray1[nowLine + 1] - myAduio.currentTime;
setPlay(true);
break;
}
}
progress_bar.onmousemove = null;
document.onmouseup = null;
};
return false;
};
let goto = document.getElementsByClassName('goto')[0];
goto.onmouseup = function () {
nowLine = line;
myAduio.currentTime = timeArray1[line];
setLyrics(line - 1);
setPlay(true);
lyricsMove = false;
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
document.onmouseup = null;
};
}
// 设置播放状态
function setPlay(state) {
var play_pause = document.getElementsByClassName('play-pause')[0];
if (state == null) {
state = myAduio.paused;
}
clearTimeout(rollT);
if (state == true) {
myAduio.play();
play_pause.style.backgroundImage = 'url(../img/pause.png)';
playState = true;
setTimeText();
lyricsRoll();
setProgress();
// 开始播放后要重新将时间间距改回来,不然下次播放计时器会出错
timeInterval[nowLine] = timeArray1[nowLine + 1] - timeArray1[nowLine];
} else {
myAduio.pause();
play_pause.style.backgroundImage = 'url(../img/play.png)';
playState = false;
timeInterval[nowLine] = timeArray1[nowLine + 1] - myAduio.currentTime;
}
}
// 设置音量
function setVolume(volume) {
myAduio.volume = volume;
}
// 设置静音
function setMuted() {
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
if (myAduio.muted == true) {
myAduio.muted = false;
volume_a.style.backgroundImage = 'url(../img/volume.png)';
volume_now.style.width = myAduio.volume * 100 + 'px';
volume_text.innerText = myAduio.volume * 100;
} else {
myAduio.muted = true;
volume_a.style.backgroundImage = 'url(../img/mute.png)';
volume_now.style.width = '0';
volume_text.innerText = '0';
}
}
// 歌词回弹
function lyricsRebound(lyricsTop) {
if (parseInt(lyricsStyle.marginTop) != nowLine * -34 + 136) {
if (lyricsTop == null) {
lyricsTop = nowLine * -34 + 136;
}
let lyrics_animation = lyricsFirst.animate(
[
{
marginTop: lyricsStyle.marginTop,
},
{
marginTop: lyricsTop + 'px',
},
],
{
duration: 500,
}
);
lyrics_animation.addEventListener(
'finish',
function () {
lyricsFirst.style.marginTop = lyricsTop + 'px';
},
false
);
}
}
// 初始化歌词
function initialLyrics() {
let sp = divTitle.getElementsByTagName('span')[0];
let ar = divTitle.getElementsByTagName('span')[1];
let ti = divTitle.getElementsByTagName('h1')[0];
let lyricsData, timeString;
let lyricsArray = new Array();
// 清除数组
timeArray1.splice(0, timeArray1.length);
timeArray2.splice(0, timeArray2.length);
lyricsArray.splice(0, lyricsArray.length);
// 按相同格式放入歌词更换歌曲即可达到相同效果
lyricsData =
'[ar]韩安旭\n[ti]不在\n[sp]不在\n[00:00.74]韩安旭 - 不在\n[00:01.76]词:尤雅琪\n[00:02.76]曲:胜屿\n[00:15.62]我累了就紧紧锁住情绪\n[00:18.11]不再放任它堆积\n[00:22.14]我痛了就静静屏住呼吸\n[00:26.02]不给想念留余地\n[00:28.88]只是下雨时会委屈\n[00:32.80]只是想起你会哭泣\n[00:36.77]没关系 真没关系\n[00:44.28]我终于学会一个人弹琴\n[00:47.12]只是弹琴没有你\n[00:49.29]我终于学会一个人做梦\n[00:54.85]只是做梦没有你\n[00:57.73]我依旧像从前粗心\n[01:01.09]时常会忘记星期几\n[01:05.00]却始终忘不掉你看我的眼睛\n[01:11.71]穿过了熙攘的人海\n[01:15.11]想找谁能把你取代\n[01:19.62]复制你曾给过我的\n[01:21.44]那种宠爱\n[01:26.32]掏空了回忆的脑海\n[01:30.75]寂寞却狠狠扑过来\n[01:33.69]措手不及 无法躲开\n[01:41.52]我承认是我太依赖\n[01:44.92]像个不懂事的小孩\n[01:48.35]挥霍掉我们的未来\n[01:51.22]才醒过来\n[01:55.15]我承认后悔了伤害\n[01:59.06]抛开你的好我的坏\n[02:02.14]直到如今学会忍耐 你不在\n[02:26.95]我终于学会一个人弹琴\n[02:29.33]只是弹琴没有你\n[02:33.26]我终于学会一个人做梦\n[02:36.64]只是做梦没有你\n[02:39.53]我依旧像从前粗心\n[02:42.90]时常会忘记星期几\n[02:46.82]却始终忘不掉你看我的眼睛\n[02:53.62]穿过了熙攘的人海\n[02:57.09]想找谁能把你取代\n[03:00.98]复制你曾给过我的\n[03:05.43]那种宠爱\n[03:08.25]掏空了回忆的脑海\n[03:11.67]寂寞却狠狠扑过来\n[03:15.56]措手不及 无法躲开\n[03:22.49]我承认是我太依赖\n[03:26.37]像个不懂事的小孩\n[03:30.38]挥霍掉我们的未来\n[03:33.80]才醒过来\n[03:37.81]我承认后悔了伤害\n[03:41.29]抛开你的好我的坏\n[03:44.73]直到如今学会忍耐 你不在';
// 文本.split('分隔符'),用于分割文本
lyricsArray = lyricsData.split('\n');
// 添加歌曲信息
ar.innerText = lyricsArray[0].split(']')[1];
ti.innerText = lyricsArray[1].split(']')[1];
sp.innerText = lyricsArray[2].split(']')[1];
// 添加歌词
for (var i = 3; i < lyricsArray.length; i++) {
if (i == 3) {
divLyrics.innerHTML +=
'<p style="margin-top: 100%;color:#5192fe;">' +
lyricsArray[i].split(']')[1] +
'</p>';
} else {
divLyrics.innerHTML +=
'<p>' + lyricsArray[i].split(']')[1] + '</p>';
}
}
// 获取后续需要使用的变量
lyricsFirst = divLyrics.getElementsByTagName('p')[0];
lyrics = divLyrics.getElementsByTagName('p');
// 计算每局歌词所在的秒数
timeArray1.push(0);
for (var i = 0; i < lyrics.length; i++) {
timeString = lyricsArray[i + 3].substring(1, 9).split(':');
timeArray1.push(
parseFloat(timeString[0]) * 60 + parseFloat(timeString[1])
);
}
// 计算时间间隔,将时间从秒数改为分钟+秒数
for (var i = 0; i < timeArray1.length - 1; i++) {
timeInterval[i] = timeArray1[i + 1] - timeArray1[i];
if (Math.floor(timeArray1[i] % 60) < 10) {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':0' +
Math.floor(timeArray1[i] % 60)
);
} else {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':' +
Math.floor(timeArray1[i] % 60)
);
}
}
}
// 设置歌词位置
function setLyrics(line) {
for (let i = 0; i < lyrics.length; i++) {
lyrics[i].style.color = '#000';
}
lyrics[line].style.color = '#5192fe';
let lyrics_animation = lyrics[0].animate(
[
{
marginTop: lyricsStyle.marginTop,
},
{
marginTop: line * -34 + 136 + 'px',
},
],
{
duration: 100,
}
);
lyrics_animation.addEventListener(
'finish',
function () {
lyrics[0].style.marginTop = line * -34 + 136 + 'px';
},
false
);
}
// 歌词滚动
function lyricsRoll() {
rollT = setTimeout(function () {
if (nowLine < lyrics.length && myAduio.paused == false) {
if (lyricsMove == false) {
setLyrics(nowLine);
}
nowLine += 1;
lyricsRoll();
}
}, timeInterval[nowLine] * 1000);
}
// 设置进度文本
function setTimeText() {
var nowTime = myAduio.currentTime;
var allTime = myAduio.duration;
// 计算时间,若为个位数,补0
if (Math.floor(nowTime % 60) < 10) {
nowTime = Math.floor(nowTime / 60) + ':0' + Math.floor(nowTime % 60);
} else {
nowTime = Math.floor(nowTime / 60) + ':' + Math.floor(nowTime % 60);
}
if (Math.floor(allTime % 60) < 10) {
allTime = Math.floor(allTime / 60) + ':0' + Math.floor(allTime % 60);
} else {
allTime = Math.floor(allTime / 60) + ':' + Math.floor(allTime % 60);
}
progressTime.innerText = nowTime + '/' + allTime;
// 每0.1秒执行一次
if (myAduio.paused == false) {
setTimeout(setTimeText, 100);
}
}
// 设置进度条进度
function setProgress() {
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
let progress = Math.floor(
(myAduio.currentTime / myAduio.duration) * progress_bar.clientWidth
);
progress_now.style.width = progress + 'px';
if (myAduio.paused == false) {
setTimeout(setProgress, 100);
}
}
// 获取网页属性
function getDocument(attribute) {
if (attribute == 'sT') {
return document.documentElement.scrollTop;
} else if (attribute == 'sL') {
return document.documentElement.scrollLeft;
} else if (attribute == 'sH') {
return document.documentElement.scrollHeight;
} else if (attribute == 'sW') {
return document.documentElement.scrollWidth;
} else if (attribute == 'cH') {
return document.documentElement.clientHeight;
} else if (attribute == 'cW') {
return document.documentElement.clientWidth;
}
}
其中JS中,获取页面属性跟歌词回弹是没有用到的,获取页面属性我写是为了方便,但结果发现没用上,歌词回弹是后来发现直接用设置歌词方法来进行回弹即可,也就是说歌词回弹写了是多余的