)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Fermat’s Spiral

  1. const canvas = document.body.appendChild(document.createElement('canvas'));
  2. const c = canvas.getContext('2d');
  3.  
  4. function resize() {
  5.   canvas.width = window.innerWidth;
  6.   canvas.height = window.innerHeight;
  7.   draw();
  8. }
  9.  
  10. function draw() {
  11.   c.clearRect(0, 0, canvas.width, canvas.height);
  12.   c.fillStyle = 'blue';
  13.  
  14.   const iter = 300;
  15.   const halfWidth = window.innerWidth / 2;
  16.   const halfHeight = window.innerHeight / 2;
  17.   let rad = 0,
  18.     theta = 0,
  19.     scale = 20 * Math.min(window.innerWidth, window.innerHeight) * 0.006,
  20.     x,
  21.     y;
  22.  
  23.   c.save();
  24.   c.translate(halfWidth, halfHeight);
  25.  
  26.   for (let i = 0; i < iter; i++) {
  27.     rad = Math.sqrt(theta) * scale;
  28.     x = rad * Math.cos(theta);
  29.     y = rad * Math.sin(theta);
  30.     c.fillRect(x, y, 2, 2);
  31.     c.fillRect(-x, -y, 5, 5);
  32.  
  33.     theta += 0.05;
  34.   }
  35.   c.restore();
  36. }
  37.  
  38. resize();
  39. window.addEventListener('resize', resize);

Draw Fermat’s spiral…

Canvas Spiraling Things

  1. const canvas = document.createElement('canvas');
  2. const c = canvas.getContext('2d');
  3.  
  4. document.body.appendChild(canvas);
  5. document.body.style.margin = 0;
  6.  
  7. function resize() {
  8.   canvas.width = innerWidth;
  9.   canvas.height = innerHeight;
  10.   c.fillStyle = '#000';
  11.   c.fillRect(0, 0, canvas.width, canvas.height);
  12. }
  13. resize();
  14. addEventListener('resize', resize);
  15.  
  16. const cols = ['#555', 'white', 'gray', '#a4c3eb', '#75879e'];
  17.  
  18. const getCol = () => cols[Math.floor(Math.random() * cols.length)];
  19.  
  20. function rect() {
  21.   let x = innerWidth / 2;
  22.   let y = innerHeight / 2;
  23.   let col = getCol();
  24.   let width = 10;
  25.   let height = 10;
  26.   let halfWidth = width / 2;
  27.   let halfHeight = height / 2;
  28.   let alpha = 0.15 + Math.random() * 0.5;
  29.   let vx = 0;
  30.   let vy = 0;
  31.   let rot = 0;
  32.   let rotInc = Math.random() * 0.1 - 0.05;
  33.  
  34.   function change() {
  35.     vx += (Math.random() * 1 - 0.5) / 2;
  36.     vy += (Math.random() * 1 - 0.2) / 2;
  37.   }
  38.   change();
  39.  
  40.   function check() {
  41.     if (x < 0) x = innerWidth;
  42.     if (y < 0) y = innerHeight;
  43.     if (x > innerWidth) x = 0;
  44.     if (y > innerHeight) y = 0;
  45.   }
  46.  
  47.   const radius = 20 + Math.random() * 80;
  48.   const verts = [];
  49.   const NUM = 200;
  50.   const off = Math.random() * 5;
  51.   const cols = [];
  52.   for (var i = 0; i < NUM; i += 2) {
  53.     let xp = Math.random() * 10 - 5;
  54.     let yp = Math.random() * 10 - 5;
  55.     let zp = Math.random() * 10 - 5;
  56.     let dist = Math.sqrt(xp * xp + yp * yp + zp * zp);
  57.     // normalize and scale x,y,z
  58.     verts[i] = (xp / dist) * radius;
  59.     verts[i + 1] = (yp / dist) * radius;
  60.     cols.push(i % 255);
  61.   }
  62.  
  63.   return () => {
  64.     if (Math.random() < 0.13) {
  65.       change();
  66.     }
  67.  
  68.     vx *= 0.99;
  69.     vy *= 0.99;
  70.  
  71.     x += vx;
  72.     y += vy;
  73.  
  74.     check();
  75.  
  76.     c.globalAlpha = alpha;
  77.  
  78.     c.save();
  79.  
  80.     rot += rotInc;
  81.     c.translate(x, y);
  82.     c.rotate(rot);
  83.  
  84.     for (var i = 0; i < NUM; i += 2) {
  85.       const channel = cols[i];
  86.       c.fillStyle = `rgb(${channel}, ${channel}, ${channel})`;
  87.       c.fillRect(verts[i], verts[i + 1], 2, 2);
  88.     }
  89.     c.restore();
  90.   };
  91. }
  92.  
  93. let rects = [];
  94. let NUM = 20;
  95.  
  96. for (let i = 0; i < NUM; i++) {
  97.   rects.push(rect());
  98. }
  99.  
  100. rects[0]();
  101.  
  102. function loop() {
  103.   c.globalCompositeOperation = 'source-over';
  104.   c.globalAlpha = 0.025;
  105.   c.fillStyle = '#000';
  106.   c.fillRect(0, 0, canvas.width, canvas.height);
  107.  
  108.   for (let i = 0; i < NUM; i++) {
  109.     rects[i]();
  110.   }
  111.  
  112.   requestAnimationFrame(loop);
  113. }
  114.  
  115. loop();

