如何将旧版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
相关产品推荐
相关产品推荐

