Skip to content Skip to sidebar Skip to footer

Animating A "wobbly Canvas" Like In Discord's Login Page?

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 effe

Solution 1:

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.

constSCALE = 0.25;
constTWO_PI = Math.PI * 2;
constHALF_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);

classBlob {
  constructor() {
    this.wobbleIncrement = 0;
    // use this to change the size of the blobthis.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 = newBlob();

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

// array of xy coords, closed booleanfunctionbezierSkin(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 pointsfunctioncalcAvgs(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.

Solution 2:

The animation runs on a canvas and it is a simple bezier curve animation.

For organic feel, you should look at perlin noise, that was introduced when developing original Tron video FX.

You can find a good guide to understand perlin noise here.

In the example I've used https://github.com/josephg/noisejs

var c = $('canvas').get(0).getContext('2d');
var simplex = newSimplexNoise();
var t = 0;

functioninit() {
	window.requestAnimationFrame(draw);
}

functiondraw() {
c.clearRect(0, 0, 600, 300);
c.strokeStyle="blue"; 
c.moveTo(100,100);
c.lineTo(300,100);
c.stroke();                

// Draw a Bézier curve by using the same line cooridinates.
c.beginPath();              
c.lineWidth="3";
c.strokeStyle="black"; 
c.moveTo(100,100);
c.bezierCurveTo((simplex.noise2D(t,t)+1)*200,(simplex.noise2D(t,t)+1)*200,(simplex.noise2D(t,t)+1)*200,0,300,100);
c.stroke();

// draw reference points
c.fillRect(100-5,100-5,10,10);
c.fillRect(200-5,200-5,10,10);
c.fillRect(200-5,0-5,10,10);
c.fillRect(300-5,100-5,10,10);
t+=0.001;
window.requestAnimationFrame(draw);
}

init();
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.js"></script><canvaswidth="600"height="300"></canvas>

Note: further investigation on Discord source code, I've pointed out that's is using https://www.npm.red/~epistemex libraries. Epistemex NPM packages are still online, while GitHub repos and profile does not exists anymore.

Note 2: Another approach could be relying on physics libraries like this demo, but it can be an overkill, if you just need a single effect.

Post a Comment for "Animating A "wobbly Canvas" Like In Discord's Login Page?"