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

如何将Canvas熔岩灯动画的随机调色板替换为预定义调色板?

问题:自定义调色板后Canvas熔岩动画白屏

基于Vanilla JS+HTML Canvas实现的熔岩/等离子画布动画,原代码通过随机生成调色板实现效果,修改为预定义调色板(仅含3种主色及渐变)后页面显示白屏。以下是修改后的代码片段及问题分析:

修改后的代码片段

// color helpers
const interpolate = (c1, c2, f) => {
  return {
    r: Math.floor(c1.r + (c2.r - c1.r) * f),
    g: Math.floor(c1.g + (c2.g - c1.g) * f),
    b: Math.floor(c1.b + (c2.b - c1.b) * f)
  };
};

// 自定义调色板(仅5个颜色)
const palette1 = [
  { r: 255, g: 255, b: 255 },
  { r: 255, g: 0, b: 0 },
  { r: 0, g: 255, b: 0 },
  { r: 0, g: 0, b: 255 },
  { r: 255, g: 255, b: 0 }
];

const palette2 = [
  { r: 0, g: 0, b: 0 },
  { r: 255, g: 255, b: 255 },
  { r: 255, g: 0, b: 0 },
  { r: 0, g: 255, b: 0 },
  { r: 0, g: 0, b: 255 }
];

// offsets for moving height maps
let dx1 = 0;
let dy1 = 0;

let dx2 = 0;
let dy2 = 0;

// adjust height maps offsets
const moveHeightMaps = (t) => {
  dx1 = Math.floor(
    (((Math.cos(t * 0.0002 + 0.4 + Math.PI) + 1) / 2) * mapSize) / 2
  );
  dy1 = Math.floor((((Math.cos(t * 0.0003 - 0.1) + 1) / 2) * mapSize) / 2);
  dx2 = Math.floor((((Math.cos(t * -0.0002 + 1.2) + 1) / 2) * mapSize) / 2);
  dy2 = Math.floor(
    (((Math.cos(t * -0.0003 - 0.8 + Math.PI) + 1) / 2) * mapSize) / 2
  );
};

// two palettes we interpolate between
const palettes = [palette1, palette2];

// current palette is established during animation
let palette = [];

// stores whether we're interpolating colors
// from palette 0 -> 1 (1) or 1 -> 0 (-1)
let prevDirection = 1;

const updatePalette = (t) => {
  const timeScale = 0.0005;
  const x = t * timeScale;

  // normalized value 0..1 used to interpolate palette colors
  const inter = (Math.cos(x) + 1) / 2;

  const direction = -Math.sin(x) >= 0 ? 1 : -1;
  if (prevDirection != direction) {
    prevDirection = direction;
    if (direction == -1) {
      palettes[0] = makeRandomPalette(); // 已删除的函数,此处报错
    } else {
      palettes[1] = makeRandomPalette(); // 已删除的函数,此处报错
    }
  }

  // create interpolated palette for current frame
  for (let i = 0; i < 256; i++) {
    palette[i] = interpolate(palettes[0][i], palettes[1][i], inter); // palettes[0][i]在i>=5时为undefined
  }
};

const updateImageData = () => {
  for (let u = 0; u < imgSize; u++) {
    for (let v = 0; v < imgSize; v++) {
      const i = (u + dy1) * mapSize + (v + dx1);
      const k = (u + dy2) * mapSize + (v + dx2);
      const j = u * imgSize * 4 + v * 4;

      let h = heightMap1[i] + heightMap2[k];
      let c = palette[h]; // h是0-255的数值,palette对应位置可能为undefined

      image.data[j] = c.r;
      image.data[j + 1] = c.g;
      image.data[j + 2] = c.b;
    }
  }
};

// helper to create a linear gradient palette
const linearGradient = (c1, c2) => {
  const g = [];
  for (let i = 0; i < 256; i++) {
    const f = i / 255;
    g[i] = interpolate(c1, c2, f);
  }
  return g;
};

const tick = (time) => {
  moveHeightMaps(time);
  updatePalette(time);
  updateImageData();

  c.putImageData(image, 0, 0);

  requestAnimationFrame(tick);
};

问题排查与修复方向

1. 未移除已删除函数的调用

updatePalette函数中仍在调用已删除的makeRandomPalette(),这会直接抛出ReferenceError,导致后续动画逻辑无法执行。

  • 修复:删除这段切换方向时重新生成调色板的代码,因为我们使用预定义调色板,不需要随机替换。

2. 自定义调色板长度不匹配

原代码的调色板是256个颜色的数组(对应高度值0-255),但你定义的palette1和palette2只有5个颜色。在updatePalette循环256次时,palettes[0][i]和palettes[1][i]在i>=5时都是undefined,调用interpolate时会访问undefined.r,导致报错,最终palette数组大部分位置为undefined,设置像素数据时出现无效值,显示白屏。

  • 修复:根据你的3种主色生成256长度的渐变调色板。比如可以用linearGradient函数扩展多段渐变:
    // 生成包含3种主色的256色渐变调色板
    const createMultiGradientPalette = (colors) => {
      const palette = [];
      const segmentLength = Math.floor(256 / (colors.length - 1));
      for (let i = 0; i < colors.length - 1; i++) {
        const start = i * segmentLength;
        const end = Math.min((i + 1) * segmentLength, 256);
        const gradient = linearGradient(colors[i], colors[i + 1]);
        for (let j = start; j < end; j++) {
          palette[j] = gradient[j - start];
        }
      }
      // 补充剩余的颜色(如果256不能被段数整除)
      for (let j = palette.length; j < 256; j++) {
        palette[j] = palette[palette.length - 1];
      }
      return palette;
    };
    
    // 定义3种主色的调色板
    const baseColors1 = [
      { r: 255, g: 0, b: 0 },
      { r: 0, g: 255, b: 0 },
      { r: 0, g: 0, b: 255 }
    ];
    const baseColors2 = [
      { r: 0, g: 0, b: 0 },
      { r: 255, g: 255, b: 255 },
      { r: 255, g: 255, b: 0 }
    ];
    
    const palette1 = createMultiGradientPalette(baseColors1);
    const palette2 = createMultiGradientPalette(baseColors2);
    

3. 像素Alpha通道未设置

原代码中没有设置像素的Alpha通道(image.data[j+3]),虽然默认是255,但如果出现异常值可能导致透明,建议显式设置:

  • 修复:在updateImageData中添加image.data[j + 3] = 255;

内容的提问来源于stack exchange,提问作者nkwef3

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:07:50