<!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;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
}
canvas {
display: block;
}
.info {
position: absolute;
bottom: 20px;
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
}
.controls {
position: absolute;
top: 20px;
color: white;
text-align: center;
}
button {
background: rgba(255, 255, 255, 0.2);
border: 1px solid white;
color: white;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 0 5px;
}
button:hover {
background: rgba(255, 255, 255, 0.3);
}
input[type="file"] {
display: none;
}
label {
background: rgba(255, 255, 255, 0.2);
border: 1px solid white;
color: white;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 0 5px;
display: inline-block;
}
label:hover {
background: rgba(255, 255, 255, 0.3);
}
</style>
</head>
<body>
<div class="controls">
<label for="audio-upload">选择音乐文件</label>
<input type="file" id="audio-upload" accept="audio/*">
<button id="play-btn">播放</button>
<button id="pause-btn">暂停</button>
<button id="stop-btn">停止</button>
<p id="song-info">未加载音乐</p>
</div>
<canvas id="visualizer"></canvas>
<div class="info">
3D音乐频谱效果
</div>
<script>
// 初始化变量
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
const audioUpload = document.getElementById('audio-upload');
const playBtn = document.getElementById('play-btn');
const pauseBtn = document.getElementById('pause-btn');
const stopBtn = document.getElementById('stop-btn');
const songInfo = document.getElementById('song-info');
// 设置画布大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 音频上下文和分析器
let audioContext;
let analyser;
let audioSource;
let audioBuffer;
let isPlaying = false;
let dataArray;
let bufferLength;
// 3D频谱参数
const bars = 128;
const barWidth = 2;
const maxHeight = 200;
const radius = 150;
const rotationSpeed = 0.001;
let rotationAngle = 0;
// 颜色渐变
const gradient = ctx.createLinearGradient(0, 0, 0, maxHeight);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(0.5, '#ffff00');
gradient.addColorStop(1, '#00ff00');
// 初始化音频上下文
function initAudioContext() {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
}
// 处理音频文件
audioUpload.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
if (!audioContext) {
initAudioContext();
}
const fileReader = new FileReader();
fileReader.onload = function(e) {
audioContext.decodeAudioData(e.target.result)
.then(buffer => {
audioBuffer = buffer;
songInfo.textContent = `已加载: ${file.name}`;
playBtn.disabled = false;
})
.catch(err => {
console.error('解码音频数据错误:', err);
songInfo.textContent = '加载音乐失败';
});
};
fileReader.readAsArrayBuffer(file);
});
// 播放音频
playBtn.addEventListener('click', function() {
if (!audioBuffer || isPlaying) return;
if (audioContext.state === 'suspended') {
audioContext.resume();
}
audioSource = audioContext.createBufferSource();
audioSource.buffer = audioBuffer;
audioSource.connect(analyser);
analyser.connect(audioContext.destination);
audioSource.start(0);
audioSource.loop = true;
isPlaying = true;
animate();
});
// 暂停音频
pauseBtn.addEventListener('click', function() {
if (!isPlaying) return;
audioContext.suspend();
isPlaying = false;
});
// 停止音频
stopBtn.addEventListener('click', function() {
if (!isPlaying) return;
audioSource.stop();
isPlaying = false;
});
// 动画循环
function animate() {
if (!isPlaying) return;
requestAnimationFrame(animate);
analyser.getByteFrequencyData(dataArray);
// 清除画布
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新旋转角度
rotationAngle += rotationSpeed;
// 绘制3D频谱
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
for (let i = 0; i < bars; i++) {
// 计算频谱条的高度
const barHeight = (dataArray[i % bufferLength] / 255) * maxHeight;
// 计算3D位置
const angle = (i / bars) * Math.PI * 2 + rotationAngle;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
// 绘制频谱条
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle + Math.PI / 2);
// 设置颜色和绘制
ctx.fillStyle = gradient;
ctx.fillRect(-barWidth / 2, 0, barWidth, -barHeight);
// 添加发光效果
ctx.shadowBlur = 15;
ctx.shadowColor = `hsl(${i * 360 / bars}, 100%, 50%)`;
ctx.restore();
}
// 添加中心圆点
ctx.beginPath();
ctx.arc(centerX, centerY, 10, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.fill();
}
// 窗口大小调整
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
</body>
</html>