(function(env) {
const PI2 = Math.PI * 2
, PI_180 = Math.PI / 180
, imgHash = {}
, identity = (d) => d
, padding = 1.5
, clusterPadding = 15
;
class Utils {
static preloadImage(url, obj) {
let img = imgHash[url];
if (img) {
obj && (obj.img = img);
return;
}
img = new Image();
img.onload = () => {
obj.img = img;
}
img.src = url
}
static dist(a, b) {
return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2))
}
static generateSprite(w, h, c) {
let tempFileCanvas = document.createElement("canvas");
tempFileCanvas.width = w;
tempFileCanvas.height = h;
let ctx = tempFileCanvas.getContext("2d");
let g = ctx.createRadialGradient( w/2, h/2, 0, w/2, h/2, w/2 );
g.addColorStop(0, 'hsla(' + c + ', 75%, 45%, 1)');
g.addColorStop(0.6, 'hsla(' + c + ', 95%, 30%,' + .1 + ')');
g.addColorStop(1, 'hsla(226, 55%, 50%, 0)');
ctx.fillStyle = g;
ctx.fillRect( 0, 0, w, h);
return tempFileCanvas;
}
}
class Canvas {
constructor(width, height) {
this.canvas = document.createElement("canvas");
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext("2d");
document.body.appendChild(this.canvas)
}
get width() {
return this.canvas.width
}
get height() {
return this.canvas.height
}
resize(width, height) {
if (this.width != width)
this.canvas.width = +width
if (this.height != height)
this.canvas.height = +height
}
}
class Point {
constructor({
x,
y,
color,
radius
}) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius
}
}
class Person extends Point {
constructor({
x,
y,
radius,
name,
color,
img
}) {
super({
x,
y,
color,
radius
});
this.image = img;
this.name = name
}
get image() {
return this.img
}
set image(value) {
value instanceof Image
&& (this.img = value) || Utils.preloadImage(value, this)
}
render(ctx) {
let r = this.radius,
r2 = r * 2;
ctx.fillStyle = this.color;
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, r, 0, PI2);
ctx.closePath();
ctx.stroke();
ctx.fill();
if (this.img) {
ctx.save();
ctx.clip();
ctx.drawImage(this.img, this.x - r, this.y - r, r2, r2);
ctx.restore()
}
}
}
var hashGrad = {};
class Particle extends Point {
constructor({
x,
y,
color,
radius,
speed = 1,
parent,
...arg
}) {
super({
x,
y,
color,
radius
});
this.parent = parent;
this.speed = speed;
this.prop = arg
}
render(ctx) {
let g = hashGrad[this.color];
if (!g) {
/* g = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius/2);
g.addColorStop(0, 'hsla(' + this.color + ', 75%, 45%, 1)');
g.addColorStop(0.6, 'hsla(' + this.color + ', 95%, 30%,' + .1 + ')');
g.addColorStop(1, 'hsla(226, 55%, 50%, 0)'); */
hashGrad[this.color] = g = Utils.generateSprite(200, 200, this.color);
}
// ctx.fillStyle = g;
// ctx.beginPath();
// ctx.arc(this.x, this.y, this.radius, PI2, false);
// ctx.fill();
let r = this.radius;
ctx.drawImage(g, this.x - r, this.y - r, r, r);
}
}
class Force {
constructor(width, height, nodes) {
let collide5, cluster;
this.force = d3.layout.force()
.nodes(nodes)
.size([width, height])
//.gravity(.1)
.charge((d) => -d.radius)
.chargeDistance(200)
.on("tick", (e) => {
cluster = cluster || this.cluster(.05);
let ns = this.nodes;
if (ns) {
ns.forEach(cluster);
}
this.force.resume();
})
}
size(width, height) {
this.force.size([width, height])
}
get nodes() {
return this.force.nodes()
}
set nodes(value) {
this.force.nodes(value)
}
start() {
this.force.start()
return this;
}
stop() {
this.force.stop()
return this;
}
clustering(fn) {
if (!arguments.length)
return this.clustering.do || identity;
this.clustering.do = fn;
return this;
}
// Move d to be adjacent to the cluster node.
cluster(alpha) {
return (d) => {
let cluster = this.clustering()(d);
if (!cluster || cluster === d)
return;
// d.x += (cluster.x - d.x) * alpha;
// d.y += (cluster.y - d.y) * alpha;
// the same
let x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = (d.radius + cluster.radius) * alpha;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
}
};
}
}
let w = env.innerWidth;
let h = env.innerHeight;
let first = new Person({
x: w * .5,
y: h * .5,
radius: 0,
color: "rgba(255, 255, 255, 0)",
//img: "https://vk.com/images/stickers/101/128.png"
});
let second = new Person({
x: w * .5,
y: h * .5,
radius: 0,
color: "rgba(255, 255, 255, 0)",
//img: "https://vk.com/images/stickers/101/128.png"
});
let ps = [], ps1 = [];
let c = new Canvas(w, h);
let force = (new Force(w, h, ps))
.clustering((d) => {
return d.parent;
})
.start()
;
let force1 = (new Force(w, h, ps1))
.clustering((d) => {
return d.parent;
})
.start()
;
let t = 0,k = 1;
(function anim() {
requestAnimationFrame(anim);
w = env.innerWidth;
h = env.innerHeight;
c.resize(w, h);
let ctx = c.ctx;
ctx.save();
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "rgba(0, 0, 0, .5)";
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'lighter';
let l = ps.length;
while (l--) {
ps[l].render(ctx);
}
l = ps1.length;
while (l--) {
ps1[l].render(ctx);
}
first.render(ctx);
k = (t % 200 > 190) ? -1 : 1;
first.y += k * Math.cos(t * PI_180) * 6;
second.x += k * Math.cos(t++ * PI_180) * 4;
ctx.restore();
})()
let tns = 0;
d3.timer(function() {
let ns = ps;
let p = first;
if (tns % 2) {
//ns = ps1;
p = second;
}
ns.push(new Particle({
x: p.x,
y: p.y,
color: /* 150 - Math.random() * 150 */ tns % 2 ? 185 : 100,
radius: 20,
parent: p,
speed: Math.random() * 4
}));
force.start();
force1.start();
return tns++ > 400;
})
env.addEventListener('resize', function() {
w = env.innerWidth;
h = env.innerHeight;
first.x = w * .5;
first.y = h * .5;
second.x = w * .5;
second.y = h * .5;
}, false);
})(window)
body, html
height 100%
width 100%
overflow hidden
background #001B36
background radial-gradient(ellipse at center, #001B36 0%, #000 100%)
console