I was building some bezier interpolation code and half way through I settled on these jiggling creatures
A Pen by Matthew Willox on CodePen.
I was building some bezier interpolation code and half way through I settled on these jiggling creatures
A Pen by Matthew Willox on CodePen.
<canvas id="canvas"></canvas> |
let w, h; | |
const canvas = document.getElementById("canvas"); | |
canvas.width = w = window.innerWidth; | |
canvas.height = h = window.innerHeight; | |
const ctx = canvas.getContext("2d"); | |
ctx.globalCompositeOperation = "screen"; | |
function polarRandom(scale = 200) { | |
return (0.5 - Math.random()) * scale; | |
} | |
class Point { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
add(point) { | |
this.x += point.x; | |
this.y += point.y; | |
} | |
sub(point) { | |
this.x -= point.x; | |
this.y -= point.y; | |
} | |
scale(value) { | |
this.x *= value; | |
this.y *= value; | |
} | |
static random(scale) { | |
return new Point(Math.random() * scale, Math.random() * scale); | |
} | |
} | |
class Bezier { | |
constructor(p1, h1, p2, h2, r, g, b) { | |
this.start = p1; | |
this.end = p2; | |
this.startHandle = h1; | |
this.endHandle = h2; | |
this.r = r || Math.random() * 255; | |
this.g = g || Math.random() * 255; | |
this.b = b || Math.random() * 255; | |
} | |
get color() { | |
return `rgba(${this.r}, ${this.g}, ${this.b}, 1)`; | |
} | |
set color(value) { | |
const [r, g, b] = value; | |
this.r = r; | |
this.g = g; | |
this.b = b; | |
} | |
pointOnPath(t) { | |
return cubicBezier( | |
this.start, | |
this.end, | |
{ | |
x: this.startHandle.x + this.start.x, | |
y: this.startHandle.y + this.start.y | |
}, | |
{ x: this.endHandle.x + this.end.x, y: this.endHandle.y + this.end.y }, | |
t | |
); | |
} | |
get raw() { | |
return [ | |
this.startHandle.x + this.start.x, | |
this.startHandle.y + this.start.y, | |
this.endHandle.x + this.end.x, | |
this.endHandle.y + this.end.y, | |
this.end.x, | |
this.end.y | |
]; | |
} | |
} | |
function cubicBezier(start, end, startHandle, endHandle, t) { | |
// Calculate the blending functions | |
const blend1 = (1 - t) ** 3; | |
const blend2 = 3 * t * (1 - t) ** 2; | |
const blend3 = 3 * t ** 2 * (1 - t); | |
const blend4 = t ** 3; | |
// Calculate the x and y coordinates of the point on the curve | |
const x = | |
start.x * blend1 + | |
startHandle.x * blend2 + | |
endHandle.x * blend3 + | |
end.x * blend4; | |
const y = | |
start.y * blend1 + | |
startHandle.y * blend2 + | |
endHandle.y * blend3 + | |
end.y * blend4; | |
// Return the calculated point | |
return new Point(x, y); | |
} | |
class Shape { | |
path = []; | |
points = []; | |
handles = []; | |
blendControl = null; | |
amount = 10; | |
constructor(points, amount) { | |
for (let i = 0; i < points.length; i += 2) { | |
this.points.push(points[i]); | |
this.handles.push(points[i + 1]); | |
} | |
let acc = []; | |
let l = points.length; | |
for (let i = 0; i < this.points.length; i++) { | |
acc.push(this.points[i], this.handles[i]); | |
if (acc.length === 4) { | |
this.add( | |
new Bezier( | |
acc[0], | |
acc[1], | |
acc[2], | |
acc[3], | |
Math.random() * 255, | |
Math.random() * 255, | |
Math.random() * 255 | |
) | |
); | |
acc.splice(0, 2); | |
} | |
} | |
this.add( | |
new Bezier( | |
points[l - 2], | |
points[l - 1], | |
points[0], | |
points[1], | |
Math.random() * 255, | |
Math.random() * 255, | |
Math.random() * 255 | |
) | |
); | |
this.amount = amount; | |
} | |
add(bez) { | |
this.path.push(bez); | |
} | |
} | |
function interpolate(shape1, shape2, ctx) { | |
if (shape1.path.length === shape2.path.length) { | |
shape1.path.forEach((path, i) => { | |
const start = path; | |
const end = shape2.path[i]; | |
const s = start.start; | |
const e = start.end; | |
// start.draw(ctx); | |
// end.draw(ctx); | |
const num = shape1.amount; | |
for (let t = 0; t < num; t++) { | |
const x1 = s.x + ((end.start.x - start.start.x) / num) * t; | |
const y1 = s.y + ((end.start.y - start.start.y) / num) * t; | |
const c1x = | |
start.startHandle.x + | |
((end.startHandle.x - start.startHandle.x) / num) * t; | |
const c1y = | |
start.startHandle.y + | |
((end.startHandle.y - start.startHandle.y) / num) * t; | |
const x2 = e.x + ((end.end.x - start.end.x) / num) * t; | |
const y2 = e.y + ((end.end.y - start.end.y) / num) * t; | |
const c2x = | |
start.endHandle.x + ((end.endHandle.x - start.endHandle.x) / num) * t; | |
const c2y = | |
start.endHandle.y + ((end.endHandle.y - start.endHandle.y) / num) * t; | |
const r = start.r + ((end.r - start.r) / num) * t; | |
const g = start.g + ((end.g - start.g) / num) * t; | |
const b = start.b + ((end.b - start.b) / num) * t; | |
ctx.beginPath(); | |
ctx.moveTo(x1, y1); | |
ctx.lineCap = "round"; | |
ctx.strokeStyle = `rgba(${r},${g},${b}, 0.9)`; | |
var deg = (t / num) * 360 * 0.0174533; | |
ctx.lineWidth = 0.1 + (1 - Math.cos(deg)) * 0.5 * 3; | |
ctx.bezierCurveTo(c1x + x1, c1y + y1, c2x + x2, c2y + y2, x2, y2); | |
ctx.stroke(); | |
} | |
}); | |
} | |
} | |
function setup() { | |
const size = 3 + Math.ceil(Math.random() * 3); | |
const amount = 15 + Math.ceil(Math.random() * 15); | |
let handle = new Point(0, 10); | |
const ps1 = new Array(size * 2).fill(0).map((x, i) => { | |
let point; | |
if (i % 2 === 0) { | |
point = new Point(w * Math.random(), h * Math.random()); | |
} else { | |
point = handle; | |
} | |
return point; | |
}); | |
const ps2 = new Array(size * 2).fill(0).map((x, i) => { | |
let point; | |
if (i % 2 === 0) { | |
point = new Point(w * Math.random(), h * Math.random()); | |
} else { | |
//handle = handle.reflect(); | |
point = handle; | |
} | |
return point; | |
}); | |
const ps3 = new Array(3*2).fill(0).map((x, i) => { | |
let point; | |
if (i % 2 === 0) { | |
point = new Point((w/2)+(polarRandom(2)), h/2 + (h/2*polarRandom(2))); | |
} else { | |
point = handle; | |
} | |
return point; | |
}); | |
const ps4 = new Array(3*2).fill(0).map((x, i) => { | |
let point; | |
if (i % 2 === 0) { | |
point = new Point((w/2)+(polarRandom(2)), h/2 + (h/2*polarRandom(2))); | |
} else { | |
//handle = handle.reflect(); | |
point = handle; | |
} | |
return point; | |
}); | |
const shape1 = new Shape(ps1, amount); | |
const shape2 = new Shape(ps2, amount); | |
const shape3 = new Shape(ps3, amount); | |
const shape4 = new Shape(ps4, amount); | |
return { ps1, ps2, ps3, ps4, shape1, shape2, shape3, shape4 }; | |
} | |
let { ps1, ps2, ps3, ps4, shape1, shape2, shape3, shape4 } = setup(); | |
var grd = ctx.createRadialGradient(w/2, h/2, 10, w/2, h/2, 300); | |
grd.addColorStop(0, "black"); | |
grd.addColorStop(0.9, "rgba(0,0,0,0)"); | |
grd.addColorStop(1, "rgba(0,0,0,0)"); | |
function draw() { | |
ps1.forEach((p, i) => { | |
p.x += Math.sin(i + performance.now() * 0.00342442); | |
p.y += Math.cos(i + performance.now() * 0.00978345); | |
}); | |
ps2.forEach((p, i) => { | |
p.x += Math.cos(i + performance.now() * 0.00762)*2; | |
p.y += Math.sin(i + performance.now() * 0.0095649)*2; | |
}); | |
ps3.forEach((p, i) => { | |
p.x += Math.cos(i + performance.now() * 0.0035642)*2; | |
p.y += Math.sin(i + performance.now() * 0.005649)*2; | |
}); | |
ps4.forEach((p, i) => { | |
p.x += Math.cos(i + performance.now() * 0.004576)*2; | |
p.y += Math.sin(i + performance.now() * 0.00649)*2; | |
}); | |
//ctx.clearRect(0, 0, w, h); | |
ctx.restore() | |
ctx.clearRect(0,0, w, h); | |
ctx.globalCompositeOperation = "screen"; | |
interpolate(shape1, shape2, ctx); | |
ctx.save() | |
ctx.globalCompositeOperation = "source-over"; | |
ctx.moveTo(0,0); | |
ctx.fillStyle = grd; | |
ctx.fillRect(0, 0, w, h); | |
ctx.restore(); | |
ctx.globalCompositeOperation = "screen"; | |
interpolate(shape3, shape4, ctx); | |
ctx.save(); | |
ctx.globalCompositeOperation = "screen"; | |
ctx.translate(w, 0); | |
ctx.scale(-1, 1); | |
ctx.drawImage(canvas, 0, 0, w, h) | |
ctx.restore(); | |
requestAnimationFrame(draw); | |
} | |
requestAnimationFrame(draw); | |
window.addEventListener("resize", () => { | |
canvas.width = w = window.innerWidth; | |
canvas.height = h = window.innerHeight; | |
}); | |
window.addEventListener("click", () => { | |
let set = setup(); | |
ps1 = set.ps1; | |
ps2 = set.ps2; | |
ps3 = set.ps3; | |
ps4 = set.ps4; | |
shape1 = set.shape1; | |
shape2 = set.shape2; | |
shape3 = set.shape3; | |
shape4 = set.shape4; | |
}); | |
window.addEventListener("pointermove", (e) => { | |
}); |
body { | |
margin:0; | |
padding:0; | |
background: linear-gradient(rgba(3,4,29,1),rgba(14,15,26,1)); | |
cursor: pointer; | |
} | |
canvas { | |
width: 100%; | |
height: calc(100vh); | |
} |