Wobbling Discord Blob

  1. const SCALE = 0.25;
  2. const TWO_PI = Math.PI * 2;
  3. const HALF_PI = Math.PI / 2;
  4. const canvas = document.createElement("canvas");
  5. const c = canvas.getContext("2d");
  6.  
  7. canvas.width = window.innerWidth;
  8. canvas.height = window.innerHeight;
  9. document.body.appendChild(canvas);
  10.  
  11. class Blob {
  12.   constructor() {
  13.     this.wobbleIncrement = 0;
  14.     // use this to change the size of the blob
  15.     // use this to change the size of the blob
  16.     this.radius = 1100;
  17.     // think of this as detail level
  18.     // number of conections in the `bezierSkin`
  19.     this.segments = 14;
  20.     this.step = HALF_PI / this.segments;
  21.     this.anchors = [];
  22.     this.radii = [];
  23.     this.thetaOff = [];
  24.  
  25.     const bumpRadius = 200;
  26.     const halfBumpRadius = bumpRadius / 2;
  27.  
  28.     for (let i = 0; i < this.segments + 2; i++) {
  29.       this.anchors.push(0, 0);
  30.       this.radii.push(Math.random() * bumpRadius - halfBumpRadius);
  31.       this.thetaOff.push(Math.random() * TWO_PI);
  32.     }
  33.  
  34.     this.theta = 0;
  35.     this.thetaRamp = 0;
  36.     this.thetaRampDest = 12;
  37.     this.rampDamp = 25;
  38.   }
  39.   update() {
  40.     this.thetaRamp += (this.thetaRampDest - this.thetaRamp) / this.rampDamp;
  41.     this.theta += 0.03;
  42.  
  43.     this.anchors = [0, this.radius];
  44.     for (let i = 0; i <= this.segments + 2; i++) {
  45.       const sine = Math.sin(this.thetaOff[i] + this.theta + this.thetaRamp);
  46.       const rad = this.radius + this.radii[i] * sine;
  47.       const theta = this.step * i;
  48.       const x = rad * Math.sin(theta);
  49.       const y = rad * Math.cos(theta);
  50.       this.anchors.push(x, y);
  51.     }
  52.  
  53.     c.save();
  54.     c.translate(-10, -10);
  55.     c.scale(SCALE, SCALE);
  56.     c.fillStyle = "blue";
  57.     c.beginPath();
  58.     c.moveTo(0, 0);
  59.     bezierSkin(this.anchors, false);
  60.     c.lineTo(0, 0);
  61.     c.fill();
  62.     c.restore();
  63.   }
  64. }
  65.  
  66. const blob = new Blob();
  67.  
  68. function loop() {
  69.   c.clearRect(0, 0, canvas.width, canvas.height);
  70.   blob.update();
  71.   window.requestAnimationFrame(loop);
  72. }
  73. loop();
  74.  
  75. // array of xy coords, closed boolean
  76. function bezierSkin(bez, closed = true) {
  77.   const avg = calcAvgs(bez);
  78.   const leng = bez.length;
  79.  
  80.   if (closed) {
  81.     c.moveTo(avg[0], avg[1]);
  82.     for (let i = 2; i < leng; i += 2) {
  83.       let n = i + 1;
  84.       c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
  85.     }
  86.     c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);
  87.   } else {
  88.     c.moveTo(bez[0], bez[1]);
  89.     c.lineTo(avg[0], avg[1]);
  90.     for (let i = 2; i < leng - 2; i += 2) {
  91.       let n = i + 1;
  92.       c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
  93.     }
  94.     c.lineTo(bez[leng - 2], bez[leng - 1]);
  95.   }
  96. }
  97.  
  98. // create anchor points by averaging the control points
  99. function calcAvgs(p) {
  100.   const avg = [];
  101.   const leng = p.length;
  102.   let prev;
  103.  
  104.   for (let i = 2; i < leng; i++) {
  105.     prev = i - 2;
  106.     avg.push((p[prev] + p[i]) / 2);
  107.   }
  108.   // close
  109.   avg.push((p[0] + p[leng - 2]) / 2, (p[1] + p[leng - 1]) / 2);
  110.   return avg;
  111. }

This is a stackoverflow answer of mine. The question was asking how to create a wobbling blob like the one in the background of the discord login page. Take a look at the answer here.

