如何为多个 Canvas 元素独立启用水彩动画效果

梦伟小哥_3829

梦伟小哥_3829

2026-11-11

361人浏览

原创

如何为多个 Canvas 元素独立启用水彩动画效果

本文详解如何将单例 Canvas 水彩动画脚本改造为支持多个 .see-more 画布的可复用方案,通过 querySelectorAll + 闭包隔离 + 参数化渲染,确保每个 canvas 拥有独立上下文、动画状态与生命周期。

本文详解如何将单例 canvas 水彩动画脚本改造为支持多个 `.see-more` 画布的可复用方案,通过 `queryselectorall` + 闭包隔离 + 参数化渲染,确保每个 canvas 拥有独立上下文、动画状态与生命周期。

在 Web 开发中,将一段原本为单个 DOM 元素编写的 JavaScript 动画逻辑(如 Canvas 水彩扩散效果)扩展到多个同类元素上,是一个常见但易出错的需求。原始代码使用 document.querySelector('.see-more') 仅获取首个匹配元素,因此动画始终只作用于第一个 —— 这正是问题根源:Canvas 动画状态(如 watercolors 数组、frameId、绘图上下文 cx)被全局共享,无法区分不同画布实例。

要实现多画布并行渲染,核心原则是:每个 canvas 必须拥有独立的动画循环、独立的绘图上下文、独立的粒子系统(Watercolor 实例集合)和独立的时间上下文。以下是关键改造步骤:

✅ 1. 获取全部画布,并为每个实例单独初始化

const canvases = document.querySelectorAll('.see-more');
canvases.forEach(canvas => {
  const cx = canvas.getContext('2d');
  cx.globalCompositeOperation = 'source-over';
  cx.imageSmoothingEnabled = true;
  cx.imageSmoothingQuality = 'low';
  start(canvas); // 启动该 canvas 的专属动画循环
});

✅ 2. 将动画逻辑封装为「画布感知」函数

所有依赖 canvas 的操作(创建渐变、实例化 Watercolor、读取尺寸、渲染)必须显式接收 canvas 参数,避免闭包污染或上下文错位:

// ✅ 正确:按需为指定 canvas 创建渐变
const getGradients = (canvas) => {
  const cx = canvas.getContext('2d');
  return palette.map(([r, g, b]) => {
    const gradient = cx.createRadialGradient(0, 0, 1, 0, 0, 50);
    gradient.addColorStop(0, `rgba(${r},${g},${b},1)`);
    gradient.addColorStop(1, `rgba(${r},${g},${b},0)`);
    return gradient;
  });
};

// ✅ Watercolor 构造器接收 canvas,确保坐标与尺寸基于目标画布
class Watercolor {
  constructor(time, canvas) {
    const gradientsArr = getGradients(canvas);
    const colorIndex = Math.floor(Math.random() * palette.length);
    this.color = gradientsArr[colorIndex];
    this.radius = 50;
    this.x = Math.random() * canvas.width;     // ← 基于当前 canvas
    this.y = Math.random() * canvas.height;    // ← 基于当前 canvas
    this.dx = Math.random() * 2 - 1;
    this.dy = Math.random() * 2 - 1;
    this.startTime = time;
    this.currentTime = time;
    this.duration = Math.ceil(Math.random() * 5000);
  }
  // ... update() 和 render() 保持不变,但 render 中需传入对应 cx
}

✅ 3. 使用 bind() 或箭头函数隔离每帧动画作用域

requestAnimationFrame 回调必须绑定到具体 canvas,防止多实例间 frameId 和 watercolors 被覆盖:

function start(canvas) {
  // ✅ 每个 canvas 拥有独立 frameId(注意:此处需为每个 canvas 维护自己的 frameId)
  // → 更佳实践:将动画状态封装进对象,见下方优化建议
  window.requestAnimationFrame(time => frame(canvas, time));
}

function frame(canvas, time) {
  const cx = canvas.getContext('2d');

  // 自适应缩放 canvas 像素尺寸(关键!避免模糊/拉伸)
  const multiplier = 0.25;
  const w = Math.floor(canvas.clientWidth * multiplier);
  const h = Math.floor(canvas.clientHeight * multiplier);
  if (canvas.width !== w) canvas.width = w;
  if (canvas.height !== h) canvas.height = h;

  // ✅ 每个 canvas 应维护自己的 watercolors 数组(见下方重构)
  // 此处仅为示意;实际需为每个 canvas 创建独立数组

  // 渲染逻辑(略)...
  window.requestAnimationFrame(time => frame(canvas, time));
}

⚠️ 重要注意事项与最佳实践

  • ❌ 避免全局共享状态:原代码中 watercolors = [] 和 frameId 是全局变量,多 canvas 会互相干扰。✅ 正确做法是为每个 canvas 创建独立状态对象:

    Gigma Design Canvas
    Gigma Design Canvas

    AI驱动的设计工具,拥有真实可编辑画布和完整的MCP控制,可创建、编辑并导出社交媒体图形、缩略图、横幅、卡片及批量处理。

    下载
    const canvasStates = new Map();
    canvases.forEach(canvas => {
      canvasStates.set(canvas, {
        watercolors: [],
        frameId: null,
        lastTime: 0
      });
      start(canvas);
    });
  • ? CSS 样式需作用于每个 canvas:确保 .see-more { width: 100%; height: 100%; } 在父容器具有明确宽高(如 position: relative),否则 clientWidth/clientHeight 可能为 0。

  • ⚡ 性能提示:若页面 canvas 数量较多(>5),建议添加节流或暂停非可视区域 canvas 的 requestAnimationFrame,提升性能。

  • ? 调试技巧:在 frame() 中添加 console.log('Rendering on:', canvas.parentElement?.closest('.see-more-wrap')) 可快速验证是否命中预期元素。

通过以上结构化改造,你不仅能解决「只生效于第一个画布」的问题,更能构建出可扩展、易维护、符合现代前端模块化思维的 Canvas 动画系统。每个 .see-more 将真正拥有独立生命,互不干扰,协同呈现统一视觉风格。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

canva

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

5786

5

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

2023.07.04

1389

3

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

2023.07.04

5704

6

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

2023.09.01

1060

4

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

2016

5

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

2023.09.04

3965

4

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

2023.09.12

5213

6

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

2023.09.20

3420

5

java访问控制修饰符介绍
java访问控制修饰符介绍

java访问控制修饰符有四种,分别是public、protected、private、默认访问修饰符。详细介绍:1、public,public是最宽松的访问控制修饰符,被修饰的类、方法和变量可以被任何其他类访问,当一个类、方法或变量被声明为public时,它们可以在任何地方被访问,无论是同一个包中的类还是不同包中的类;2、protected修饰符等等。

2023.09.20

1108

7

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Canva使用教程
Canva使用教程

共0课时 | 0人学习