Particle Logo

Logo 粒子动画的原理

Logo 粒子动画先把 Logo 画到屏幕外,保留一个网格上的可见像素作为目标点,再让一个粒子在每一帧里走向各自的目标点。这里按 Particle Logo 的做法讲四个步骤、9 种效果各自怎么运动,并附一段能自己运行的 Canvas 示例。

更新于

怎样把 Logo 变成粒子?

  1. 采样。把 Logo 画到屏幕外的画布上,读出像素,每隔 N 个像素取一个属于 Logo 的像素。每个取到的像素就是一个目标点。
  2. 分配。为每个目标点创建一个粒子,给它一个随机的起始位置。
  3. 运动。每一帧让每个粒子朝目标走完剩余距离的一小部分。决定路径的规则不同,就是不同的效果。
  4. 绘制。清空同一块画布,把每个粒子画成小方块或小圆点。

第 1 步:点是怎么采样的?

默认步长 4 时,我们的三张测试图分别采样出 589 个点(白色四字母字标)、4,139 个点(五种颜色的几何图形)和 7,082 个点(Particle Logo 自己的标志)。工坊会在预览旁边显示点数。代码导出最多保留 1,600 个均匀分布的点,JSON 导出保留全部。

第 2 步:粒子从哪里出发、到哪里去?

每个粒子有一个画布内的随机起点和一个目标点。效果的代码每一帧拿到一个 0 到 100 的进度值,据此算出粒子此刻应该去哪里,再让粒子朝那里走一部分,系数在 0.09 到 0.28 之间,越大越干脆。

第 3 步:9 种效果各自怎么运动?

下表对应 src/engines/morphParticle.ts 里每种效果的规则。“偏移”指叠加在目标位置上的额外位移。

效果粒子怎么动
量子凝聚态每个粒子沿直线从起点走向目标,前 80% 的时间匀速,之后略微冲过头再回落定型。
莫比乌斯拓扑光流同样沿直线走向目标,再加一道沿点序传播的正弦波,把粒子沿离开中心的方向推出推回,最多 4.5 像素,随 Logo 成形逐渐减弱。
星系引力坍缩每个粒子的起点在目标位置绕中心转 2.25 圈、再向外推出画布较长边的 65%;随进度推进这两个偏移都收到 0,粒子就一边转一边向内收紧。
全息光栅断层扫描一条扫描线从画面顶部移到底部;目标在线上方的粒子飞到目标位置,其余粒子留在起点附近,透明度很低。
扩散降噪重构每个粒子朝目标位置移动,同时叠加一个最多 ±24 像素的随机偏移,进度到 100 时偏移收到 0。
超导磁通涟漪一圈波环从中心向外扩散。离波环 60 像素以内的粒子被沿离开中心的方向推开最多 14 像素,并以满亮度绘制;所有粒子同时走向目标。
纳米蜂群智能组装粒子按序号分成四组。每组绕中心转圈,半径是画布较短边的 45%,随进度最多收窄 80%,粒子再平滑地从轨道上落到各自的目标。
引力弹弓时间逆流前 38% 的时间里,每个粒子比目标位置更远离中心 85 像素;之后猛地收向目标,带一段最多 14 像素、逐渐衰减的抖动。
克拉尼声波共振每个粒子朝目标位置移动,同时叠加一个随机偏移;偏移大小跟随画面上的二维驻波纹理(正弦乘余弦),随进度收到 0,最大 ±18 像素。

第 4 步:怎么画出来?

一个能直接运行的 Canvas 示例

下面这段代码只用了上面的前三步,没有任何依赖:

<canvas id="logo" width="480" height="480"></canvas>
<script>
  const canvas = document.getElementById('logo');
  const ctx = canvas.getContext('2d');
  const img = new Image();
  img.onload = start;
  img.src = 'logo.png'; // 必须同源;用背景透明的 PNG 或 SVG

  function start() {
    // 1. 把 Logo 画到屏幕外的画布上,读取像素
    const off = document.createElement('canvas');
    off.width = canvas.width;
    off.height = canvas.height;
    const octx = off.getContext('2d', { willReadFrequently: true });
    const scale = Math.min(off.width / img.width, off.height / img.height) * 0.8;
    const w = img.width * scale, h = img.height * scale;
    octx.drawImage(img, (off.width - w) / 2, (off.height - h) / 2, w, h);
    const data = octx.getImageData(0, 0, off.width, off.height).data;

    // 2. 每隔 4 个像素取一个不透明的像素,作为目标位置
    const step = 4;
    const particles = [];
    for (let y = 0; y < off.height; y += step) {
      for (let x = 0; x < off.width; x += step) {
        const i = (y * off.width + x) * 4;
        if (data[i + 3] > 128) {
          particles.push({
            x: Math.random() * canvas.width,   // 随机起点
            y: Math.random() * canvas.height,
            tx: x, ty: y,                       // 在 Logo 上的目标位置
            color: 'rgb(' + data[i] + ',' + data[i + 1] + ',' + data[i + 2] + ')'
          });
        }
      }
    }

    // 3. 每一帧让每个粒子朝目标走完剩余距离的一小部分
    function frame() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for (const p of particles) {
        p.x += (p.tx - p.x) * 0.06;
        p.y += (p.ty - p.y) * 0.06;
        ctx.fillStyle = p.color;
        ctx.fillRect(p.x - 1, p.y - 1, 2, 2);
      }
      requestAnimationFrame(frame);
    }
    frame();
  }
</script>

常见问题

Particle Logo 用 WebGL 或 Three.js 吗?

不用。导出的文件用浏览器自带的 2D 画布接口绘制,运行时没有任何依赖。

导出的文件里最多有多少个粒子?

最多 1,600 个。工坊里采样出的点可以更多:默认步长下,我们的三张测试图是 589 到 7,082 个。

为什么采样出来的 Logo 有缺口?

通常是步长太大,或阈值不合适。调小步长可以采到更多点,再用阈值和反相开关调整哪些像素算 Logo;工坊的“点云透视”可以看到实际采到了哪些点。

想直接用现成的组件,而不是自己写?

参见接入教程:HTML、Vue 3 和 React 组件都已经带有九种效果、颜色配置、鼠标交互和 progress 参数。

进入工坊

其他教程