以下是一个完整、可直接运行的现代风格音乐播放器项目,使用纯 HTML + CSS + JavaScript(无任何框架),适合作为综合大项目练习或简历作品。
特点:
- 响应式布局(手机 → 桌面都友好)
- 深色主题 + 圆润现代 UI
- 播放/暂停、上一首/下一首、进度条拖拽、音量控制
- 歌曲列表 + 点击切换
- 专辑封面旋转动画
- 时间显示 + 自动下一首
完整代码(index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>重阳音乐 · Music Player</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
<style>
:root {
--bg: #0f0f0f;
--card: #1a1a1a;
--text: #e0e0e0;
--accent: #e91e63;
--accent-dark: #c2185b;
--progress: #444;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.player {
background: var(--card);
border-radius: 24px;
width: 100%;
max-width: 420px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
backdrop-filter: blur(10px);
}
.cover {
position: relative;
height: 280px;
overflow: hidden;
}
.cover img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.8s ease;
}
.cover.playing img {
transform: scale(1.12) rotate(8deg);
}
.info {
padding: 24px 28px 16px;
text-align: center;
}
.title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 8px;
}
.artist {
font-size: 0.95rem;
color: #aaa;
margin-bottom: 20px;
}
.progress-container {
height: 6px;
background: var(--progress);
border-radius: 3px;
margin: 0 28px 12px;
cursor: pointer;
position: relative;
}
.progress {
height: 100%;
width: 0%;
background: var(--accent);
border-radius: 3px;
transition: width 0.2s linear;
}
.time {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: #bbb;
padding: 0 28px;
margin-bottom: 20px;
}
.controls {
display: flex;
justify-content: center;
align-items: center;
gap: 28px;
margin-bottom: 24px;
}
.btn {
font-size: 1.6rem;
color: var(--text);
cursor: pointer;
transition: all 0.3s;
}
.btn:hover { color: var(--accent); transform: scale(1.15); }
.play-btn {
font-size: 3.2rem;
color: var(--accent);
}
.volume-container {
display: flex;
align-items: center;
gap: 12px;
padding: 0 28px 28px;
}
.volume-bar {
flex: 1;
height: 6px;
background: var(--progress);
border-radius: 3px;
cursor: pointer;
position: relative;
}
.volume-progress {
height: 100%;
width: 70%;
background: var(--accent);
border-radius: 3px;
}
.playlist {
max-height: 240px;
overflow-y: auto;
padding: 0 16px 16px;
border-top: 1px solid #333;
}
.song-item {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 16px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
}
.song-item:hover,
.song-item.active {
background: rgba(233,30,99,0.12);
}
.song-item img {
width: 50px;
height: 50px;
border-radius: 8px;
object-fit: cover;
}
.song-info {
flex: 1;
}
.song-title {
font-weight: 500;
font-size: 0.95rem;
}
.song-artist {
font-size: 0.8rem;
color: #aaa;
}
@media (max-width: 480px) {
.player { border-radius: 0; }
.cover { height: 240px; }
}
</style>
</head>
<body>
<div class="player">
<div class="cover">
<img id="cover" src="https://images.unsplash.com/photo-1611339555312-e607c8352fd7?w=800" alt="cover">
</div>
<div class="info">
<div class="title" id="title">暂无播放</div>
<div class="artist" id="artist">—</div>
<div class="progress-container" id="progressContainer">
<div class="progress" id="progress"></div>
</div>
<div class="time">
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
<div class="controls">
<i class="fas fa-backward btn" id="prev"></i>
<i class="fas fa-play-circle play-btn" id="play"></i>
<i class="fas fa-forward btn" id="next"></i>
</div>
</div>
<div class="volume-container">
<i class="fas fa-volume-down"></i>
<div class="volume-bar" id="volumeBar">
<div class="volume-progress" id="volumeProgress"></div>
</div>
</div>
<div class="playlist" id="playlist">
<!-- 歌曲列表由 JS 动态生成 -->
</div>
</div>
<audio id="audio" preload="metadata"></audio>
<script>
// === 数据 ===
const songs = [
{
title: "Neon Dreams",
artist: "Cyber Wave",
src: "https://cdn.pixabay.com/audio/2023/08/07/audio_3b8d7d6e7a.mp3",
cover: "https://images.unsplash.com/photo-1557672172-298e090bd0f1?w=800"
},
{
title: "Midnight City",
artist: "Electric Youth",
src: "https://cdn.pixabay.com/audio/2022/03/15/audio_d4c8c7d9f2.mp3",
cover: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800"
},
{
title: "Floating",
artist: "Chillhop Music",
src: "https://cdn.pixabay.com/audio/2024/01/12/audio_8f9e2c3b4d.mp3",
cover: "https://images.unsplash.com/photo-1511671782779-c97d3d27c1d4?w=800"
}
];
// === DOM 元素 ===
const audio = document.getElementById('audio');
const cover = document.getElementById('cover');
const titleEl = document.getElementById('title');
const artistEl = document.getElementById('artist');
const playBtn = document.getElementById('play');
const prevBtn = document.getElementById('prev');
const nextBtn = document.getElementById('next');
const progressContainer = document.getElementById('progressContainer');
const progress = document.getElementById('progress');
const currentTimeEl = document.getElementById('currentTime');
const durationEl = document.getElementById('duration');
const volumeBar = document.getElementById('volumeBar');
const volumeProgress = document.getElementById('volumeProgress');
const playlistEl = document.getElementById('playlist');
let currentSong = 0;
let isPlaying = false;
// === 函数 ===
function loadSong(index) {
const song = songs[index];
titleEl.textContent = song.title;
artistEl.textContent = song.artist;
audio.src = song.src;
cover.src = song.cover;
highlightPlaylistItem(index);
}
function playSong() {
playBtn.classList.replace('fa-play-circle', 'fa-pause-circle');
document.querySelector('.cover').classList.add('playing');
audio.play().catch(e => console.log("播放失败", e));
isPlaying = true;
}
function pauseSong() {
playBtn.classList.replace('fa-pause-circle', 'fa-play-circle');
document.querySelector('.cover').classList.remove('playing');
audio.pause();
isPlaying = false;
}
function togglePlay() {
isPlaying ? pauseSong() : playSong();
}
function prevSong() {
currentSong = (currentSong - 1 + songs.length) % songs.length;
loadSong(currentSong);
playSong();
}
function nextSong() {
currentSong = (currentSong + 1) % songs.length;
loadSong(currentSong);
playSong();
}
function updateProgress(e) {
const {duration, currentTime} = e.srcElement;
if (isNaN(duration)) return;
const percent = (currentTime / duration) * 100;
progress.style.width = percent + '%';
const curMin = Math.floor(currentTime / 60);
const curSec = Math.floor(currentTime % 60).toString().padStart(2, '0');
currentTimeEl.textContent = `${curMin}:${curSec}`;
const durMin = Math.floor(duration / 60);
const durSec = Math.floor(duration % 60).toString().padStart(2, '0');
durationEl.textContent = `${durMin}:${durSec}`;
}
function setProgress(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration;
}
function setVolume(e) {
const width = this.clientWidth;
const clickX = e.offsetX;
const vol = clickX / width;
audio.volume = vol;
volumeProgress.style.width = (vol * 100) + '%';
}
function highlightPlaylistItem(index) {
document.querySelectorAll('.song-item').forEach((item, i) => {
item.classList.toggle('active', i === index);
});
}
function renderPlaylist() {
playlistEl.innerHTML = '';
songs.forEach((song, index) => {
const div = document.createElement('div');
div.classList.add('song-item');
div.innerHTML = `
<img src="${song.cover}" alt="cover">
<div class="song-info">
<div class="song-title">${song.title}</div>
<div class="song-artist">${song.artist}</div>
</div>
`;
div.addEventListener('click', () => {
currentSong = index;
loadSong(index);
playSong();
});
playlistEl.appendChild(div);
});
}
// === 事件监听 ===
playBtn.addEventListener('click', togglePlay);
prevBtn.addEventListener('click', prevSong);
nextBtn.addEventListener('click', nextSong);
audio.addEventListener('timeupdate', updateProgress);
audio.addEventListener('ended', nextSong);
progressContainer.addEventListener('click', setProgress);
volumeBar.addEventListener('click', setVolume);
// 初始化
loadSong(currentSong);
renderPlaylist();
volumeProgress.style.width = (audio.volume * 100) + '%';
// 键盘控制(可选)
document.addEventListener('keydown', e => {
if (e.code === 'Space') {
e.preventDefault();
togglePlay();
}
});
</script>
</body>
</html>
如何使用 & 扩展建议
- 直接使用:把上面全部代码保存为
index.html,用浏览器打开即可。 - 替换歌曲:把
songs数组里的src换成你自己的 mp3 链接(推荐免费版权音乐:Pixabay、Free Music Archive)。 - 本地音乐:可以改成
<input type="file" multiple accept="audio/*">+URL.createObjectURL()实现上传播放。 - 进阶方向:
- 拖拽排序歌单(Sortable.js)
- 均衡器可视化(Web Audio API)
- 播放模式(单曲循环、随机、列表循环)
- 主题切换(明暗模式)
- 后台播放(Service Worker + Media Session API)
这是一个非常适合前端综合练习的项目,涵盖了:
- DOM 操作、事件委托
- 音频 API(HTML5
<audio>) - CSS 动画 & 响应式
- 模块化思维(函数拆分)
- 交互细节(拖拽进度、音量)
如果想加拖拽上传、播放列表排序、歌词同步、Web Audio 可视化等高级功能,告诉我,我可以继续扩展代码!