const seededRandom = (() => {
var seed = 1;
return { max : 2576436549074795, reseed (s) { seed = s }, random () { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const randSPow = (min, max = min + (min = 0), p = 2) => (max + min) / 2 + (Math.pow(seededRandom.random() / seededRandom.max, p) * (max - min) * 0.5) * (randSI(2) < 1 ? 1 : -1);
const ctx = canvas.getContext("2d");
const W = ctx.canvas.width;
const H = ctx.canvas.height;
const DIAG = (W * W + H * H) ** 0.5;
const colors = {
dark : {
minRGB : [100 * 0.6,200 * 0.6,240 * 0.6],
maxRGB : [255 * 0.6,255 * 0.6,255 * 0.6],
},
light : {
minRGB : [100,200,240],
maxRGB : [255,255,255],
},
}
const getCol = (pos, range) => "rgba(" +
((range.maxRGB[0] - range.minRGB[0]) * pos + range.minRGB[0] | 0) + "," +
((range.maxRGB[1] - range.minRGB[1]) * pos + range.minRGB[1] | 0) + "," +
((range.maxRGB[2] - range.minRGB[2]) * pos + range.minRGB[2] | 0) + "," +(pos * 0.2 + 0.8) + ")";
const Cloud = {
x : 0,
y : 0,
dir : 0, // in radians
wobble : 0,
wobble1 : 0,
wSpeed : 0,
wSpeed1 : 0,
mx : 0, // Move offsets
my : 0,
seed : 0,
size : 2,
detail : null,
reset : true, // when true could resets
init() {
this.seed = randSI(10000000);
this.reset = false;
var x,y,r,dir,dist,f;
if (this.detail === null) { this.detail = [] }
else { this.detail.length = 0 }
randSeed(this.seed);
this.size = randSPow(2, 8); // The pow add bias to smaller values
var col = (this.size -2) / 6;
this.col1 = getCol(col,colors.dark)
this.col2 = getCol(col,colors.light)
var flufCount = randSI(5,15);
while (flufCount--) {
x = randSI(-this.size * 8, this.size * 8);
r = randS(this.size * 2, this.size * 8);
dir = randS(Math.PI * 2);
dist = randSPow(1) * r ;
this.detail.push(f = {x,r,y : 0,mx:0,my:0, move : randS(0.001,0.01), phase : randS(Math.PI * 2)});
f.x+= Math.cos(dir) * dist;
f.y+= Math.sin(dir) * dist;
}
this.xMax = this.size * 12 + this.size * 10 + this.size * 4;
this.yMax = this.size * 10 + this.size * 4;
this.wobble = randS(Math.PI * 2);
this.wSpeed = randS(0.01,0.02);
this.wSpeed1 = randS(0.01,0.02);
const aOff = randS(1) * Math.PI * 0.5 - Math.PI *0.25;
this.x = W / 2 - Math.cos(this.dir+aOff) * DIAG * 0.7;
this.y = H / 2 - Math.sin(this.dir+aOff) * DIAG * 0.7;
clouds.sortMe = true; // flag that coulds need resort
},
move() {
var dx,dy;
this.dir = gTime / 10000;
if(this.reset) { this.init() }
this.wobble += this.wSpeed;
this.wobble1 += this.wSpeed1;
this.mx = Math.cos(this.wobble) * this.size * 4;
this.my = Math.sin(this.wobble1) * this.size * 4;
this.x += dx = Math.cos(this.dir) * this.size / 5;
this.y += dy = Math.sin(this.dir) * this.size / 5;
if (dx > 0 && this.x > W + this.xMax ) { this.reset = true }
else if (dx < 0 && this.x < - this.xMax ) { this.reset = true }
if (dy > 0 && this.y > H + this.yMax) { this.reset = true }
else if (dy < 0 && this.y < - this.yMax) { this.reset = true }
},
draw(){
const s = this.size;
const s8 = this.size * 8;
ctx.fillStyle = this.col1;
ctx.setTransform(1,0,0,1,this.x+ this.mx,this.y +this.my);
ctx.beginPath();
for (const fluf of this.detail) {
fluf.phase += fluf.move + Math.sin(this.wobble * this.wSpeed1) * 0.02 * Math.cos(fluf.phase);
fluf.mx = Math.cos(fluf.phase) * fluf.r / 2;
fluf.my = Math.sin(fluf.phase) * fluf.r / 2;
const x = fluf.x + fluf.mx;
const y = fluf.y + fluf.my;
ctx.moveTo(x + fluf.r + s, y);
ctx.arc(x,y,fluf.r+ s,0,Math.PI * 2);
}
ctx.fill();
ctx.fillStyle = this.col2;
ctx.globalAlpha = 0.5;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s;
const y = fluf.y + fluf.my - s;
ctx.moveTo(x + fluf.r, y);
ctx.arc(x,y,fluf.r,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 0.6;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s * 1.4;
const y = fluf.y + fluf.my - s * 1.4;
ctx.moveTo(x + fluf.r * 0.8, y);
ctx.arc(x,y,fluf.r* 0.8,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 1;
}
}
function createCloud(size){ return {...Cloud} }
const clouds = Object.assign([],{
move() { for(const cloud of this){ cloud.move() } },
draw() { for(const cloud of this){ cloud.draw() } },
sortMe : true, // if true then needs to resort
resort() {
this.sortMe = false;
this.sort((a,b)=>a.size - b.size);
}
});
for(let i = 0; i < 15; i ++) { clouds.push(createCloud(40)) }
requestAnimationFrame(mainLoop)
var gTime = 0;
function mainLoop() {
gTime += 16;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
if(clouds.sortMe) { clouds.resort() }
clouds.move();
clouds.draw();
requestAnimationFrame(mainLoop);
}
body { padding : 0px; margin : 0px;}
canvas {
background : rgb(60,120,148);
border : 1px solid black;
}
<canvas id="canvas" width="600" height="200"></canvas>