You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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;
  }
}

修复方案

问题出在两个关键点:

  1. 原逻辑中直接取前一个条形的默认颜色,而非当前帧中前一个条形的实际颜色
  2. 修改后的代码里currentColors未初始化,且直接用avg作为RGB值,范围与默认颜色不匹配

具体修复步骤:

  1. 初始化currentColors数组,存储每一帧每个条形的实际颜色
  2. 在draw函数中先计算所有条形的avg值,再按顺序设置颜色(确保前一个条形的颜色已确定)
  3. 对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 18:15:55