index.html
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>Animating 50,000 points with WebGL</title>
<meta name="description" content="Animating 50,000 points with WebGL">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src=https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.0.0/pixi.js></script>
</head>
<body>
<div id=graphic></div>
<script type-'text/javascript'>
let nDots = 50000,
dots = [],
stopAnimation = false,
WebGL = true,
PR = devicePixelRatio || 1,
width = document.body.clientWidth,
height = window.innerHeight-16,
scaledWidth = width*PR,
scaledHeight = height*PR,
caontainer,
renderer,
circleTexture,
canvas,
context;
function phyllotaxis(radius) {
var theta = Math.PI * (3 - Math.sqrt(5));
return function(i) {
var r = radius * Math.sqrt(i), a = theta * i;
return {
x: width / 2 + r * Math.cos(a),
y: height / 2 + r * Math.sin(a)
};
};
}
function gridLayout(points, gridWidth) {
const pointHeight = pointWidth = ((33*0.16));
const pointsPerRow = Math.floor(width / pointWidth);
const numRows = points.length / pointsPerRow;
return function(i){
return {
x: (pointWidth * (i % pointsPerRow)) + pointWidth,
y: (pointHeight * Math.floor(i / pointsPerRow)) + pointHeight
};
};
}
if(WebGL == true){
renderer = new PIXI.autoDetectRenderer(width, height,
{
backgroundColor: 0xFFFFFF,
antialias: true,
resolution: 1
}
);
document.getElementById('graphic').appendChild(renderer.view);
container = new PIXI.Container(0xFFFFFF);
renderer.render(container);
circleTexture = new PIXI.Texture.fromImage("//johnburnmurdoch.github.io/images/circle.png");
}
if(WebGL == false){
canvas = document.createElement('canvas');
document.getElementById('graphic').appendChild(canvas);
canvas.setAttribute('width', scaledWidth);
canvas.setAttribute('height', scaledHeight);
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
context = canvas.getContext("2d");
context.scale(PR, PR);
context.clearRect(0,0,scaledWidth,scaledHeight);
}
const data = Array.from({length: nDots}, (d,i) => i).map(phyllotaxis(2.5)).map((d,i) => {
let opacity = 1-(i/nDots);
return{
x: d.x,
y: d.y,
opacity: opacity,
color: WebGL ? ((240 << 16) + (0 << 8) + 0) : `hsla(0, 100%, 50%, ${opacity})`,
size: 0.11
}
});
const data1 = Array.from({length: nDots}, (d,i) => i).map(gridLayout(width)).map(d => {
let opacity = Math.random();
return{
x: d.x,
y: d.y
}
});
const data2 = Array.from({length: nDots}, d => {
return{
x: Math.random()*width,
y: Math.random()*height
}
});
function render() {
data.forEach((d,i) => {
let dot = new PIXI.Sprite(circleTexture);
dot.tint = d.color;
dot.blendMode = PIXI.BLEND_MODES.MULTIPLY;
dot.anchor.x = 0.5;
dot.anchor.y = 0.5;
dot.position.x = d.x;
dot.position.y = d.y;
dot.scale.x = dot.scale.y = d.size;
dot.alpha = d.opacity;
dots[i] = dot;
if(WebGL == false){
context.fillStyle = d.color;
context.beginPath();
context.arc(d.x, d.y, (33*0.12)/PR, 0, Math.PI * 2);
context.fill();
}else{
container.addChild(dot);
}
});
if(WebGL == true){
renderer.render(container);
}
}
setTimeout(_ => {
render();
}, 100);
const fps = 10;
const tweenTime = 5;
const tweenFrames = fps * tweenTime;
let animate,
frame = 0,
progress = 0;
animate = function() {
frame++;
progress = (frame / tweenFrames) || 0;
if(WebGL == false){
context.clearRect(0,0,scaledWidth,scaledHeight);
}
for (var i = 0; i < dots.length; i++) {
x0 = data[i].x;
x1 = data1[i].x;
y0 = data[i].y;
y1 = data1[i].y;
xInt = x0 + ((x1 - x0) * progress);
yInt = y0 + ((y1 - y0) * progress);
if(WebGL == false){
context.fillStyle = data[i].color;
context.beginPath();
context.arc(xInt, yInt, (33*0.12)/PR, 0, Math.PI * 2);
context.fill();
}else{
dots[i].position.x = xInt;
dots[i].position.y = yInt;
}
}
if(WebGL == true){
renderer.render(container);
}
if(frame >= (tweenFrames)) stopAnimation = true;
if(!stopAnimation) requestAnimationFrame(animate);
};
document.getElementsByTagName('body')[0].addEventListener('touchstart', animate);
document.getElementsByTagName('body')[0].addEventListener('dblclick', animate);
</script>
</body>
</html>