![]()
💬 开场白
“`html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Apple风格音乐播放器</title>
<style>
/* 通用样式重置和字体设置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: white; /* 设置默认文本颜色为白色 */
}/* 页面主体样式 – 浅灰色背景 */
body {
padding: 40px;
display: flex; /* 使用flexbox居中播放器 */
justify-content: center;
align-items: center;
min-height: 100vh; /* 确保body至少占满视口高度 */
}/* 新增:包裹播放器和歌曲列表的容器 */
/* 这个容器现在是垂直方向的Flexbox */
.player-and-list-wrapper {
display: flex;
flex-direction: column; /* 垂直堆叠播放器和列表 */
align-items: center; /* 水平居中 */
gap: 25px; /* 播放器和列表之间的垂直间距 */
position: relative; /* 列表按钮需要相对定位到播放器内部,而非这里 */
max-width: 320px; /* 整体最大宽度由播放器决定 */
/* overflow: hidden; /* 仅在必要时使用,以免裁剪过度 */
}/* 播放器容器样式 – 固定宽度,并移除阴影 */
.player {
width: 320px; /* 固定宽度,点击列表前后尺寸不变 */
flex-shrink: 0; /* 防止播放器在flex容器中缩小 */
background: rgba(0, 0, 0, 0.15); /* 半透明黑色背景 */
backdrop-filter: blur(20px); /* 背景模糊效果 */
border-radius: 24px;
padding: 25px; /* 稍微缩小内边距 */
border: 1px solid rgba(255, 255, 255, 0.05); /* 替换为内部边框效果 */
color: white;
position: relative; /* 相对定位,用于伪元素和歌曲列表按钮 */
overflow: hidden; /* 隐藏超出部分,用于伪元素和按钮的圆角裁剪 */
}/* 增强发光效果的伪元素 */
.player::before {
content: '';
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
border-radius: 34px;
background: linear-gradient(45deg,
rgba(255,255,255,0.05) 0%,
rgba(255,255,255,0) 50%);
z-index: -1;
filter: blur(15px);
}/* 歌曲列表按钮 – 放置在播放器右上角 */
.list-btn {
position: absolute;
top: 20px; /* 距离顶部 */
right: 20px; /* 距离右侧 */
background: none;
border: none;
color: white; /* 纯白色图标 */
font-size: 28px; /* 大小适中 */
cursor: pointer;
transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
line-height: 1;
padding: 0;
width: 28px; /* 稍微缩小 */
height: 28px;
display: flex;
justify-content: center;
align-items: center;
z-index: 10; /* 确保在其他内容之上 */
-webkit-tap-highlight-color: transparent;
}/* 列表按钮图标样式 */
.list-btn span {
display: block;
width: 20px; /* 稍微缩小 */
height: 2px;
background: white;
position: relative;
box-shadow: 0 5px 0 white, 0 -5px 0 white; /* 稍微缩小间距 */
border-radius: 1px;
}.list-btn:hover {
transform: scale(1.1);
opacity: 0.9;
}/* 专辑封面容器样式 – 移除阴影 */
.album-art {
width: 180px; /* 缩小专辑封面尺寸 */
height: 180px; /* 缩小专辑封面尺寸 */
border-radius: 50%;
margin: 0 auto 20px; /* 缩小外边距 */
overflow: hidden;
animation: rotateAlbumArt 15s linear infinite paused;
border: 2px solid rgba(255, 255, 255, 0.1);
flex-shrink: 0;
}/* 播放状态下专辑封面动画运行 */
.player.playing .album-art {
animation-play-state: running;
}/* 专辑封面图片样式 */
.album-art img {
width: 100%;
height: 100%;
object-fit: cover;
}/* 歌曲信息容器样式 */
.song-info {
text-align: center;
margin-bottom: 25px; /* 缩小外边距 */
}/* 歌曲标题样式 */
.song-title {
font-size: 22px; /* 稍微缩小字体 */
font-weight: 600;
margin-bottom: 6px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); /* 保持文字阴影,增强可读性 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}/* 艺术家姓名样式 */
.artist {
font-size: 15px; /* 稍微缩小字体 */
color: rgba(255, 255, 255, 0.8);
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); /* 保持文字阴影,增强可读性 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}/* 统一滑块的CSS变量定义 */
.slider-base {
-webkit-appearance: none;
width: 100%;
height: 5px; /* 缩小滑块高度 */
background: transparent;
border-radius: 3px;
cursor: pointer;
outline: none;
overflow: hidden;
margin: 0;/* 定义通用滑块样式变量 */
–fill-color: white;
–track-bg-color: rgba(255, 255, 255, 0.2);
–thumb-size: 12px; /* 缩小滑块圆点大小 */
–thumb-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
–thumb-margin-top: -3.5px; /* 调整垂直居中 */
}/* 统一滑块轨道样式 */
.slider-base::-webkit-slider-runnable-track {
-webkit-appearance: none;
width: 100%;
height: 5px;
border-radius: 3px;
background: linear-gradient(to right,
var(–fill-color), var(–fill-color)) 0% 0% / var(–current-fill) 100% no-repeat,
var(–track-bg-color) 100% 0% / calc(100% – var(–current-fill)) 100% no-repeat;
transition: background-size 0.1s linear;
}/* 统一滑块圆点样式 */
.slider-base::-webkit-slider-thumb {
-webkit-appearance: none;
width: var(–thumb-size);
height: var(–thumb-size);
background: white;
border-radius: 50%;
margin-top: var(–thumb-margin-top);
box-shadow: var(–thumb-shadow);
transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}/* 统一滑块圆点悬停效果 */
.slider-base::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
}/* 播放进度条滑块样式 – 继承通用滑块样式 */
.progress-slider {
margin-bottom: 20px; /* 缩小外边距 */
–current-fill: 0%;
}/* 时间信息容器样式 */
.time-info {
display: flex;
justify-content: space-between;
font-size: 11px; /* 缩小字体 */
color: rgba(255, 255, 255, 0.7);
margin-bottom: 25px; /* 缩小外边距 */
}/* 控件按钮容器样式 */
.controls {
display: flex;
justify-content: center;
align-items: center;
gap: 25px; /* 缩小间距 */
}/* 上一首/下一首按钮基础样式 */
.control-btn {
background: none;
border: none;
color: rgba(255, 255, 255, 0.7);
font-size: 26px; /* 稍微缩小字体 */
cursor: pointer;
transition: all 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
padding: 5px;
line-height: 1;
-webkit-tap-highlight-color: transparent;
}/* 上一首/下一首按钮悬停效果 */
.control-btn:hover {
color: white;
transform: scale(1.05);
}/* 播放/暂停按钮样式 – 与其他按钮UI尺寸一致 */
.play-btn {
font-size: 26px;
padding: 0 5px;
}/* 音量控制容器样式 */
.volume-control {
display: flex;
align-items: center;
gap: 8px; /* 缩小间距 */
margin-top: 20px; /* 缩小外边距 */
}/* 音量图标样式 */
.volume-icon {
font-size: 18px; /* 缩小音量图标大小 */
color: rgba(255, 255, 255, 0.7);
line-height: 1;
}/* 音量调节滑块样式 (input[type="range"]) – 继承通用滑块样式 */
.volume-slider {
–current-fill: 70%;
}/* 专辑封面旋转动画 */
@keyframes rotateAlbumArt {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}/* — 歌曲列表面板样式 — */
/* 歌曲列表面板 – 垂直出现,只显示两首歌曲高度,无内容形变 */
.song-list-panel {
width: 320px; /* 与播放器宽度一致 */
/* 预估高度:标题(22px+15mb) + 2首歌曲(2*(45px+2*10px padding+4px mb)) + 2*25px (panel padding) = ~225px */
height: 225px; /* 固定高度,确保内部内容不变形 */
flex-shrink: 0; /* 防止列表在Flex容器中收缩 */
background: rgba(255, 255, 255, 0.15); /* 浅灰色半透明背景 */
backdrop-filter: blur(20px); /* 模糊效果 */
border-radius: 24px; /* 匹配播放器圆角 */
border: 1px solid rgba(255, 255, 255, 0.05); /* 替换为内部边框效果 */
padding: 25px; /* 列表面板内部的内边距 */
display: flex;
flex-direction: column;
overflow: hidden; /* 隐藏超出滚动区域的内容 *//* 隐藏状态:完全透明,不可见,并通过 transform 向下平移 */
opacity: 0;
visibility: hidden;
/* 向下平移自身高度 + 间距,使其初始位置在播放器下方 */
transform: translateY(calc(100% + 25px));/* 动画过渡:更精细的控制 visibility 和 easing */
/* max-height 也包含在内,但它会从 0 展开到固定 height,
transform 的视觉滑动是主导 */
transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), /* 动画曲线 */
opacity 0.5s cubic-bezier(0.2, 0.8, 0.2, 1),
visibility 0s linear 0.5s, /* 隐藏在动画结束时 */
max-height 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); /* 同步 max-height 动画 */
will-change: transform, opacity, max-height; /* 提升动画性能 *//* 当 max-height 为 0 时,内容会被裁剪。这里是隐藏时的额外控制 */
max-height: 0; /* 默认隐藏时高度为0 */
}/* 歌曲列表面板激活状态 */
.song-list-panel.active {
opacity: 1;
visibility: visible; /* 立即显示 */
transform: translateY(0); /* 向上滑动到其自然位置 */
max-height: 225px; /* 展开到固定高度 */transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1),
opacity 0.5s cubic-bezier(0.2, 0.8, 0.2, 1),
visibility 0s linear 0s, /* 立即显示 */
max-height 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}/* 确保隐藏时内部内容完全折叠 */
/* 由于我们使用固定的 height 和 padding,这些规则是为了防止意外的可见性 */
.song-list-panel:not(.active) h2,
.song-list-panel:not(.active) .song-list-item {
/* 强制隐藏子元素,因为父元素的 overflow:hidden 只能裁剪,不能让内容不可见 */
opacity: 0;
pointer-events: none; /* 禁用点击事件 */
transition: opacity 0.1s; /* 让子元素也淡出 */
}/* 歌曲列表标题 */
.song-list-panel h2 {
font-size: 22px;
font-weight: 600;
text-align: center;
margin-bottom: 15px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
flex-shrink: 0;
}/* 歌曲列表 ul */
.song-list {
list-style: none;
flex-grow: 1;
overflow-y: auto; /* 列表过长时滚动 */
-webkit-overflow-scrolling: touch;
padding-right: 5px;
}
/* 美化滚动条 (仅 Webkit 浏览器) */
.song-list::-webkit-scrollbar {
width: 6px;
}
.song-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
}
.song-list::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 3px;
transition: background-color 0.2s ease;
}
.song-list::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 255, 255, 0.3);
}/* 歌曲列表项 */
.song-list-item {
display: flex;
align-items: center;
padding: 10px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08); /* 分隔线透明度降低 */
cursor: pointer;
transition: background-color 0.2s ease, transform 0.1s ease;
border-radius: 8px;
margin-bottom: 4px;
}.song-list-item:last-child {
border-bottom: none;
}.song-list-item:hover {
background-color: rgba(255, 255, 255, 0.08);
transform: translateY(-2px);
}/* 当前播放歌曲的高亮效果 – 移除阴影 */
.song-list-item.playing-now {
background-color: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
position: relative;
}/* 列表项中的专辑封面 – 移除阴影 */
.song-list-item .album-art-sm {
width: 45px;
height: 45px;
border-radius: 50%;
overflow: hidden;
margin-right: 12px;
flex-shrink: 0;
border: 1px solid rgba(255, 255, 255, 0.08);
}.song-list-item .album-art-sm img {
width: 100%;
height: 100%;
object-fit: cover;
}/* 列表项中的文本信息 */
.song-list-text {
flex-grow: 1;
min-width: 0;
}.song-list-text .song-title-sm {
font-size: 15px;
font-weight: 500;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}/* 列表项的文本内容在隐藏时完全不可见 */
.song-list-panel:not(.active) .song-list-text .song-title-sm,
.song-list-panel:not(.active) .song-list-text .artist-sm {
line-height: 0; /* 确保文字行高也为0 */
height: 0;
margin: 0;
}.song-list-text .artist-sm {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
</head>
<body>
<div class="player-and-list-wrapper">
<!– 歌曲列表面板现在位于播放器上方 –>
<div class="song-list-panel">
<h2>歌曲列表</h2>
<ul class="song-list">
<!– 歌曲列表项将由 JavaScript 动态生成 –>
</ul>
</div><div class="player">
<div class="album-art">
<img src="" alt="专辑封面">
</div><div class="song-info">
<h1 class="song-title"></h1>
<p class="artist"></p>
</div><!– 播放进度条滑块 –>
<input type="range" class="progress-slider slider-base" min="0" max="100" value="0"><div class="time-info">
<span class="current-time">0:00</span>
<span class="duration">0:00</span>
</div><div class="controls">
<button class="control-btn prev-btn">⏮</button>
<button class="control-btn play-btn">▶</button>
<button class="control-btn next-btn">⏭</button>
</div><div class="volume-control">
<span class="volume-icon">🔈</span>
<input type="range" class="volume-slider slider-base" min="0" max="100" value="70">
</div><!– 歌曲列表按钮 –>
<button class="list-btn"><span></span></button>
</div>
</div><!– 音乐来源 –>
<audio id="myAudio"></audio><script>
// — DOM 元素获取 —
const playerAndListWrapper = document.querySelector('.player-and-list-wrapper');
const player = document.querySelector('.player');
const albumArtImg = document.querySelector('.album-art img');
const songTitle = document.querySelector('.song-title');
const artistName = document.querySelector('.artist');
const playBtn = document.querySelector('.play-btn');
const prevBtn = document.querySelector('.prev-btn');
const nextBtn = document.querySelector('.next-btn');
const listBtn = document.querySelector('.list-btn'); // 歌曲列表按钮
const progressSlider = document.querySelector('.progress-slider');
const volumeSlider = document.querySelector('.volume-slider');
const currentTimeSpan = document.querySelector('.current-time');
const durationSpan = document.querySelector('.duration');
const myAudio = document.getElementById('myAudio');const songListPanel = document.querySelector('.song-list-panel'); // 歌曲列表面板
const songListUl = document.querySelector('.song-list');// — 歌曲数据 (在这里添加/修改歌曲信息) —
const songs = [
{
title: '无尽夏',
artist: '˚˖ 𓂂ꔫ 希望我们的夏天没有尽头 ⊹꙳◦',
albumArt: 'https://i.postimg.cc/L5vS9S7V/IMG-20250729-143749.jpg',
audioSrc: 'https://files.catbox.moe/5otwfu.flac'
},
// 你可以在这里继续添加更多歌曲
// 示例:
// {
// title: '你的新歌曲标题',
// artist: '你的新艺术家',
// albumArt: 'https://example.com/your-new-album-cover.jpg', // 新专辑封面链接
// audioSrc: 'https://example.com/your-new-audio.mp3' // 新音频链接
// },
];let currentSongIndex = 0; // 当前播放歌曲的索引
// — 函数定义 —
// 格式化时间
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;
}// 加载指定索引的歌曲
function loadSong(index) {
if (index < 0) { // 如果是上一首,且当前是第一首,则跳到最后一首
currentSongIndex = songs.length – 1;
} else if (index >= songs.length) { // 如果是下一首,且当前是最后一首,则跳到第一首
currentSongIndex = 0;
} else {
currentSongIndex = index;
}const song = songs[currentSongIndex];
albumArtImg.src = song.albumArt;
songTitle.textContent = song.title;
artistName.textContent = song.artist;
myAudio.src = song.audioSrc;// 重新设置播放器状态
player.classList.remove('playing');
playBtn.textContent = '▶';
progressSlider.value = 0;
progressSlider.style.setProperty('–current-fill', `0%`);
currentTimeSpan.textContent = formatTime(0);// 更新歌曲列表中的高亮显示
updateSongListHighlight();
}// 动态生成歌曲列表
function renderSongList() {
songListUl.innerHTML = ''; // 清空现有列表
songs.forEach((song, index) => {
const li = document.createElement('li');
li.classList.add('song-list-item');
if (index === currentSongIndex) {
li.classList.add('playing-now');
}
li.dataset.index = index; // 存储歌曲索引li.innerHTML = `
<div class="album-art-sm">
<img src="${song.albumArt}" alt="专辑封面">
</div>
<div class="song-list-text">
<div class="song-title-sm">${song.title}</div>
<div class="artist-sm">${song.artist}</div>
</div>
`;
songListUl.appendChild(li);// 给每个列表项添加点击事件
li.addEventListener('click', () => {
loadSong(index); // 加载点击的歌曲
myAudio.play(); // 播放
player.classList.add('playing');
playBtn.textContent = '⏸';
songListPanel.classList.remove('active'); // 隐藏列表
});
});
}// 更新歌曲列表中的高亮显示
function updateSongListHighlight() {
document.querySelectorAll('.song-list-item').forEach((item, index) => {
if (index === currentSongIndex) {
item.classList.add('playing-now');
// 确保当前播放的歌曲在列表中可见
setTimeout(() => {
item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 0);
} else {
item.classList.remove('playing-now');
}
});
}// — 事件监听器 —
// 播放/暂停功能
playBtn.addEventListener('click', function() {
if (myAudio.paused) {
myAudio.play();
player.classList.add('playing');
playBtn.textContent = '❤︎';
} else {
myAudio.pause();
player.classList.remove('playing');
playBtn.textContent = '▶';
}
});// 上一首
prevBtn.addEventListener('click', () => {
loadSong(currentSongIndex – 1);
if (!myAudio.paused || player.classList.contains('playing')) { // 如果之前在播放,则继续播放
myAudio.play();
}
});// 下一首
nextBtn.addEventListener('click', () => {
loadSong(currentSongIndex + 1);
if (!myAudio.paused || player.classList.contains('playing')) {
myAudio.play();
}
});// 歌曲列表按钮 – 现在也负责关闭列表
listBtn.addEventListener('click', () => {
if (songListPanel.classList.contains('active')) {
// 如果列表已显示,则关闭它
songListPanel.classList.remove('active');
} else {
// 如果列表未显示,则显示它并重新渲染
renderSongList();
songListPanel.classList.add('active');
}
});// 音乐加载完成后,更新总时长和初始化音量滑块
myAudio.addEventListener('loadedmetadata', function() {
durationSpan.textContent = formatTime(myAudio.duration);
if (myAudio.volume === 1) {
myAudio.volume = 0.7;
volumeSlider.value = 70;
}
volumeSlider.style.setProperty('–current-fill', `${volumeSlider.value}%`);
});// 音乐播放时,更新当前时间和进度条
myAudio.addEventListener('timeupdate', function() {
const progressPercent = (myAudio.currentTime / myAudio.duration) * 100;
progressSlider.value = progressPercent;
progressSlider.style.setProperty('–current-fill', `${progressPercent}%`);
currentTimeSpan.textContent = formatTime(myAudio.currentTime);
});// 进度条滑动时,改变音乐播放位置
progressSlider.addEventListener('input', function() {
const seekTime = (this.value / 100) * myAudio.duration;
myAudio.currentTime = seekTime;
this.style.setProperty('–current-fill', `${this.value}%`);
currentTimeSpan.textContent = formatTime(seekTime);
});// 音乐播放结束时
myAudio.addEventListener('ended', function() {
if (currentSongIndex < songs.length – 1) {
loadSong(currentSongIndex + 1);
myAudio.play();
player.classList.add('playing');
playBtn.textContent = '⏸';
} else {
loadSong(0);
player.classList.remove('playing');
playBtn.textContent = '▶';
}
});// 音量滑块滑动时,改变音乐音量
volumeSlider.addEventListener('input', function() {
myAudio.volume = this.value / 100;
this.style.setProperty('–current-fill', `${this.value}%`);
});// — 初始化 —
loadSong(currentSongIndex);
volumeSlider.value = myAudio.volume * 100;
volumeSlider.style.setProperty('–current-fill', `${volumeSlider.value}%`);
</script>
</body>
</html>
“`