动画“摇晃的画布"就像在 Discord 的登录页面中一样? [英] Animating a "Wobbly Canvas" like in Discord's Login page?

查看:26
本文介绍了动画“摇晃的画布"就像在 Discord 的登录页面中一样?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为参考,我指的是

它有许多非常酷的效果,点和(较暗的阴影)随着鼠标移动,但我对摇摆边缘"效果更感兴趣,在较小程度上对快速摇摆/在页面加载时缩放"(加载时在画布中缩放会产生类似的,如果不是更便宜"的效果).

不幸的是,我不能以 MCVE 的方式制作太多东西,因为我不确定从哪里开始.我尝试挖掘 Discord 的资产,但我对 Webpack 不够熟悉,无法确定发生了什么.

我能够在动画波浪/摆动"上挖掘的所有内容都是 CSS 驱动的 SVG 或剪辑路径边框,我想制作一些更有机的东西.

解决方案

非常有趣的问题.我已将 blob 缩小,以便在下面的预览中可见.

这是一个更大尺寸的代码笔.

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);类 Blob {构造函数(){this.wobbleIncrement = 0;//使用它来改变 blob 的大小this.radius = 500;//将此视为细节级别//`bezierSkin` 中的连接数this.segments = 12;this.step = HALF_PI/this.segments;this.anchors = [];this.radii = [];this.thetaOff = [];常量凹凸半径 = 100;const halfBumpRadius = 凹凸半径/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);}这个.theta = 0;this.thetaRamp = 0;this.thetaRampDest = 12;this.rampDamp = 25;}更新() {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] * 正弦;const theta = this.step * i;const x = rad * Math.sin(theta);const y = rad * Math.cos(theta);this.anchors.push(x, y);}c.save();c.translate(-10, -10);c.规模(规模,规模);c.fillStyle = "蓝色";c.beginPath();c.moveTo(0, 0);bezierSkin(this.anchors, false);c.lineTo(0, 0);c.fill();c.恢复();}}const blob = new Blob();函数循环(){c.clearRect(0, 0, canvas.width, canvas.height);blob.update();window.requestAnimationFrame(loop);}环形();//xy 坐标数组,闭合布尔值函数 bezierSkin(bez, closed = true) {const avg = calcAvgs(bez);const leng = bez.length;如果(关闭){c.moveTo(avg[0], avg[1]);为(让我= 2;我<长度;我+= 2){让 n = i + 1;c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);}c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);} 别的 {c.moveTo(bez[0], bez[1]);c.lineTo(avg[0], avg[1]);对于(让我= 2;我

这里发生了很多事情.为了创建这种效果,您需要很好地了解如何定义二次贝塞尔曲线.一旦你有了它,有一个古老的技巧,多年来我已经使用了很多次.要生成平滑的链接二次贝塞尔曲线,请定义一个点列表并计算它们的平均值.然后将这些点用作控制点,将新的平均点用作锚点.请参阅 bezierSkincalcAvgs 函数.

有了绘制平滑贝塞尔曲线的能力后,剩下的就是将点定位在弧形中,然后为它们设置动画.为此,我们使用一点数学:

x = 半径 * sin(theta)y = 半径 * cos(theta)

将极坐标转换为笛卡尔坐标.其中theta 是圆[0 - 2pi] 圆周上的角度.

至于动画,这里还有很多事情要做 - 我会看看这个周末是否有更多时间来更新答案,提供更多细节和信息,但希望这会有所帮助.

For reference, I'm talking about the dark-gray space in the upper left of Discord's Login Page. For anyone who can't access that link, here's a screenshot:

It has a number of effects that are really cool, the dots and (darker shadows) move with the mouse, but I'm more interested in the "wobbly edge" effect, and to a lesser extent the "fast wobble/scale in" on page load (scaling in the canvas on load would give a similar, if not "cheaper" effect).

Unfortunately, I can't produce much in the way of a MCVE, because I'm not really sure where to start. I tried digging through Discord's assets, but I'm not familiar enough to Webpack to be able to determine what's going on.

Everything I've been able to dig up on "animated wave/wobble" is CSS powered SVG or clip-path borders, I'd like to produce something a bit more organic.

解决方案

Very interesting problem. I've scaled the blob down so it is visible in the preview below.

Here is a codepen as well at a larger size.

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;
    // use this to change the size of the blob
    this.radius = 500;
    // think of this as detail level
    // number of conections in the `bezierSkin`
    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;
      const x = rad * Math.sin(theta);
      const y = rad * Math.cos(theta);
      this.anchors.push(x, y);
    }

    c.save();
    c.translate(-10, -10);
    c.scale(SCALE, SCALE);
    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]);
  }
}

// create anchor points by averaging the control points
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;
}

There are lots of things going on here. In order to create this effect you need a good working knowledge of how quadratic bezier curves are defined. Once you have that, there is an old trick that I've used many many times over the years. To generate smooth linked quadratic bezier curves, define a list of points and calculate their averages. Then use the points as control points and the new averaged points as anchor points. See the bezierSkin and calcAvgs functions.

With the ability to draw smooth bezier curves, the rest is about positioning the points in an arc and then animating them. For this we use a little math:

x = radius * sin(theta)
y = radius * cos(theta)

That converts polar to cartesian coordinates. Where theta is the angle on the circumference of a circle [0 - 2pi].

As for the animation, there is a good deal more going on here - I'll see if I have some more time this weekend to update the answer with more details and info, but hopefully this will be helpful.

这篇关于动画“摇晃的画布"就像在 Discord 的登录页面中一样?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