User AmooHesam wrapped it up in a github repo here.

// animation // canvas // javascript // math // paths // ui

Low Resolution Sine Table

  1. const sin = [
  2. 0,1,1,2,2,3,3,4,4,5,5,6,6,6,7,7,7,8,8,8,8,9,9,9,9,9,9,9,
  3. 9,9,9,9,9,9,9,9,9,9,9,8,8,8,8,7,7,7,6,6,5,5,5,4,4,3,3,2,2,
  4. 1,1,0,0,-1,-1,-2,-2,-3,-3,-4,-4,-4,-5,-5,-6,-6,-7,-7,-7,-8,
  5. -8,-8,-9,-9,-9,-9,-10,-10,-10,-10,-10,-10,-10,-10,-10,-10,
  6. -10,-10,-10,-10,-10,-10,-10,-10,-9,-9,-9,-9,-8,-8,-8,-7,-7,
  7. -6,-6,-6,-5,-5,-4,-4,-3,-3,-2,-2,-1,-1,0];
  8.  
  9. const c = document.body.appendChild(
  10.   document.createElement('canvas')
  11. ).getContext('2d');
  12.  
  13. const size = 200;
  14. c.canvas.width = c.canvas.height = size;
  15.  
  16. let x = 0;
  17. let tick = 0;
  18. let xOff = 30;
  19. let lines = 14;
  20. let pad = 10;
  21. let stagger = 8;
  22.  
  23. const loop = () => {
  24.   tick++;
  25.   c.fillStyle = 'red';
  26.   c.fillRect(0, 0, size, size);
  27.   c.fillStyle = 'white';
  28.  
  29.   for (let i = 0; i < size; i++) {
  30.     for (let j = 1; j < lines; j++) { 
  31.       x = xOff + sin[(i + j * stagger + tick) % 125];
  32.       c.fillRect(x + j * pad, i, 1, 1);
  33.     }
  34.   }
  35.   requestAnimationFrame(loop);
  36. };
  37. loop()

This is a low resolution sine table. I created this array to run on the original gameboy sometime last year… worked great for that…

Random Walk to Target

  1. const canvas = document.createElement('canvas');
  2. const c = canvas.getContext('2d');
  3.  
  4. let targetX, targetY, startX, startY;
  5. const rectSize = 20;
  6. const maxStep = 10;
  7.  
  8. const matchStep = maxStep / 2;
  9. const halfRectSize = rectSize / 2;
  10. let matchTime = 0;
  11. const resetTime = 20;
  12.  
  13. function randX() {
  14.   return innerWidth * 0.8 * Math.random() + innerWidth * 0.1;
  15. }
  16. function randY() {
  17.   return innerHeight * 0.8 * Math.random() + innerHeight * 0.1;
  18. }
  19.  
  20. function resize() {
  21.   canvas.width = innerWidth;
  22.   canvas.height = innerHeight;
  23.   reset();
  24. }
  25. window.addEventListener('resize', resize);
  26. resize();
  27.  
  28. document.body.appendChild(canvas);
  29. document.body.style.margin = 0;
  30.  
  31. function reset() {
  32.   matchTime = 0;
  33.   targetX = randX();
  34.   targetY = randY();
  35.   startX = randX();
  36.   startY = randY();
  37.   c.fillStyle = '#ccc';
  38.   c.fillRect(0, 0, innerWidth, innerHeight);
  39.  
  40.   c.fillStyle = '#c79500';
  41.   c.fillRect(
  42.     targetX - halfRectSize,
  43.     targetY - halfRectSize,
  44.     rectSize,
  45.     rectSize
  46.   );
  47.  
  48.   c.fillStyle = '#4e82c7';
  49.   c.fillRect(startX - halfRectSize, startY - halfRectSize, rectSize, rectSize);
  50. }
  51.  
  52. function loop() {
  53.   c.strokeStyle = 'black';
  54.   c.beginPath();
  55.   c.moveTo(startX, startY);
  56.   if (startX < targetX) {
  57.     startX += Math.random() * maxStep;
  58.   } else if (startX > targetX) {
  59.     startX -= Math.random() * maxStep;
  60.   }
  61.   if (startY < targetY) {
  62.     startY += Math.random() * maxStep;
  63.   } else if (startY > targetY) {
  64.     startY -= Math.random() * maxStep;
  65.   }
  66.  
  67.   c.lineTo(startX, startY);
  68.   c.stroke();
  69.  
  70.   if (
  71.     Math.abs(startX - targetX) < matchStep &&
  72.     Math.abs(startY - targetY) < matchStep
  73.   ) {
  74.     matchTime++;
  75.     if (matchTime > resetTime) {
  76.       reset();
  77.     }
  78.   }
  79.  
  80.   window.requestAnimationFrame(loop);
  81. }
  82. loop();

Randomly walk to a target.

snippet.zone /// {s/z}