<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D音乐频谱效果</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000;
}
canvas {
display: block;
}
#audioControl {
position: absolute;
top: 20px;
left: 20px;
z-index: 100;
color: white;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
}
#info {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center;
color: white;
font-family: Arial, sans-serif;
pointer-events: none;
}
</style>
</head>
<body>
<div id="audioControl">
<input type="file" id="audioInput" accept="audio/*">
<button id="playButton" disabled>播放</button>
<button id="stopButton" disabled>停止</button>
</div>
<div id="info">3D音乐频谱效果 - 请上传音频文件</div>
<canvas id="canvas"></canvas>
<script>
// 初始化变量
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const audioInput = document.getElementById('audioInput');
const playButton = document.getElementById('playButton');
const stopButton = document.getElementById('stopButton');
let audioContext;
let analyser;
let dataArray;
let source;
let isPlaying = false;
let animationId;
// 设置画布大小
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// 初始化音频上下文和分析器
function initAudio() {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
}
// 处理音频文件
audioInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
if (!audioContext) {
initAudio();
}
const fileReader = new FileReader();
fileReader.onload = function(e) {
const audioData = e.target.result;
audioContext.decodeAudioData(audioData).then(function(buffer) {
if (source) {
source.stop();
}
source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(analyser);
analyser.connect(audioContext.destination);
playButton.disabled = false;
stopButton.disabled = false;
document.getElementById('info').textContent = `正在播放: ${file.name}`;
});
};
fileReader.readAsArrayBuffer(file);
});
// 播放按钮事件
playButton.addEventListener('click', function() {
if (source && !isPlaying) {
source.start(0);
isPlaying = true;
animate();
}
});
// 停止按钮事件
stopButton.addEventListener('click', function() {
if (source && isPlaying) {
source.stop();
isPlaying = false;
cancelAnimationFrame(animationId);
ctx.clearRect(0, 0, canvas.width, canvas.height);
document.getElementById('info').textContent = '3D音乐频谱效果 - 请上传音频文件';
}
});
// 3D频谱动画
function animate() {
animationId = requestAnimationFrame(animate);
analyser.getByteFrequencyData(dataArray);
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = (canvas.width / dataArray.length) * 2.5;
let x = 0;
for (let i = 0; i < dataArray.length; i++) {
const barHeight = dataArray[i] * 1.5;
// 创建3D效果
const hue = i / dataArray.length * 360;
ctx.fillStyle = `hsla(${hue}, 100%, 50%, 0.8)`;
// 绘制3D柱状图
const posX = x + canvas.width / 4;
const posY = canvas.height / 2;
// 前表面
ctx.fillRect(posX, posY - barHeight / 2, barWidth, barHeight);
// 侧表面
ctx.fillStyle = `hsla(${hue}, 100%, 40%, 0.6)`;
ctx.beginPath();
ctx.moveTo(posX + barWidth, posY - barHeight / 2);
ctx.lineTo(posX + barWidth + 10, posY - barHeight / 4);
ctx.lineTo(posX + barWidth + 10, posY + barHeight / 4);
ctx.lineTo(posX + barWidth, posY + barHeight / 2);
ctx.closePath();
ctx.fill();
// 顶表面
ctx.fillStyle = `hsla(${hue}, 100%, 60%, 0.6)`;
ctx.beginPath();
ctx.moveTo(posX, posY - barHeight / 2);
ctx.lineTo(posX + barWidth, posY - barHeight / 2);
ctx.lineTo(posX + barWidth + 10, posY - barHeight / 4);
ctx.lineTo(posX + 10, posY - barHeight / 4);
ctx.closePath();
ctx.fill();
x += barWidth + 1;
}
// 添加一些粒子效果增强视觉效果
for (let i = 0; i < 5; i++) {
const size = Math.random() * 5;
ctx.fillStyle = `hsla(${Math.random() * 360}, 100%, 50%, ${Math.random()})`;
ctx.beginPath();
ctx.arc(
Math.random() * canvas.width,
Math.random() * canvas.height,
size,
0,
Math.PI * 2
);
ctx.fill();
}
}
</script>
</body>
</html>