如何修复Canvas中音频可视化条形的颜色匹配问题?
修复Canvas音乐可视化条形颜色匹配问题
我编写了一段JavaScript代码,用于在Canvas中绘制垂直条形,并根据音乐平均频率改变条形颜色。目标是让每个条形的颜色与前一个条形的颜色匹配,但实际得到的颜色不符合预期。
初始完整代码
HTML部分
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> <style> body, html { margin: 0; padding: 0; width: 100%; height: 100%; } #canvas { width: 100%; height: 100vh; display: block; } </style> </head> <body> <input type="file" id="fileInput" accept="audio/*"> <canvas id="canvas"></canvas> </body> </html>
JavaScript部分
const fileInput = document.getElementById('fileInput'); const canvas = document.getElementById('canvas'); const canvasCtx = canvas.getContext('2d'); const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); // 调整Canvas尺寸函数 function resizeCanvas() { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // 初始调用设置Canvas尺寸 let barColors = []; const defaultBarColors = []; fileInput.addEventListener('change', function() { const file = this.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { audioCtx.decodeAudioData(e.target.result, function(buffer) { visualize(buffer); }); }; reader.readAsArrayBuffer(file); } }); let rev; function visualize(buffer) { const source = audioCtx.createBufferSource(); source.buffer = buffer; const analyser = audioCtx.createAnalyser(); source.connect(analyser); analyser.connect(audioCtx.destination); analyser.fftSize = 512; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); source.start(); draw(); function draw() { requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvasCtx.clearRect(0, 0, canvas.width, canvas.height); const barWidth = Math.floor(canvas.width / 5); const sensitivity = 1; const frequencyBands = [ { from: 0, to: Math.floor(bufferLength * 0.2) }, // 低频 { from: Math.floor(bufferLength * 0.2), to: Math.floor(bufferLength * 0.4) }, // 中低频 { from: Math.floor(bufferLength * 0.4), to: Math.floor(bufferLength * 0.6) }, // 中频 { from: Math.floor(bufferLength * 0.6), to: Math.floor(bufferLength * 0.8) }, // 中高频 { from: Math.floor(bufferLength * 0.8), to: bufferLength } // 高频 ]; let x = 0; for (let i = 0; i < frequencyBands.length; i++) { // 0,1,2,3,4 const { from, to } = frequencyBands[i]; const sum = dataArray.slice(from, to).reduce((acc, val) => acc + val, 0); const avg = sum / (to - from); let colorValue = rev[i]; if (avg > 0) { if (i > 0) { colorValue = rev[i - 1]; } else if (i == 0) { colorValue = "rgb(255, 255, 255)"; // 如果是第一个条形,则设为白色 } } else { colorValue = rev[i]; } canvasCtx.fillStyle = colorValue; const barHeight = Math.max((avg / 255) * canvas.height * sensitivity, canvas.height); canvasCtx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); x += barWidth; } } } function initializeCanvas() { const barWidth = Math.floor(canvas.width / 5); let x = 0; for (let i = 4; i >= 0; i--) { const colorValue = Math.floor(((i / 5) * (240 - 51)) + 51); const color = `rgb(${colorValue}, ${colorValue}, ${colorValue})`; defaultBarColors[i] = color; canvasCtx.fillStyle = color; canvasCtx.fillRect(x, 0, barWidth, canvas.height); x += barWidth; } rev = defaultBarColors.reverse(); } initializeCanvas();
核心颜色逻辑问题
我原本的颜色逻辑是:
- 第0个条形设为白色
- 若平均频率
avg>0,则当前条形取前一个条形的默认颜色 - 否则使用
rev数组中的默认颜色
核心代码片段:
let colorValue = rev[i]; if (avg > 0) { if (i > 0) { colorValue = rev[i - 1]; } else if (i == 0) { colorValue = "rgb(255, 255, 255)"; // 如果是第一个条形,则设为白色 } } else { colorValue = rev[i]; }
之后我尝试修改了draw函数,但问题仍未解决:
function draw() { requestAnimationFrame(draw); const numBars = 5; let bar = 0; analyser.getByteFrequencyData(dataArray); canvasCtx.fillStyle = "#000"; canvasCtx.fillRect(0, 0, canvas.width, canvas.height); const barWidth = canvas.width / numBars; for (let i = 0; i < numBars; i++) { const start = Math.floor(i * bufferLength / numBars); const end = Math.floor((i + 1) * bufferLength / numBars); let sum = 0; for (let j = start; j < end; j++) { sum += dataArray[j]; } const avg = sum / (end - start); if (avg > 0) { currentColors[i] = avg; } else { currentColors[i] = rev[i]; // 恢复为初始颜色 } const rgbColor = `rgb(${currentColors[i]}, ${currentColors[i]}, ${currentColors[i]})`; canvasCtx.fillStyle = rgbColor; canvasCtx.fillRect(bar, 0, barWidth, canvas.height); bar += barWidth; } }
修复方案
问题出在两个关键点:
- 原逻辑中直接取前一个条形的默认颜色,而非当前帧中前一个条形的实际颜色
- 修改后的代码里
currentColors未初始化,且直接用avg作为RGB值,范围与默认颜色不匹配
具体修复步骤:
- 初始化
currentColors数组,存储每一帧每个条形的实际颜色 - 在
draw函数中先计算所有条形的avg值,再按顺序设置颜色(确保前一个条形的颜色已确定) - 对
avg>0的条形,继承前一个条形的当前颜色;第一个条形固定白色;avg<=0的条形使用默认颜色
修复后的完整visualize函数代码:
function visualize(buffer) { const source = audioCtx.createBufferSource(); source.buffer = buffer; const analyser = audioCtx.createAnalyser(); source.connect(analyser); analyser.connect(audioCtx.destination); analyser.fftSize = 512; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); // 初始化当前颜色数组,默认使用rev的颜色 const currentColors = [...rev]; source.start(); draw(); function draw() { requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvasCtx.clearRect(0, 0, canvas.width, canvas.height); const barWidth = Math.floor(canvas.width / 5); const sensitivity = 1; const frequencyBands = [ { from: 0, to: Math.floor(bufferLength * 0.2) }, { from: Math.floor(bufferLength * 0.2), to: Math.floor(bufferLength * 0.4) }, { from: Math.floor(bufferLength * 0.4), to: Math.floor(bufferLength * 0.6) }, { from: Math.floor(bufferLength * 0.6), to: Math.floor(bufferLength * 0.8) }, { from: Math.floor(bufferLength * 0.8), to: bufferLength } ]; // 先计算所有条形的avg值,避免绘制时顺序影响颜色赋值 const avgValues = []; for (let i = 0; i < frequencyBands.length; i++) { const { from, to } = frequencyBands[i]; const sum = dataArray.slice(from, to).reduce((acc, val) => acc + val, 0); avgValues.push(sum / (to - from)); } let x = 0; for (let i = 0; i < frequencyBands.length; i++) { const avg = avgValues[i]; let colorValue; if (i === 0) { // 第一个条形固定白色 colorValue = "rgb(255, 255, 255)"; } else if (avg > 0) { // 继承前一个条形的当前帧颜色 colorValue = currentColors[i - 1]; } else { // 使用默认颜色 colorValue = rev[i]; } // 更新当前颜色数组,供后续条形使用 currentColors[i] = colorValue; canvasCtx.fillStyle = colorValue; // 修正barHeight计算,避免始终填满画布 const barHeight = Math.max((avg / 255) * canvas.height * sensitivity, 1); canvasCtx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); x += barWidth; } } }
关键修复点说明:
- 新增
avgValues数组先计算所有条形的平均频率,确保颜色赋值时前一个条形的颜色已确定 currentColors数组存储每一帧的实际颜色,保证后续条形能正确继承前一个的颜色- 修正
barHeight计算逻辑,避免出现条形始终填满画布的问题 - 颜色继承逻辑改为使用前一个条形的当前帧颜色,而非默认颜色
内容的提问来源于stack exchange,提问作者Jony
相关产品推荐
相关产品推荐

