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

如何将旧版Discord登录页的Corner blob动画移动到页面右侧

Discord风格边角Blob动画原理说明与右侧适配方案

我前阵子找到一个实现了与旧版Discord登录页效果相近的Corner blob动画的实现方案,下面针对该动画的运行原理,以及你提到的调整至页面右侧展示的需求做对应说明:

动画运行原理

  • 整体基于Canvas 2D API实现,通过requestAnimationFrameAPI驱动逐帧渲染,实现流畅的动画效果
  • 核心Blob类维护了曲面的所有锚点参数:初始定义了固定半径的四分之一圆作为基准面,给每个分段锚点分配了随机的波动幅度和初始相位,保证晃动效果自然无规律
  • 每帧更新时,每个锚点的实际半径会基于正弦函数做周期性变化,叠加不同的初始相位偏移后,整体呈现不规则的果冻晃动效果
  • 绘制阶段通过bezierSkin函数计算相邻锚点的平均值作为二次贝塞尔曲线的控制点,对边缘做平滑过渡,最终填充得到边缘柔和的流体效果

调整至页面右侧展示的实现

你只需要修改原始代码中Blob.update方法内的坐标平移参数即可适配右上角展示,修改后的完整可运行代码如下:

const SCALE = 0.25;
const TWO_PI = Math.PI * 2;
const HALF_PI = Math.PI / 2;
const canvas = document.createElement("canvas");
const c = canvas.getContext("2d");

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

class Blob {
  constructor() {
    this.wobbleIncrement = 0;
    // 可调整Blob大小
    this.radius = 500;
    // 曲面细节等级,数值越高边缘越细腻
    this.segments = 12;
    this.step = HALF_PI / this.segments;
    this.anchors = [];
    this.radii = [];
    this.thetaOff = [];

    const bumpRadius = 100;
    const halfBumpRadius = bumpRadius / 2;

    for (let i = 0; i < this.segments + 2; i++) {
      this.anchors.push(0, 0);
      this.radii.push(Math.random() * bumpRadius - halfBumpRadius);
      this.thetaOff.push(Math.random() * TWO_PI);
    }

    this.theta = 0;
    this.thetaRamp = 0;
    this.thetaRampDest = 12;
    this.rampDamp = 25;
  }
  update() {
    this.thetaRamp += (this.thetaRampDest - this.thetaRamp) / this.rampDamp;
    this.theta += 0.03;

    this.anchors = [0, this.radius];
    for (let i = 0; i <= this.segments + 2; i++) {
      const sine = Math.sin(this.thetaOff[i] + this.theta + this.thetaRamp);
      const rad = this.radius + this.radii[i] * sine;
      const theta = this.step * i;
      // 适配右侧展示,x坐标取反
      const x = -rad * Math.sin(theta);
      const y = rad * Math.cos(theta);
      this.anchors.push(x, y);
    }

    c.save();
    // 修改平移坐标,适配右上角展示
    c.translate(window.innerWidth / SCALE + 10, -10);
    c.scale(SCALE, SCALE);
    // 可调整Blob填充颜色
    c.fillStyle = "blue";
    c.beginPath();
    c.moveTo(0, 0);
    bezierSkin(this.anchors, false);
    c.lineTo(0, 0);
    c.fill();
    c.restore();
  }
}

const blob = new Blob();

function loop() {
  c.clearRect(0, 0, canvas.width, canvas.height);
  blob.update();
  window.requestAnimationFrame(loop);
}
loop();

// array of xy coords, closed boolean
function bezierSkin(bez, closed = true) {
  const avg = calcAvgs(bez);
  const leng = bez.length;

  if (closed) {
    c.moveTo(avg[0], avg[1]);
    for (let i = 2; i < leng; i += 2) {
      let n = i + 1;
      c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
    }
    c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);
  } else {
    c.moveTo(bez[0], bez[1]);
    c.lineTo(avg[0], avg[1]);
    for (let i = 2; i < leng - 2; i += 2) {
      let n = i + 1;
      c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
    }
    c.lineTo(bez[leng - 2], bez[leng - 1]);
  }
}

// 计算相邻锚点平均值生成贝塞尔曲线平滑控制点
function calcAvgs(p) {
  const avg = [];
  const leng = p.length;
  let prev;

  for (let i = 2; i < leng; i++) {
    prev = i - 2;
    avg.push((p[prev] + p[i]) / 2);
  }
  // close
  avg.push((p[0] + p[leng - 2]) / 2, (p[1] + p[leng - 1]) / 2);
  return avg;
}

内容的提问来源于stack exchange,提问作者Cohen S Erickson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 15:18:03