介绍
使用 CSS 绘制一张唱片
点击唱片,唱片旋转并播放音乐
如果唱片没有旋转,则说明你当前使用的浏览器不支持 CSS 旋转动画。本示例暂不考虑 CSS 的兼容性问题
效果展示
<style>
/* config */
#ccc.record-player {
--record-radius: 42vmin;
}
/* 唱片机容器 */
#ccc.record-player {
--record-diameter: calc(2 * var(--record-radius));
--record-grooves-width: max(0.1px, calc(var(--record-radius) / 100));
--record-shadow: calc(4 * var(--record-grooves-width));
--record-img-radius: calc(0.5 * var(--record-radius));
position: relative;
width: calc(var(--record-diameter) + 2 * var(--tonearm-width));
height: calc(var(--record-diameter) + 2 * var(--tonearm-width));
border-radius: calc(0.5 * var(--padding-width));
}
/* 唱片 [begin] */
#ccc.record-player .record-container {
padding: var(--record-shadow);
display: block;
overflow: hidden;
background:
radial-gradient(
#00000055 var(--record-radius),
transparent calc(var(--record-radius) + var(--record-shadow))
);
}
#ccc.record-player .record {
width: var(--record-diameter);
height: var(--record-diameter);
box-sizing: border-box;
border: var(--record-grooves-width) solid #000000;
border-radius: 50%;
background:
conic-gradient(
#00000022 15%,
#2a2a2a22 25%,
#00000022 35%,
#00000022 60%,
#2a2a2a22 75%,
#00000022 85%
),
radial-gradient(
#0a0a0aff 41%,
#44444422 41%,
#44444422 48%,
#20202022 48%,
#20202022 64%,
#4a4a4a22 64%
),
repeating-radial-gradient(
#000000 0,
#000000 calc(0.5 * var(--record-grooves-width)),
#1a1a1a calc(0.5 * var(--record-grooves-width)),
#1a1a1a var(--record-grooves-width)
),
#000000;
}
#ccc.record-player .record:hover {
cursor: pointer;
}
/* 唱片 [end] */
/* 唱片封面 */
#ccc.record-player .record img {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: calc(2 * var(--record-img-radius));
height: calc(2 * var(--record-img-radius));
box-sizing: border-box;
object-fit: cover;
user-select: none;
pointer-events: none;
border-radius: 50%;
border: calc(2.5 * var(--record-grooves-width)) solid #a78d0b;
background-color: #222;
}
/* 亮光 */
#ccc.record-player .light {
position: absolute;
left: 0;
top: 0;
width: calc(var(--record-diameter) + 2 * var(--record-shadow));
height: calc(var(--record-diameter) + 2 * var(--record-shadow));
pointer-events: none;
border-radius: 50%;
mask-image: radial-gradient(
transparent var(--record-img-radius),
#000000 0
);
background:
conic-gradient(
#ffffff01 25%,
#ffffff22 35%,
transparent 45%,
transparent 70%,
#ffffff22 85%,
#ffffff01 95%
);
}
</style>
<style>
/* 唱片旋转 [begin] */
@keyframes record-rotation-animation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.record-player .record {
--record-animation: record-rotation-animation 4s linear infinite;
}
.record-player:has(input[type="checkbox"]:checked) .record {
animation: var(--record-animation);
}
.record-player:has(input[type="checkbox"]:not(:checked)) .record {
animation: var(--record-animation) paused;
}
/* 唱片旋转 [end] */
</style>
<div><p>
选择歌曲:
<select id="selector" style="min-width: 12em; border: 1px solid #f60; outline: none;"></select>
更多我的音乐作品 ->
<a href="https://space.bilibili.com/387314477/channel/seriesdetail?sid=3957845&ctype=0" target="_Blank">我的 bilibili 个人空间</a>
</p>
</div>
<div style="margin: auto; width: fit-content;"><div class="record-player" id="ccc">
<label class="record-container">
<input type="checkbox" hidden="">
<div class="record">
<img src="" draggable="false">
</div>
<div class="light"></div>
</label>
<audio>
<source src="" type="audio/mpeg">
</audio></div></div>
<script>
(function() {
const record_player = document.getElementById("ccc");
const checkbox = record_player.querySelector("input[type='checkbox']");
const img = record_player.querySelector("img");
const audio = record_player.querySelector("audio");
const select = document.getElementById("selector")
function reset() {
audio.currentTime = 0;
audio.pause();
checkbox.checked = false;
}
audio.addEventListener("timeupdate", () => {
if (audio.currentTime >= audio.duration)
reset()
});
checkbox.addEventListener("change", () => {
if (checkbox.checked)
audio.play();
else
audio.pause();
});
const music_list = [
{
"name": "【全是爱】嫣汐 & 琴歌",
"img": `/upload/%5B封面%5D%5B全是爱%5D%5B琴歌%20嫣汐%5D%5B袅袅虚拟歌手%20Muta%5D%5B2023-12-31%5D.bmp`,
"audio": `/upload/%5B翻唱%5D%5B全是爱%5D%5B琴歌%20嫣汐%5D%5B袅袅虚拟歌手%20Muta%5D%5B2023-12-31%5D.mp3`
}, {
"name": "【错错错】嫣汐 & 琉璃",
"img": `/upload/%5B封面%5D%5B错错错%5D%5B嫣汐%20琉璃%5D%5BMuta%20AISingers%5D%5B2024-05-06%5D.bmp`,
"audio": `/upload/%5B翻唱%5D%5B错错错%5D%5B嫣汐%20琉璃%5D%5BMuta%20AISingers%5D%5B2024-05-06%5D.mp3`
}, {
"name": "【无法原谅】嫣汐",
"img": `/upload/%5B封面%5D%5B无法原谅%5D%5B嫣汐%5D%5BAISingers%20Muta%5D%5B2022-01-21%5D.jpg`,
"audio": `/upload/%5B翻唱%5D%5B无法原谅%5D%5B嫣汐%5D%5BAISingers%20Muta%5D%5B2022-01-21%5D.mp3`
}, {
"name": "【游山恋】心华",
"img": `/upload/%5B封面%5D%5B游山恋%5D%5B心华%5D%5BVocaloid4%5D%5B2023-07-17%5D.jpg`,
"audio": `/upload/%5B翻唱%5D%5B游山恋%5D%5B心华%5D%5BVocaloid4%5D%5B2023-07-17%5D.mp3`
}
];
for (let i = 0; i < music_list.length; i++) {
const music = music_list[i];
const option = document.createElement("option");
option.textContent = music["name"];
option.value = i;
select.appendChild(option);
}
select.addEventListener("change", (e) => {
const music = music_list[select.selectedIndex];
reset();
img.src = music["img"];
audio.src = music["audio"];
})
select.selectedIndex = 0;
select.dispatchEvent(new Event("change"));
})();
</script>
代码实现
你需要自定义 ./img.png
和 ./music.mp3
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>唱片</title>
<style>
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
background-color: #f0f0f0;
}
</style>
<style>
.record-player {
--record-radius: 45vmin;
}
/* 唱片机容器 */
.container {
margin: auto;
width: fit-content;
}
.record-player {
--record-diameter: calc(2 * var(--record-radius));
--record-grooves-width: max(0.1px, calc(var(--record-radius) / 100));
--record-shadow: calc(4 * var(--record-grooves-width));
--record-img-radius: calc(0.5 * var(--record-radius));
position: relative;
width: calc(var(--record-diameter) + 2 * var(--tonearm-width));
height: calc(var(--record-diameter) + 2 * var(--tonearm-width));
border-radius: calc(0.5 * var(--padding-width));
}
/* 唱片 [begin] */
.record-player .record-container {
padding: var(--record-shadow);
display: block;
overflow: hidden;
background:
radial-gradient(
#00000055 var(--record-radius),
transparent calc(var(--record-radius) + var(--record-shadow))
);
}
.record-player .record {
width: var(--record-diameter);
height: var(--record-diameter);
box-sizing: border-box;
border: var(--record-grooves-width) solid #000000;
border-radius: 50%;
background:
conic-gradient(
#00000022 15%,
#2a2a2a22 25%,
#00000022 35%,
#00000022 60%,
#2a2a2a22 75%,
#00000022 85%
),
radial-gradient(
#0a0a0aff 41%,
#44444422 41%,
#44444422 48%,
#20202022 48%,
#20202022 64%,
#4a4a4a22 64%
),
repeating-radial-gradient(
#000000 0,
#000000 calc(0.5 * var(--record-grooves-width)),
#1a1a1a calc(0.5 * var(--record-grooves-width)),
#1a1a1a var(--record-grooves-width)
),
#000000;
}
.record-player .record:hover {
cursor: pointer;
}
/* 唱片 [end] */
/* 唱片封面 */
.record-player .record img {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: calc(2 * var(--record-img-radius));
height: calc(2 * var(--record-img-radius));
box-sizing: border-box;
object-fit: cover;
user-select: none;
pointer-events: none;
border-radius: 50%;
border: calc(2.5 * var(--record-grooves-width)) solid #a78d0b;
background-color: #222;
}
/* 亮光 */
.record-player .light {
position: absolute;
left: 0;
top: 0;
width: calc(var(--record-diameter) + 2 * var(--record-shadow));
height: calc(var(--record-diameter) + 2 * var(--record-shadow));
pointer-events: none;
border-radius: 50%;
mask-image: radial-gradient(
transparent var(--record-img-radius),
#000000 0
);
background:
conic-gradient(
#ffffff01 25%,
#ffffff22 35%,
transparent 45%,
transparent 70%,
#ffffff22 85%,
#ffffff01 95%
);
}
</style>
<style>
/* 唱片旋转 [begin] */
@keyframes record-rotation-animation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.record-player .record {
--record-animation: record-rotation-animation 4s linear infinite;
}
.record-player:has(input[type="checkbox"]:checked) .record {
animation: var(--record-animation);
}
.record-player:has(input[type="checkbox"]:not(:checked)) .record {
animation: var(--record-animation) paused;
}
/* 唱片旋转 [end] */
</style>
</head>
<body>
<div class="container">
<div class="record-player" id="ccc">
<label class="record-container">
<input type="checkbox" hidden>
<div class="record">
<img src="./img.png" draggable="false">
</div>
<div class="light"></div>
</label>
<audio>
<source src="./music.mp3" type="audio/mpeg">
</audio>
</div>
</div>
<script>
const record_player = document.getElementById("ccc");
const checkbox = record_player.querySelector("input[type='checkbox']");
const audio = record_player.querySelector("audio");
audio.load();
audio.addEventListener("timeupdate", () => {
if (audio.currentTime >= audio.duration) {
audio.currentTime = 0;
audio.pause();
checkbox.checked = false;
}
});
checkbox.addEventListener("change", () => {
if (checkbox.checked) {
audio.play();
} else {
audio.pause();
}
});
</script>
</body>
</html>