我们将使用 Bootstrap 5 作为 UI 框架,Font Awesome 提供图标,并用原生 JavaScript 来实现所有交互逻辑。

(图片来源网络,侵删)
最终效果预览
这里有一个可以运行的在线示例,你可以先体验一下效果:
第一步:HTML 结构
我们将播放器的所有元素都包裹在一个 div 容器中,并使用 Bootstrap 的网格系统来布局。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">Bootstrap 网页播放器</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* 自定义样式,让播放器更美观 */
body {
background-color: #f8f9fa;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.player-container {
width: 100%;
max-width: 600px;
background-color: #fff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
border-radius: 15px;
overflow: hidden;
}
.player-header {
background-color: #343a40;
color: white;
padding: 15px 20px;
}
.player-body {
padding: 20px;
}
.album-art {
width: 100%;
border-radius: 10px;
margin-bottom: 20px;
}
.progress-container {
cursor: pointer;
}
.progress {
height: 6px;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
.control-btn {
background: none;
border: none;
color: #6c757d;
font-size: 1.2rem;
cursor: pointer;
transition: color 0.2s;
}
.control-btn:hover {
color: #000;
}
.play-pause-btn {
font-size: 2rem;
color: #000;
}
.volume-control {
display: flex;
align-items: center;
gap: 10px;
}
.playlist {
max-height: 200px;
overflow-y: auto;
}
.playlist-item {
padding: 10px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.playlist-item:hover {
background-color: #e9ecef;
}
.playlist-item.active {
background-color: #dee2e6;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="player-container">
<!-- 播放器头部 -->
<div class="player-header">
<h4 class="mb-0">音乐播放器</h4>
</div>
<!-- 播放器主体 -->
<div class="player-body">
<!-- 专辑封面 -->
<img src="https://via.placeholder.com/400x400" alt="Album Art" class="album-art" id="albumArt">
<!-- 歌曲信息 -->
<h5 id="songTitle">示例歌曲标题</h5>
<p id="artistName">示例艺术家</p>
<!-- 进度条 -->
<div class="progress-container" id="progressContainer">
<div class="progress">
<div class="progress-bar" role="progressbar" id="progressBar" style="width: 0%"></div>
</div>
</div>
<div class="d-flex justify-content-between small text-muted">
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
<!-- 控制按钮 -->
<div class="controls">
<button class="control-btn" id="prevBtn"><i class="fas fa-step-backward"></i></button>
<button class="control-btn play-pause-btn" id="playPauseBtn"><i class="fas fa-play"></i></button>
<button class="control-btn" id="nextBtn"><i class="fas fa-step-forward"></i></button>
</div>
<!-- 音量控制 -->
<div class="volume-control mt-3">
<button class="control-btn" id="volumeBtn"><i class="fas fa-volume-up"></i></button>
<input type="range" class="form-range" id="volumeSlider" min="0" max="100" value="70">
</div>
<!-- 播放列表 -->
<div class="playlist mt-4">
<h6>播放列表</h6>
<div id="playlistItems">
<!-- 播放列表项将通过 JS 动态生成 -->
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS (optional, for some components if needed) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- 我们的核心 JavaScript -->
<script src="player.js"></script>
</body>
</html>
第二步:JavaScript 逻辑 (player.js)
这是播放器的核心,我们将在这里处理所有的播放器逻辑。

(图片来源网络,侵删)
document.addEventListener('DOMContentLoaded', () => {
// --- 1. 获取 DOM 元素 ---
const audio = new Audio();
const playPauseBtn = document.getElementById('playPauseBtn');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const progressBar = document.getElementById('progressBar');
const progressContainer = document.getElementById('progressContainer');
const currentTimeEl = document.getElementById('currentTime');
const durationEl = document.getElementById('duration');
const volumeSlider = document.getElementById('volumeSlider');
const volumeBtn = document.getElementById('volumeBtn');
const songTitle = document.getElementById('songTitle');
const artistName = document.getElementById('artistName');
const albumArt = document.getElementById('albumArt');
const playlistItems = document.getElementById('playlistItems');
// --- 2. 播放列表数据 ---
const playlist = [
{ src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', title: '夏日微风', artist: '自然之声', art: 'https://via.placeholder.com/400x400/007bff/ffffff?text=Song+1' },
{ src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', title: '城市节拍', artist: '都市乐队', art: 'https://via.placeholder.com/400x400/28a745/ffffff?text=Song+2' },
{ src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3', title: '星空漫步', artist: '梦境旅人', art: 'https://via.placeholder.com/400x400/dc3545/ffffff?text=Song+3' },
];
let currentSongIndex = 0;
// --- 3. 初始化播放列表 ---
function initPlaylist() {
playlistItems.innerHTML = '';
playlist.forEach((song, index) => {
const item = document.createElement('div');
item.className = 'playlist-item';
if (index === currentSongIndex) {
item.classList.add('active');
}
item.innerHTML = `<strong>${song.title}</strong> - ${song.artist}`;
item.addEventListener('click', () => loadSong(index));
playlistItems.appendChild(item);
});
}
// --- 4. 加载歌曲 ---
function loadSong(index) {
currentSongIndex = index;
const song = playlist[currentSongIndex];
audio.src = song.src;
songTitle.textContent = song.title;
artistName.textContent = song.artist;
albumArt.src = song.art;
// 更新播放列表高亮
document.querySelectorAll('.playlist-item').forEach((item, i) => {
if (i === index) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
if (!audio.paused) {
audio.play();
playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
}
}
// --- 5. 播放/暂停功能 ---
function togglePlayPause() {
if (audio.paused) {
audio.play();
playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
} else {
audio.pause();
playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
}
}
// --- 6. 更新进度条 ---
function updateProgress() {
const { currentTime, duration } = audio;
const progressPercent = (currentTime / duration) * 100;
progressBar.style.width = `${progressPercent}%`;
currentTimeEl.textContent = formatTime(currentTime);
durationEl.textContent = formatTime(duration);
}
// --- 7. 设置进度条 ---
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration;
}
// --- 8. 音量控制 ---
function changeVolume() {
audio.volume = volumeSlider.value / 100;
updateVolumeIcon();
}
function toggleMute() {
if (audio.muted) {
audio.muted = false;
volumeSlider.value = audio.volume * 100;
} else {
audio.muted = true;
}
updateVolumeIcon();
}
function updateVolumeIcon() {
const volume = audio.volume;
if (audio.muted || volume === 0) {
volumeBtn.innerHTML = '<i class="fas fa-volume-mute"></i>';
} else if (volume < 0.5) {
volumeBtn.innerHTML = '<i class="fas fa-volume-down"></i>';
} else {
volumeBtn.innerHTML = '<i class="fas fa-volume-up"></i>';
}
}
// --- 9. 上一首/下一首 ---
function prevSong() {
currentSongIndex--;
if (currentSongIndex < 0) {
currentSongIndex = playlist.length - 1;
}
loadSong(currentSongIndex);
}
function nextSong() {
currentSongIndex++;
if (currentSongIndex >= playlist.length) {
currentSongIndex = 0;
}
loadSong(currentSongIndex);
}
// --- 10. 格式化时间 ---
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
}
// --- 11. 事件监听器 ---
playPauseBtn.addEventListener('click', togglePlayPause);
prevBtn.addEventListener('click', prevSong);
nextBtn.addEventListener('click', nextSong);
audio.addEventListener('timeupdate', updateProgress);
audio.addEventListener('ended', nextSong); // 歌曲结束时自动播放下一首
progressContainer.addEventListener('click', setProgress);
volumeSlider.addEventListener('input', changeVolume);
volumeBtn.addEventListener('click', toggleMute);
// --- 12. 初始化 ---
loadSong(currentSongIndex);
initPlaylist();
audio.volume = 0.7; // 设置初始音量
updateVolumeIcon();
});
代码解析
-
HTML 结构:
- 使用 Bootstrap 的
container和自定义的player-container来居中并美化播放器。 - 使用
player-header,player-body等类来组织布局。 - 所有控制按钮都使用了
button标签,并添加了 Font Awesome 图标。 - 进度条和音量条使用了 Bootstrap 5 的
progress和form-range组件。 - 播放列表区域留空,由 JavaScript 动态填充。
- 使用 Bootstrap 的
-
CSS 样式:
- 自定义的 CSS 主要用于美化,让播放器看起来更现代、更专业。
flexbox用于灵活地排列控制按钮和音量控件。- 添加了
hover和active状态的样式,提升交互体验。
-
JavaScript 逻辑 (
player.js):- DOM 获取: 首先获取所有需要操作的 HTML 元素。
- 播放列表数据: 使用一个 JavaScript 对象数组来存储歌曲信息,包括源文件、标题、艺术家和封面图,这使得扩展和管理播放列表变得非常容易。
- 初始化函数 (
initPlaylist): 遍历playlist数组,为每首歌创建一个列表项,并添加点击事件,点击时会调用loadSong函数。 - 加载歌曲 (
loadSong): 这是核心功能之一,它根据传入的索引更新audio元素的src,并更新页面上的歌曲信息和封面,它会高亮当前正在播放的歌曲。 - 播放/暂停 (
togglePlayPause): 检查audio元素的paused属性来决定是播放还是暂停,并相应地更改按钮图标。 - 进度条更新 (
updateProgress): 监听audio的timeupdate事件,该事件在播放时持续触发,我们根据currentTime和duration计算进度百分比,并更新progressBar的宽度。 - 设置进度 (
setProgress): 允许用户通过点击进度条的任意位置来跳转到对应的时间点,通过计算点击位置占整个进度条宽度的比例来设置audio.currentTime。 - 音量控制 (
changeVolume,toggleMute):volumeSlider的input事件会实时改变audio.volume,点击音量图标可以切换静音状态,updateVolumeIcon函数会根据当前音量大小显示不同的图标。 - 上一首/下一首 (
prevSong,nextSong): 通过改变currentSongIndex并调用loadSong来实现,还处理了循环播放的逻辑(即当是第一首时,上一首是最后一首;当是最后一首时,下一首是第一首)。 - 事件监听器: 将所有函数与对应的 DOM 事件(如
click,timeupdate等)连接起来,实现交互。 - 初始化: 在页面加载完成后,调用
loadSong和initPlaylist来加载第一首歌并渲染播放列表。
这个播放器已经具备了非常完整的功能,你可以基于这个代码进行进一步的扩展,比如添加随机播放、循环模式、歌词显示等。
