音频可视化小工具
- 文章说明
- 功能特点
- 使用说明
- 技术细节
- 核心代码
- 效果展示
- 源码下载
文章说明
这是一个基于 JavaScript 和 Web Audio API 实现的音频可视化小工具。通过上传音频文件,工具可以实时展示音频的可视化效果,包括条形频谱、圆形频谱等多种模式。代码由 GPT 生成,适合用于学习、演示或嵌入到网页中。
功能特点
-
音频可视化:
- 支持条形频谱(Bar Mode)、圆形频谱(Circle Mode)和扇形频谱(Arc Mode)三种可视化模式。
- 实时动态展示音频的频率数据。
-
音频播放控制:
- 支持播放、暂停功能。
- 上传音频文件后自动播放。
-
元数据展示:
- 自动提取音频文件的元数据(如歌曲名称、艺术家、封面图片)。
- 如果元数据不存在,则显示文件名作为歌曲名称。
-
交互设计:
- 提供上传按钮、模式切换按钮和暂停按钮,操作简单直观。
- 界面美观,支持浅蓝色主题和毛玻璃效果。
使用说明
-
上传音频文件:
- 点击 Upload Audio File 按钮,选择本地音频文件(支持 MP3、WAV 等格式)。
- 上传后,音频会自动播放,并显示可视化效果。
-
切换可视化模式:
- 点击 Bar Mode、Circle Mode 或 Arc Mode 按钮,切换不同的可视化效果。
-
播放/暂停音频:
- 点击 Pause 按钮可以暂停音频,再次点击可以继续播放。
-
查看元数据:
- 上传音频后,工具会自动显示歌曲名称、艺术家和封面图片(如果音频文件包含元数据)。
技术细节
-
核心技术:
- 使用 Web Audio API 分析音频数据。
- 通过 Canvas 绘制实时可视化效果。
- 使用 jsmediatags 库提取音频文件的元数据(如 ID3 标签)。
-
可视化模式:
- 条形频谱:将音频频率数据绘制为条形图。
- 圆形频谱:将音频频率数据围绕中心点绘制为圆形。
- 扇形频谱:将音频频率数据围绕中心点绘制为扇形。
-
界面设计:
- 使用 CSS 实现毛玻璃效果和浅蓝色主题。
- 动态切换按钮状态,提升用户体验。
核心代码
一个HTML页面
html"><!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Audio Visualization</title><style>body {display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;background: linear-gradient(135deg, #1e3c72, #2a5298);color: #fff;font-family: 'Arial', sans-serif;overflow: hidden;}.container {display: flex;flex-direction: column;align-items: center;background: rgba(255, 255, 255, 0.1);border-radius: 20px;padding: 20px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);backdrop-filter: blur(10px);width: 90%;max-width: 800px;}canvas {background: rgba(255, 255, 255, 0.1);border-radius: 15px;margin-top: 20px;width: 100%;height: 300px;}#upload-label {background: rgba(255, 255, 255, 0.2);padding: 10px 20px;border-radius: 25px;cursor: pointer;font-size: 16px;color: #fff;transition: background 0.3s ease;margin-bottom: 20px;}#upload-label:hover {background: rgba(255, 255, 255, 0.3);}#audioFile {display: none;}.music-info {display: flex;align-items: center;width: 100%;margin-bottom: 20px;}.music-cover {width: 100px;height: 100px;border-radius: 10px;margin-right: 20px;background: rgba(255, 255, 255, 0.1);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);object-fit: cover;opacity: 0;/* Initially hidden */transition: opacity 0.5s ease;}.music-cover.visible {opacity: 1;/* Show when cover is loaded */}.music-details {display: flex;flex-direction: column;justify-content: center;}.music-title {font-size: 20px;font-weight: bold;margin: 0;}.music-artist {font-size: 16px;color: rgba(255, 255, 255, 0.8);margin: 5px 0 0 0;}.mode-switcher {display: flex;gap: 10px;margin-top: 20px;}.mode-switcher button {background: rgba(255, 255, 255, 0.2);border: none;padding: 10px 20px;border-radius: 25px;color: #fff;font-size: 14px;cursor: pointer;transition: background 0.3s ease;}.mode-switcher button:hover {background: rgba(255, 255, 255, 0.3);}.mode-switcher button.active {background: #00b4db;}#pause-button {background: rgba(255, 255, 255, 0.2);border: none;padding: 10px 20px;border-radius: 25px;color: #fff;font-size: 14px;cursor: pointer;transition: background 0.3s ease;margin-top: 20px;}#pause-button:hover {background: rgba(255, 255, 255, 0.3);}</style></head><body><div class="container"><label for="audioFile" id="upload-label">Upload Audio File</label><input type="file" id="audioFile" accept="audio/*"/><div class="music-info"><div class="music-cover" id="music-cover"></div><div class="music-details"><p class="music-title" id="music-title">Song Title</p><p class="music-artist" id="music-artist">Artist</p></div></div><canvas id="visualizer" width="800" height="300"></canvas><div class="mode-switcher"><button id="bar-mode" class="active">Bar Mode</button><button id="circle-mode">Circle Mode</button><button id="arc-mode">Arc Mode</button></div><button id="pause-button">Pause</button></div><!-- Include jsmediatags library --><script src="https://unpkg.com/jsmediatags@3.9.7/dist/jsmediatags.min.js"></script><script>html" title=javascript>javascript">const canvas = document.getElementById('visualizer');const ctx = canvas.getContext('2d');const audioFileInput = document.getElementById('audioFile');const musicCover = document.getElementById('music-cover');const musicTitle = document.getElementById('music-title');const musicArtist = document.getElementById('music-artist');const barModeButton = document.getElementById('bar-mode');const circleModeButton = document.getElementById('circle-mode');const arcModeButton = document.getElementById('arc-mode');const pauseButton = document.getElementById('pause-button');let audioContext;let analyser;let source;let dataArray;let bufferLength;let currentMode = 'bar'; // Default modelet audio; // Audio elementlet isPlaying = false; // Track playback state// Event listeners for mode switchingbarModeButton.addEventListener('click', () => switchMode('bar'));circleModeButton.addEventListener('click', () => switchMode('circle'));arcModeButton.addEventListener('click', () => switchMode('arc'));// Event listener for pause buttonpauseButton.addEventListener('click', togglePlayback);function switchMode(mode) {currentMode = mode;barModeButton.classList.remove('active');circleModeButton.classList.remove('active');arcModeButton.classList.remove('active');if (mode === 'bar') barModeButton.classList.add('active');if (mode === 'circle') circleModeButton.classList.add('active');if (mode === 'arc') arcModeButton.classList.add('active');}function togglePlayback() {if (isPlaying) {audio.pause();pauseButton.textContent = 'Play';} else {audio.play();pauseButton.textContent = 'Pause';}isPlaying = !isPlaying;}audioFileInput.addEventListener('change', function (event) {const file = event.target.files[0];if (file) {const fileURL = URL.createObjectURL(file);audio = new Audio(fileURL);setupAudioContext(audio);audio.play();isPlaying = true;pauseButton.textContent = 'Pause';// Extract metadata using jsmediatagsjsmediatags.read(file, {onSuccess: function (tag) {const tags = tag.tags;// Update song titleif (tags.title) {musicTitle.textContent = tags.title;} else {musicTitle.textContent = file.name.replace(/\.[^/.]+$/, ""); // Fallback to file name}// Update artistif (tags.artist) {musicArtist.textContent = tags.artist;} else {musicArtist.textContent = "Unknown Artist";}// Update cover imageif (tags.picture) {const picture = tags.picture;const base64String = Array.from(picture.data).map(byte => String.fromCharCode(byte)).join('');const base64 = `data:${picture.format};base64,${window.btoa(base64String)}`;musicCover.style.backgroundImage = `url(${base64})`;musicCover.classList.add('visible'); // Show cover} else {musicCover.style.backgroundImage = ''; // No covermusicCover.classList.remove('visible'); // Hide cover}},onError: function (error) {console.error("Error reading metadata:", error);// Fallback to file name if metadata cannot be readmusicTitle.textContent = file.name.replace(/\.[^/.]+$/, "");musicArtist.textContent = "Unknown Artist";musicCover.style.backgroundImage = ''; // No covermusicCover.classList.remove('visible'); // Hide cover}});}});function setupAudioContext(audio) {audioContext = new (window.AudioContext || window.webkitAudioContext)();analyser = audioContext.createAnalyser();source = audioContext.createMediaElementSource(audio);source.connect(analyser);analyser.connect(audioContext.destination);analyser.fftSize = 512;bufferLength = analyser.frequencyBinCount;dataArray = new Uint8Array(bufferLength);draw();}function draw() {requestAnimationFrame(draw);analyser.getByteFrequencyData(dataArray);ctx.clearRect(0, 0, canvas.width, canvas.height);if (currentMode === 'bar') {drawBars();} else if (currentMode === 'circle') {drawCircle();} else if (currentMode === 'arc') {drawArc();}}function drawBars() {const barWidth = (canvas.width / bufferLength) * 1.5; // Thinner barslet barHeight;let x = 0;for (let i = 0; i < bufferLength; i++) {barHeight = dataArray[i] / 2;// Create a gradient for each barconst gradient = ctx.createLinearGradient(x, canvas.height, x + barWidth, canvas.height - barHeight);gradient.addColorStop(0, '#00b4db');gradient.addColorStop(1, '#0083b0');ctx.fillStyle = gradient;ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);// Add glow effectctx.shadowBlur = 10;ctx.shadowColor = 'rgba(0, 180, 219, 0.7)';x += barWidth + 1;}}function drawCircle() {const centerX = canvas.width / 2;const centerY = canvas.height / 2;const radius = 100;const barWidth = 2; // Thinner barsconst angleStep = (Math.PI * 2) / bufferLength;for (let i = 0; i < bufferLength; i++) {const barHeight = dataArray[i] / 2;const angle = i * angleStep;const x = centerX + Math.cos(angle) * radius;const y = centerY + Math.sin(angle) * radius;const endX = centerX + Math.cos(angle) * (radius + barHeight);const endY = centerY + Math.sin(angle) * (radius + barHeight);// Create a gradient for each barconst gradient = ctx.createLinearGradient(x, y, endX, endY);gradient.addColorStop(0, '#00b4db');gradient.addColorStop(1, '#0083b0');ctx.strokeStyle = gradient;ctx.lineWidth = barWidth;ctx.beginPath();ctx.moveTo(x, y);ctx.lineTo(endX, endY);ctx.stroke();// Add glow effectctx.shadowBlur = 10;ctx.shadowColor = 'rgba(0, 180, 219, 0.7)';}}function drawArc() {const centerX = canvas.width / 2;const centerY = canvas.height / 2;const radius = 100;const barWidth = 2; // Thinner barsconst angleStep = (Math.PI * 2) / bufferLength;for (let i = 0; i < bufferLength; i++) {const barHeight = dataArray[i] / 2;const startAngle = i * angleStep;const endAngle = startAngle + angleStep;// Create a gradient for each barconst gradient = ctx.createRadialGradient(centerX, centerY, radius,centerX, centerY, radius + barHeight);gradient.addColorStop(0, '#00b4db');gradient.addColorStop(1, '#0083b0');ctx.strokeStyle = gradient;ctx.lineWidth = barWidth;ctx.beginPath();ctx.arc(centerX, centerY, radius + barHeight, startAngle, endAngle);ctx.stroke();// Add glow effectctx.shadowBlur = 10;ctx.shadowColor = 'rgba(0, 180, 219, 0.7)';}}</script></body></html>
效果展示
条形频谱模式
圆形频谱模式
源码下载
音频可视化小工具