手把手教你用HTML5撸个免费mp3播放器

前言 ✍️
学了那么多前端的知识点,今天我们就不谈别的,就来实战一下做一个简易的音乐播放器~
当然这也是为了让大家可以完全掌握使用HTML5+CSS+JavaScript来开发一个音乐播放的过程与一会,同时我们也可以利用技术来摆脱第三方平台限制,自由实现个性化功能与无广告纯净享受音乐乐趣为目的~ 哈哈哈 废话不多说,我们开始吧~
UI界面与功能描述 👓
首先你要做一个什么样子的播放器,要先想好呀 ,不然无处下手代码,我们可以参考一下网上有很多这样的案例
这里就不过多赘述了,大家自行百度查就行了~
我这里就做成如下形式
如图

那么这个音乐播放器大致的功能包含:重听、上一曲、下一曲、暂停 / 播放、快退 5 秒、快进 5 秒、双倍快退 10 秒、双倍快进 10 秒 这些功能也算是一个音乐播放器基本的功能了~
HTML基本结构 👔
有了以上的UI布局样式效果图与功能描述,那HTML+CSS的结构与样式代码就好办了~
以下是播放器的基本HTML结构
代码
<!--播放器 元素容器-->
<div class="player">
<!--标题-->
<h2 class="player-title">CUSTOM MUSIC PLAYER</h2>
<!--播放器描述-->
<p class="player-subtitle">MUSIC HAS THE POWER TO SOOTHE AND CALM THE MIND</p>
<!--当前播放的音乐名称-->
<div class="music-info" id="musicName">当前音乐的名称</div>
<!--播放器按钮-->
<div class="btn-group">
<!-- 重听按钮 -->
<button class="player-btn reset-btn" id="resetBtn">↺</button>
<!-- 上一曲 -->
<button class="player-btn" id="prevTrack">◄◄</button>
<!-- 停止 -->
<button class="player-btn" id="stopBtn">■</button>
<!-- 播放/暂停 -->
<button class="player-btn play-pause" id="playPauseBtn">❚❚</button>
<!-- 快退5秒 -->
<button class="player-btn" id="backBtn">◄</button>
<!-- 快进5秒 -->
<button class="player-btn" id="nextBtn">►</button>
<!-- 下一曲 -->
<button class="player-btn" id="nextTrack">►►</button>
<!-- 双倍快退(10秒) -->
<button class="player-btn speed-btn" id="doubleBackBtn">◄×2</button>
<!-- 双倍快进(10秒) -->
<button class="player-btn speed-btn" id="doubleNextBtn">►×2</button>
</div>
<!--原始音频元素-->
<audio id="audio" preload="auto"></audio>
这里我们定义了很多按钮和class类, 其中最多的就是button的样式类
一些特别的按钮,我们可以单独拿出来再定义样式就行了
如下
| 类名 | 描述 |
|---|---|
player-btn |
用来统一button元素的样式 |
reset-btn |
单独给重听按钮设置样式 |
play-pause |
单独给播放/暂停按钮设置样式 |
speed-btn |
单独给双倍快进快退按钮设置样式 |
我们直接在Chrome浏览器中运行,看看是什么效果!
效果如下

其实从代码中,我们不难看出,这些HTML元素,也仅仅是元素而已,它们并没有播放音乐的功能~
我们也只是在模拟,播放器的UI界面而已,真正能播放音乐的是audio元素,这里大家一定要清楚!
另外这个效果,是基本上的纯HTML效果,当然接下来,我们要使用CSS来美化一下它!
不然看起来也太丑了~🥹
CSS样式效果 👕
那么接下来,我们就根据这个HTML样式结构,来书写CSS样式~
容器player元素样式 ☂️
首先我们定义外部最大的容器元素player 的样式结构
如下
.player {
width: 500px; /*定义宽度*/
height: 520px; /*定义高度*/
padding: 40px; /*定义内边距*/
background: linear-gradient(135deg, #4158d0, #c850c0, #ffcc70); /*定义背景颜色 */
border-radius: 10px; /*定义圆角*/
text-align: center; /*定义文字整体居中*/
color: #fff; /*定义文字颜色*/
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); /*定义阴影*/
position: relative; /*定义相对定位*/
overflow: hidden; /*定义超出其宽高范围的内容被隐藏*/
margin: 100px auto; /*定义外边距 让其居中*/
}
那么现在效果如下
如图

