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

bootstrap 网页播放器
(图片来源网络,侵删)

最终效果预览

这里有一个可以运行的在线示例,你可以先体验一下效果:

点击此处查看在线 Demo


第一步: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)

这是播放器的核心,我们将在这里处理所有的播放器逻辑。

bootstrap 网页播放器
(图片来源网络,侵删)
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();
});

代码解析

  1. HTML 结构:

    • 使用 Bootstrap 的 container 和自定义的 player-container 来居中并美化播放器。
    • 使用 player-header, player-body 等类来组织布局。
    • 所有控制按钮都使用了 button 标签,并添加了 Font Awesome 图标。
    • 进度条和音量条使用了 Bootstrap 5 的 progressform-range 组件。
    • 播放列表区域留空,由 JavaScript 动态填充。
  2. CSS 样式:

    • 自定义的 CSS 主要用于美化,让播放器看起来更现代、更专业。
    • flexbox 用于灵活地排列控制按钮和音量控件。
    • 添加了 hoveractive 状态的样式,提升交互体验。
  3. JavaScript 逻辑 (player.js):

    • DOM 获取: 首先获取所有需要操作的 HTML 元素。
    • 播放列表数据: 使用一个 JavaScript 对象数组来存储歌曲信息,包括源文件、标题、艺术家和封面图,这使得扩展和管理播放列表变得非常容易。
    • 初始化函数 (initPlaylist): 遍历 playlist 数组,为每首歌创建一个列表项,并添加点击事件,点击时会调用 loadSong 函数。
    • 加载歌曲 (loadSong): 这是核心功能之一,它根据传入的索引更新 audio 元素的 src,并更新页面上的歌曲信息和封面,它会高亮当前正在播放的歌曲。
    • 播放/暂停 (togglePlayPause): 检查 audio 元素的 paused 属性来决定是播放还是暂停,并相应地更改按钮图标。
    • 进度条更新 (updateProgress): 监听 audiotimeupdate 事件,该事件在播放时持续触发,我们根据 currentTimeduration 计算进度百分比,并更新 progressBar 的宽度。
    • 设置进度 (setProgress): 允许用户通过点击进度条的任意位置来跳转到对应的时间点,通过计算点击位置占整个进度条宽度的比例来设置 audio.currentTime
    • 音量控制 (changeVolume, toggleMute): volumeSliderinput 事件会实时改变 audio.volume,点击音量图标可以切换静音状态,updateVolumeIcon 函数会根据当前音量大小显示不同的图标。
    • 上一首/下一首 (prevSong, nextSong): 通过改变 currentSongIndex 并调用 loadSong 来实现,还处理了循环播放的逻辑(即当是第一首时,上一首是最后一首;当是最后一首时,下一首是第一首)。
    • 事件监听器: 将所有函数与对应的 DOM 事件(如 click, timeupdate 等)连接起来,实现交互。
    • 初始化: 在页面加载完成后,调用 loadSonginitPlaylist 来加载第一首歌并渲染播放列表。

这个播放器已经具备了非常完整的功能,你可以基于这个代码进行进一步的扩展,比如添加随机播放、循环模式、歌词显示等。