嗯,这样有点那意思了~👨🏼💻 但还要继续优化CSS~
继续给这个player元素加点CSS样式
如下
.player::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.1), transparent 70%);
pointer-events: none;
}
这段代码的意思我是想通过伪元素给.player这个容器添加一个绝对定位的半透明径向渐变层,
注意这里可能有些朋友对background:radial-gradient和pointer-events: none;两个属性不是很了解,这里简单说明一下!
background:radial-gradient中的radial-gradient()函数可以用来创建一个图像, 而且这个图像是由从原点发出的两种或者多种颜色之间的逐步过渡组成~形状可以是圆形circle和椭圆形ellipse与其它渐变相同,径向渐变是一个不固定尺寸的图像,也就是说它没有首选的大小,也没有首选的比例。具体大小将由它所应用的元素的大小决定! 所以我这里设置从容器的30% 20%位置向外呈现由浅白透明到完全透明的圆形渐变效果~
pointer-events: none属性让该渐变层不遮挡下方元素的交互
加了之后效果如下
如图

而这个半透明径向渐变层我使用了::before把这个内容加在了player容器内部内容之前
我们也可以使用审查元素看到这个元素~
如图

这样整体为播放器容器增加通透的光影质感~ 当然这一段代码你也可以不用加,纯属个人审美加入~
头部文本样式 🧣
接着,我们对容器内部的文字元素:.player-title、.player-subtitle、.music-info 进行样式的设计
代码如下
/*播放器名*/
.player-title {
font-size: 30px;
margin-top: 50px;
letter-spacing: 3px;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
/*播放器描述*/
.player-subtitle {
font-size: 14px;
opacity: 0.7;
margin: 10px 0 50px;
letter-spacing: 1px;
color: #eee;
}
/*音乐名称描述*/
.music-info {
margin-bottom: 20px;
font-size: 18px;
color: #fff;
opacity: 0.9;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
这里我给文本添加了一些阴影,以及描述字幕的透明度等样式~
效果如下

播放器按钮样式 🦖
这个部分的CSS样式也是本例中比较多的地方~
首先,我把所有的按钮,都放到了一个.btn-group的元素容器里面~ 然后设置样式
如下
.btn-group {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 20px;
}
这里我们使用到了Flexbox弹性盒子布局~ 不明白什么事弹性布局的,赶紧去补习一下~
display:flex 让容器内的按钮等元素横向排列
justify-content:center 让这些元素在容器水平方向居中
align-items:center 让元素在垂直方向居中
gap:15px给元素之间留15像素的间距
margin-top:20px 让容器整体距离上方元素有 20 像素的距离~
效果如下

然后我们就可以很方便的去挨个操作里面的按钮样式了~
使用.player-btn来统一所有按钮的样式
代码如下
.player-btn {
width: 45px;
height: 45px;
border-radius: 50%;
border: 2px solid #fff;
background: transparent;
color: #fff;
font-size: 16px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s;
}
.player-btn:hover {
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
这里我们要注意最后的弹性布局, 设置display:flex、justify-content:center、align-items:center 是为了让按钮内部的内容水平垂直居中~
同时我们设置了transition:all 0.3s 的一个动画,让按钮所有样式变化都有 0.3 秒的过渡效果
整体就是做出一个45px 大小、白色边框、透明背景、内容居中然后鼠标移上去有手型且样式变化, 更顺滑的圆形播放按钮~
效果如下

最后我们还可以对一些特别想要处理的按钮进行单独CSS调整
例如
/*单独调整暂停播放键按钮*/
.player-btn.play-pause {
width: 60px;
height: 60px;
background: #00e5ff;
border-color: #00e5ff;
color: #182848;
font-size: 20px;
box-shadow: 0 0 15px #00e5ff;
}
/*单独调整重置按钮*/
.player-btn.reset-btn {
font-size: 12px;
}
/*单独调整快进快退按钮*/
.player-btn.speed-btn {
font-size: 14px;
}
效果如下

播放器进度条HTML结构与CSS样式 🪴
写到这里,我感觉这个播放器差点意思,仔细一看,没有音乐播放的进度条~ 🦊
所以我们这里还需要给播放器加上一个进度条的HTML+CSS结构~
HTML代码如下
<div class="progress-container" id="progressContainer">
<div class="progress-bar" id="progressBar">
<div class="progress-dot"></div>
</div>
</div>
在这里我们创建了3层嵌套的容器!
外层是: class 为 progress-container id为 progressContainer 是进度条整体容器~
中间层: class 为 progress-bar id 为 progressBar 是进度条主体容器
最内层是 class 为progress-dot的进度条滑块/圆点容器
我们的目的是为了创建一个带滑块的进度条结构,通过不同层级容器分别控制进度条整体、来完成进度部分和可拖动的圆点滑块的样式与交互处理!
结构梳理清楚了,那样式自然也就简单了~
首先是进度条外层样式
CSS代码如下
/*进度条外层样式*/
.progress-container {
width: 85%;
height: 3px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
margin: 0 auto 50px;
cursor: pointer;
position: relative;
}
如图

然后是进度条中间层样式
CSS代码如下
/*进度条中间层样式*/
.progress-bar {
height: 100%;
background: #00e5ff;
border-radius: 2px;
width: 0%;
position: relative;
box-shadow: 0 0 8px #00e5ff;
}
这里中间层的代码其实很关键,progress-bar设置分析:
height:100% 让它高度和父容器一致
background:#00e5ff 设置背景为亮青色!
border-radius:2px 给它2像素的圆角
width:0% 初始宽度为 0, 这个属性为什么要这样设置,我们可以观察一下控制台的效果!
如图

看到了吧, 我们后续可通过js调整显示进度, 来显示颜色效果进度~ 怎么样我是不是个大聪明~ 🦞嘿嘿~
position:relative 为内部圆点定位做参考准备!~
box-shadow:0 0 8px #00e5ff 给它加亮青色的发光阴影
最后整体是做出一个初始宽度为0、高度铺满父容器、带圆角和发光效果的亮青色进度条主体来呈现显示音频的播放进度!
最后是进度条最内层样式
CSS代码如下
/*进度条 滑块样式*/
.progress-dot {
position: absolute;
right: -6px;
top: 50%;
transform: translateY(-50%);
width: 15px;
height: 15px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 10px #00e5ff;
}
这里就是设置进度条的圆点滑块progress-dot类的设置样式:
position:absolute 让它基于父容器绝对定位
right:-6px 使它在进度条主体右侧偏移6像素
top:50%和transform:translateY (-50%) 让滑块垂直居中
width:15px和height:15px 设置宽高为15像素
background:#fff 设为白色背景
border-radius:50% 变成圆形
box-shadow:0 0 10px #00e5ff 加亮青色发光阴影
最后整体是做出一个白色圆形、带亮青色发光效果、精准定位在进度条右端且垂直居中的滑块, 用于标识进度条当前位置, 以及后期提供给用户进行拖拽~
到这里,我们的播放器UI界面就算是大功告成了~
整体的CSS代码如下
.player {
width: 500px;
height: 520px;
padding: 40px;
background: linear-gradient(135deg, #4158d0, #c850c0, #ffcc70);
border-radius: 10px;
text-align: center;
color: #fff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
position: relative;
overflow: hidden;
margin: 100px auto;
}
/*光影质感*/
.player::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.1), transparent 70%);
pointer-events: none;
}
/*播放器名*/
.player-title {
font-size: 30px;
margin-top: 50px;
letter-spacing: 3px;
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
/*播放器描述*/
.player-subtitle {
font-size: 14px;
opacity: 0.7;
margin: 10px 0 50px;
letter-spacing: 1px;
color: #eee;
}
/*音乐名称描述*/
.music-info {
margin-bottom: 20px;
font-size: 18px;
color: #fff;
opacity: 0.9;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
/*按钮容器 弹性布局*/
.btn-group {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 20px;
}
/*统一按钮样式+弹性布局*/
.player-btn {
width: 45px;
height: 45px;
border-radius: 50%;
border: 2px solid #fff;
background: transparent;
color: #fff;
font-size: 16px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s;
}
.player-btn:hover {
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
/*单独调整暂停播放键按钮*/
.player-btn.play-pause {
width: 60px;
height: 60px;
background: #00e5ff;
border-color: #00e5ff;
color: #182848;
font-size: 20px;
box-shadow: 0 0 15px #00e5ff;
}
/*单独调整重置按钮*/
.player-btn.reset-btn {
font-size: 12px;
}
/*单独调整快进快退按钮*/
.player-btn.speed-btn {
font-size: 14px;
}
/*进度条外层样式*/
.progress-container {
width: 85%;
height: 3px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
margin: 0 auto 50px;
cursor: pointer;
position: relative;
}
/*进度条中间层样式*/
.progress-bar {
height: 100%;
background: #00e5ff;
border-radius: 2px;
width: 0%;
position: relative;
box-shadow: 0 0 8px #00e5ff;
}
/*进度条 滑块样式*/
.progress-dot {
position: absolute;
right: -6px;
top: 50%;
transform: translateY(-50%);
width: 15px;
height: 15px;
background: #fff;
border-radius: 50%;
box-shadow: 0 0 10px #00e5ff;
}
/*隐藏原始音频元素*/
#audio {
display: none;
}
注意: 原始的音频播放元素,我们要隐藏起来
最终的UI效果如下~
如图

HTML+CSS我们就写到这里了,如果你还不满意,可以自行对它进行扩展美化~
接下来,就正式进入到JS代码交互功能实现的阶段了~
Javascript 播放器交互功能实现 🦜
整理素材 🦚
首先我们可以先把一些基本上的数据整理出来~
这里就从本地选择一些音乐文件作为播放素材~, 保存到本地项目目录下的mp3目录中
如图

音频文件大家可以自行准备一些MP3/WAV格式的都可以~
初始化功能 🐬
用一个数组 + JSON格式来模拟一个播放音频的数据列表
例如
// 歌曲列表
const musicList = [
{ src: 'mp3/1.wav', name: 'Once Upon A Time' },
{ src: 'mp3/2.wav', name: 'Instruments of Retribution' },
{ src: 'mp3/3.wav', name: 'Millennia'},
{ src: 'mp3/4.wav', name: 'We Never' }
];
//获取必要的元素
var audio = document.getElementById("audio"); //获取原始音频元素
var musicName = document.getElementById("musicName"); //获取歌曲名称容器
var playPauseBtn = document.getElementById("playPauseBtn"); //暂停和播放按钮
var stopBtn = document.getElementById('stopBtn'); //停止播放按钮
var progressBar = document.getElementById('progressBar'); //获取中层进度条, 控制显示它的状态
var resetBtn = document.getElementById('resetBtn'); //重听按钮
var progressContainer = document.getElementById('progressContainer'); //进度条
var prevTrack = document.getElementById('prevTrack'); //上一曲按钮
var nextTrack = document.getElementById('nextTrack'); //下一曲按钮
var backBtn = document.getElementById('backBtn'); //快退5秒
var nextBtn = document.getElementById('nextBtn'); //快进5秒
var doubleBackBtn = document.getElementById('doubleBackBtn'); //双倍快退
var doubleNextBtn = document.getElementById('doubleNextBtn'); //双倍快进
//定义一个currentIndex变量,用来记录当前选中/激活/播放的项目初始值为0
var currentIndex = 0;
//定义初始化函数initMusic
function initMusic(index) {
//赋值播放音频路径与音频名称
audio.src = musicList[index].src;
musicName.textContent = musicList[index].name;
//重新加载音频资源
audio.load();
//如果音频没暂停就播放
if (!audio.paused) {
audio.play();
}
}
//初始化一下
initMusic(currentIndex);
注意:初始化函数,跟后面的曲目切换有很大关系,如果你不理解没关系,顺着看我们后面的功能你就能慢慢理解了
播放与暂停功能🌿
我们可以给播放/暂停绑定一个点击事件
实现逻辑:
当点击的时候,判断是否处于暂停状态,如果是就开始播放音乐并且设置按钮中的内容显示为暂停
否则就是播放的状态,对吧,那么就暂停音乐, 并且设置按钮中的内容显示为播放
代码如下
// 播放/暂停
playPauseBtn.addEventListener('click', () => {
if (audio.paused) {
audio.play();
playPauseBtn.textContent = '❚❚';
} else {
audio.pause();
playPauseBtn.textContent = '▶';
}
});
效果如下

当然我这里是有声音的~
另外如果大家有方法和函数不明白,我们可以去借助w3school文档进行查看~
刚才我们使用到的一些方法都可以在这里找到, 没有这个文档的可以后台私信我!
如图

停止播放按钮功能 🌎
这个功能的逻辑代码也很简单, 同样也是利用事件绑定
当点击停止按钮的时候,首先我们可以利用暂停方法,把音乐暂停!
因为API里面没有停止方法,然后我们设置currentTime为0
currentTime是设置或返回音频和视频中的当前播放的位置, 以秒来计算~
如图

那么根据逻辑,我们也需要把暂停按钮所显示的状态修改成准备播放的内容~
并且因为这里是停止,所以如果有进度条,那也肯定是从头开始~
所以进度条,我们要重置到0
代码如下
stopBtn.addEventListener('click', () => {
audio.pause();
audio.currentTime = 0;
playPauseBtn.textContent = '▶';
progressBar.style.width = '0%';
});
重听当前歌曲功能 🐋
其实我们在做这个功能之前,要理解这个功能到底是怎么样的~
就比如这里的重听功能, 你把它的意思梳理清晰,那么代码自然也就出来了
重听的意思很显然就是从头开始听, 那会导致哪些状态~
首先我们的音频播放位置肯定要设置为0, 也就是audio.currentTime属性
然后我们给重听按钮绑定一个点击事件看看效果~
代码如下
// 重听当前歌曲
resetBtn.addEventListener('click', () => {
audio.currentTime = 0;
//这里要考虑到如果用户点击了暂停键,要重听的情况下也要播放
if (audio.paused) {
//开始播放
audio.play();
//设置暂停与播放按钮的内容为❚❚
playPauseBtn.textContent = '❚❚';
}
});
播放进度条功能 🐞
获取进度条信息 🪲
我们在做这个功能的时候,一定要先梳理清楚HTML+CSS结构,你才知道如何去写JS效果
但不管怎么样,作为用户来说,第一时间肯定是点击或者在这个进度条上拖拽,实现快进/快退的效果~
这里我们先做点击事件, 当我们点击进度条,也就相当于点击了最外层的progressContainer元素
然后用一个叫getBoundingClientRect()方法来获取这个元素的信息~
小知识
getBoundingClientRect()方法是DOM方法
用来获取元素在浏览器视口中的位置和尺寸信息 它的返回值是一个DOMRect对象
注意: 视口一般指浏览器的可见区域, 但是不包括地址栏、工具栏这些地方~
我们可以先打印一下看看效果~
代码
//点击进度条某个位置之后计算进度条对应时间点
progressContainer.addEventListener('click', (e) => {
//获取元素在视口坐标系中的位置和尺寸信息 返回对象
var rect = progressContainer.getBoundingClientRect();
console.log(rect);
});
如图

如果你仔细看,你就能看到不管我点击这个progressContainer元素哪里,打印出来的结果都是一样的数据~
如图

仔细展开DOMRect对象, 看一下里面的东西~ 具体如下
| 属性名 | 描述 |
|---|---|
left |
元素左边框外侧到视口左侧的距离 |
right |
元素右边框外侧到视口左侧的距离 |
top |
元素上边框外侧到视口顶部的距离 |
bottom |
元素下边框外侧到视口顶部的距离 |
width |
元素的视觉宽度 |
height |
元素的视觉高度 |
x |
等同于 left |
y |
等同于 top |
注意:
这里面的属性,主要我们要留意以下width和height,它们并不是独立存储的,而是通过边界坐标计算~
width: 你其实可以理解为元素的 (右边框外侧到视口左侧的距离 - 左边框外侧到视口左侧的距离)
同理
height: 你其实可以理解为元素的 (下边框外侧到视口顶部的距离 - 上边框外侧到视口顶部的距离)
那么这意味着width和height属性其实是元素实际占据的宽高,同时还包括内容、内边距、边框
计算用户点击进度 音频条跳转到当前播放位置 🍂
那么当我们点击这个外层容器的时候,获取了它本身的数据,我们才能够往下一步进行计算!
首先要计算出用户点击这个进度条上的位置
这里需要借助事件对象中的clientX属性, 它能获取到鼠标在整个浏览器视口中的水平坐标位置~
那么我们可以用这个鼠标水平坐标位置 - 元素左边框外侧到视口左侧的距离
理解:这里我们主要计算的就是,当用户点击进度条某个位置之后, 所要知道进度条对应时间点
如图

所以,我要知道的是当用户点击进度条时,先算出点击位置相对于进度条左侧的水平距离
再用这个距离去除以进度条总宽度得到点击位置占比,最后用该占比去乘以音频总时长得到当前音频播放位置
最后把计算出来的值赋值到音频播放位置,也就是currentTime属性, 让音频跳转到对应时间点即可!
那逻辑我们梳理清楚了,代码自然也就出来了!
代码如下
//点击进度条某个位置之后计算进度条对应时间点
progressContainer.addEventListener('click', (e) => {
//获取元素在视口坐标系中的位置和尺寸信息 返回对象
var rect = progressContainer.getBoundingClientRect();
//计算当前点击的位置
var clickX = e.clientX - rect.left;
// 用这个clickX距离除以进度条总宽度得到点击位置占比,
// 最后用这个占比乘以音乐的总时长,把计算出来的结果赋值到当前音频播放位置
audio.currentTime = (clickX / rect.width) * audio.duration;//算出当前快进的音乐位置
console.log(audio.currentTime);
//同时播放音乐
audio.play();
});
如图

如果你还不明白,这里我们来举个栗子:
音频总时长就像一整个10寸的蛋糕 , 比如音频总长10秒,蛋糕就10寸,
进度条的总宽度就对应这整个蛋糕的大小
当你点进度条的某个位置,就像在蛋糕上指了想吃的那一口,先算出你指的位置离蛋糕左边沿有几寸, 对应代码里 clickX结果
重点来了,因为我们的进度条是固定的,但是我们的音频的时长不固定,那么要如何计算才能自适应进度条呢?
所以再用这个几寸除以蛋糕总寸数(进度条总宽度),就能算出你要吃的位置占整个蛋糕的比例
比如点在6寸位置,6÷10=0.6,相当于是60%, 最后用这个比例乘以音频总时长 例如: 0.6 × 10秒 =6秒
那么音频就直接跳到6秒的位置播放,就像直接吃到蛋糕 60%的位置~
现在明白了吗?!
实时更新进度条状态 🌚
我们在上面把进度条的位置计算出来之后,还要让滑块动起来呀~
这里我们需要使用到一个叫timeupdate事件 它是属于HTML5 Audio/Video 事件,大家可以去查一下文档
如图

它的作用就是可以实时帮我们追踪目前的播放位置已更改之后,所发生的状态!~
也就是说一旦音频播放的位置发生了变化,那么这个事件就会被触发!
大家可以先想一下,什么情况下,播放位置会发生变化?
是不是当我们使用play()方法开始播放的时候,它的音频位置就是一直变化的~
我们可以先测试一下看看~
audio.addEventListener('timeupdate', () => {
console.log(audio.currentTime);
});
如图

所以当我们开始播放音乐的时候,这个事件其实就已经默认一直在触发着!
那我们就可以利用这一点来让进度条的滑块动起来了!
计算方式如下:
(当前播放位置秒 / 当前音频的总秒) * 100;
逻辑分析:
音频播放时会不停触发这段代码,先确认音频有总时长~不然就不执行!
音频已播放时间 除以 音频总时长,再乘以100, 这样来算出百分之多少,
最后把进度条的宽度设为这个百分比!
当然我们这个事件函数在执行的时候,是不断执行的,所以这个百分比也会不断变化,
这样可以让我们的progressBar元素动起来,从而达到一种视觉效果~
具体代码
//使用timeupdate时间,实时更新进度条位置状态
//currentTime 设置或返回音频中的当前播放位置(以秒计)
//duration 返回当前音频的长度(以秒计)
audio.addEventListener('timeupdate', () => {
if (!audio.duration) return;
var progress = (audio.currentTime / audio.duration) * 100;
//最后把这个百分比值赋值给中间层
//然后最内层的滑块样式圆形因为绝对定位与父元素,
//会被中间层的动态宽度带过来!
progressBar.style.width = progress+'%';
});
当然这里可能有人会问,为什么不用像素,要用百分比?
用百分比是因为进度条宽度是相对的, 这样可以适配不同尺寸容器,按播放占比设百分比能精准对应音频播放进度,用像素那就要先算进度条总像素再乘占比,有点麻烦,并且适配性差!
那么做到这一步,我们的播放器也有一个大概的雏形了~
如图

我们接着把剩下的音乐播放器功能也都加上~
上下曲目切换 🦀
切换上一曲 🏄🏼♂️
这个逻辑也很简单,也就是当你点击上一曲按钮的时候,切换到前一首歌~
功能上看上去简单,但是有很多人会在这里犯错~我简单的梳理一下逻辑
功能逻辑
当我们在第一首歌的时候,如果点击上一曲,应该切换到最后一首歌去!
在这里我们之前写的一个initMusic函数就派上用场了~
这个函数我是用来记录当前选中的音乐项目, 并且播放, 默认初始值为0
同时,我们也定义了一个currentIndex来保存索引值~
那么对这个索引值进行减法运算自然可以得到上一曲目的索引
代码如下
//切换上一曲
prevTrack.addEventListener('click',() =>{
currentIndex = currentIndex - 1;
if(currentIndex < 0){
currentIndex = musicList.length - 1;
}
//传递播放曲目索引
initMusic(currentIndex);
//设置一下暂停按钮的显示状态
playPauseBtn.textContent = '❚❚';
//开始播放
audio.play();
});
代码分析
利用当前曲目索引-1, 同时判断是否到顶, 也就是说当前曲目已经是第一首歌了,如果还要点击上一曲目,应该切换到最后一首歌去!
然后把计算好的索引值传递给initMusic函数
注意
这里要特别留意几个问题!~我们在看看之前的这段initMusic函数代码
function initMusic(index) {
audio.src = musicList[index].src;
musicName.textContent = musicList[index].name;
audio.load();
if (!audio.paused) {
audio.play();
}
}
首先我们传递过去的曲目索引值,帮助我们切换到对应的曲目,获取歌曲的一些信息~这个没什么问题
然后audio.load() 会主动触发音频元素的重新加载流程,让浏览器放弃之前的音频缓存,并且重新请求并解析新设置的src对应的音频文件,确保音频能正确关联到最新的歌曲资源~
如果你在这里不调用audio.load()时,浏览器可能不会立即响应src的变更,甚至会保留上一个音频的播放状态、时长等信息,导致新歌曲无法正常播放~比如切换歌曲后还是播放原来的音乐
所以加上audio.load()会重置音频元素的所有状态,包括当前播放时间、缓冲数据等,为新音频的播放做好准备,保证歌曲切换的流畅性和准确性~ 很多人会忽视这一点,导致不少的BUG问题~特别注意一下
同时判断if (!audio.paused) 如果没有暂停播放的情况下,就直接播放audio.play()
这里的意思其实也很简单,作用就是在重新加载新音频后,无缝延续之前的播放状态~ 也就是说原本处于播放中的音频,在我们切换曲目后自动播放新歌曲,不用我们用户再次点击播放~
可能有些人要问我那为什么要在切换上一曲的最后也加上一个audio.play()呢
你其实可以试试看,不加会怎么样,
很显然,你不加,默认情况下,歌曲切换是没问题的,但是就是不会播放
因为initMusic的最后逻辑里audio.play()有前置条件!audio.paused,只有音频原本处于非暂停状态也就是正在播放时才会执行播放~
如果我们不在切换上一曲目的最后加上这个audio.play()那么就没有代码能触发首次播放~懂这个意思吧
这时候,有大聪明要出来说用鼠标把进度条拖拽到中间,播放了, 这总证明音频处于非暂停状态了吧, 按上一曲目 ,同时最后不加audio.play() 也不行啊~
这是因为initMusic函数切换曲目时重新设置了audio.src并执行了audio.load()对吧~
但是这会重置音频状态为暂停paused: true
你可以用属性测试一下就知道了
function initMusic(index) {
audio.src = musicList[index].src;
musicName.textContent = musicList[index].name;
audio.load();
//检测音频/视频是否已暂停
console.log(audio.paused);
if (!audio.paused) {
audio.play();
}
}
如图

看到了吧,之前拖拽播放的非暂停状态会被清除,导致!audio.paused条件不满足,内部audio.play()不执行
知识点
function initMusic(index) {
audio.src = musicList[index].src;
musicName.textContent = musicList[index].name;
audio.load();
//检测音频/视频是否已暂停
console.log(audio.paused);
if (!audio.paused) {
audio.play();
}
}
//切换上一曲
prevTrack.addEventListener('click',() =>{
currentIndex = currentIndex - 1;
if(currentIndex < 0){
currentIndex = musicList.length - 1;
}
initMusic(currentIndex);
playPauseBtn.textContent = '❚❚';
//开始播放
audio.play();
});
另外我们在prevTrack中设置了audio.play()但是在initMusic函数中打印audio.paused还是会返回true
这是因为外部的audio.play()会异步触发音频播放,而initMusic里的console.log(audio.paused)是同步立即执行的,此时音频还未完成播放状态切换,仍显示true,但外部异步的audio.play()最终会成功启动播放。
很多人会在这里出问题就是因为这个原因~所以别当大聪明了,一定要仔细看梳理清楚逻辑才是重点!
如果你之前没有搞懂那个initMusic函数,那么现在你应该清楚了吧~
最后优化一下代码简写
prevTrack.addEventListener('click', () => {
currentIndex = (currentIndex - 1 + musicList.length) % musicList.length;
initMusic(currentIndex);
playPauseBtn.textContent = '❚❚';
audio.play();
});
切换下一曲 🏄🏼
上一曲目切换如果没问题,那下一曲目切换也是同理~
逻辑也很简单,也就是操作当前曲目索引值currentIndex + 1
代码如下
//切换下一曲
nextTrack.addEventListener('click',()=>{
//当前曲目索引+1
currentIndex = currentIndex + 1;
//当超过最后一个曲目的时候,切换到第一首
if(currentIndex>musicList.length-1){
currentIndex = 0;
}
//传递播放曲目索引
initMusic(currentIndex);
//设置一下暂停按钮的显示状态
playPauseBtn.textContent = '❚❚';
//开始播放
audio.play();
});
快退/快进5秒 🦗
这个功能其实本质上是修改audio的currentTime属性
快退5秒 🌙
本质上是在currentTime的基础上减去5秒 , 同时可以结合使用Math.max函数来防止音频的当前播放时间currentTime变成负数,Math.max(0, ...) 能保证即使计算后结果小于 0,最终也只会取0
代码实现
backBtn.addEventListener('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 5);
});
快进5秒 ⭐️
本质上是在currentTime的基础上加上5秒, 同时可以结合使用Math.min函数来防止音频的当前播放时间currentTime超过音频总时长duration
那么Math.min函数 能保证即使计算后结果大于总时长,最终也只会取音频总时长!
代码实现
nextBtn.addEventListener('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 5);
});
双倍快退快进 🐦🔥
这个功能和快进快退的道理是一样的,只是设定的秒数不一样,倍数更大~
普通快进快退为5秒,那么这里双倍自然就是10秒,这根据大家自己的需求自定义即可~
这里我们就直接简单的实现一下
代码实现
// 双倍快退(10秒)
doubleBackBtn.addEventListener('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 10);
});
// 双倍快进(10秒)
doubleNextBtn.addEventListener('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});
基本功能的JS代码也就写得差不多了, 整体代码如下
// 歌曲列表
var musicList = [
{ src: 'mp3/1.wav', name: 'Once Upon A Time' },
{ src: 'mp3/2.wav', name: 'Instruments of Retribution' },
{ src: 'mp3/3.wav', name: 'Millennia' },
{ src: 'mp3/4.wav', name: 'We Never' }
];
//获取必要的元素
var audio = document.getElementById("audio"); //获取原始音频元素
var musicName = document.getElementById("musicName"); //获取歌曲名称容器
var playPauseBtn = document.getElementById("playPauseBtn"); //暂停和播放按钮
var stopBtn = document.getElementById('stopBtn'); //停止播放按钮
var progressBar = document.getElementById('progressBar'); //获取中层进度条, 控制显示它的状态
var resetBtn = document.getElementById('resetBtn'); //重听按钮
var progressContainer = document.getElementById('progressContainer'); //进度条
var prevTrack = document.getElementById('prevTrack'); //上一曲按钮
var nextTrack = document.getElementById('nextTrack'); //下一曲按钮
var backBtn = document.getElementById('backBtn'); //快退5秒
var nextBtn = document.getElementById('nextBtn'); //快进5秒
var doubleBackBtn = document.getElementById('doubleBackBtn'); //双倍快退
var doubleNextBtn = document.getElementById('doubleNextBtn'); //双倍快进
//定义一个currentIndex变量,用来记录当前选中/激活/播放的项目初始值为0
var currentIndex = 0;
//定义initMusic函数
function initMusic(index) {
audio.src = musicList[index].src;
musicName.textContent = musicList[index].name;
audio.load();
if (!audio.paused) {
audio.play();
}
}
//初始化一下
initMusic(currentIndex);
// 播放/暂停
playPauseBtn.addEventListener('click', () => {
if (audio.paused) {
audio.play();
playPauseBtn.textContent = '❚❚';
} else {
audio.pause();
playPauseBtn.textContent = '▶';
}
});
//停止播放按钮
stopBtn.addEventListener('click', () => {
audio.pause();
audio.currentTime = 0;
playPauseBtn.textContent = '▶';
progressBar.style.width = '0%';
});
// 重听当前歌曲
resetBtn.addEventListener('click', () => {
audio.currentTime = 0;
//这里要考虑到如果用户点击了暂停键,要重听的情况下也要播放
if (audio.paused) {
//开始播放
audio.play();
//设置暂停与播放按钮的内容为❚❚
playPauseBtn.textContent = '❚❚';
}
});
progressContainer.addEventListener('click', (e) => {
var rect = progressContainer.getBoundingClientRect();
//计算当前点击的位置
var clickX = e.clientX - rect.left;
audio.currentTime = (clickX / rect.width) * audio.duration;
//同时播放音乐
audio.play();
});
audio.addEventListener('timeupdate', () => {
if (!audio.duration) return;
var progress = (audio.currentTime / audio.duration) * 100;
progressBar.style.width = progress + '%';
});
//上一曲
prevTrack.addEventListener('click', () => {
currentIndex = currentIndex - 1;
if (currentIndex < 0) {
currentIndex = musicList.length - 1;
}
initMusic(currentIndex);
playPauseBtn.textContent = '❚❚';
audio.play();
});
//下一曲
nextTrack.addEventListener('click', () => {
//当前曲目索引+1
currentIndex = currentIndex + 1;
//当超过最后一个曲目的时候,切换到第一首
if (currentIndex > musicList.length - 1) {
currentIndex = 0;
}
//传递播放曲目索引
initMusic(currentIndex);
//设置一下暂停按钮的显示状态
playPauseBtn.textContent = '❚❚';
//开始播放
audio.play();
});
//快退5秒
backBtn.addEventListener('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 5);
});
//快进5秒
nextBtn.addEventListener('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 5);
});
// 双倍快退(10秒)
doubleBackBtn.addEventListener('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 10);
});
// 双倍快进(10秒)
doubleNextBtn.addEventListener('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});
// 解决自动播放拦截
document.addEventListener('click', () => audio.play().catch(() => { }),{ once: true });
最后 🥳
到这里这个简易的web音乐播放器的大致功能我们就做到这里了,其实这个播放器我们还可以继续挖掘它的功能,更具需求不断改进,玩出更多花样~这就要看大家的时间和耐心了
以后有时间,我还会更新一些有趣的小功能~大家一定要多多关注哦!
- 点赞
- 收藏
- 关注作者
评论(0)