Skip to content

Instantly share code, notes, and snippets.

@maritrinez
Created October 3, 2018 11:06
Show Gist options
  • Save maritrinez/7a06843beefa67b92bbb143fe1cf190f to your computer and use it in GitHub Desktop.
Save maritrinez/7a06843beefa67b92bbb143fe1cf190f to your computer and use it in GitHub Desktop.
Canvas variable points bounded

Canvas points bounded inside / outside a circle. The points inside the circle have variable radius and opacity.

Based on Mike Bostock Circles. Canvas transition taken from this Bocoup's article

<!DOCTYPE html>
<meta charset="utf-8">
<style>
button {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
font-size: 18px;
color: black;
pointer-events: all;
}
.disabled {
color: grey;
pointer-events: none;
}
</style>
<body>
<div>
<button class="restart disabled">RESTART</button>
<button class="stop">STOP</button>
</div>
<script src="//d3js.org/d3.v5.min.js"></script>
<script>
// - - - - - constant values - - - - - //
const OPACITY = 0.8,
FILL = {inside: `rgba(255, 5, 5, ${OPACITY})`, outside: 'rgba(5, 5, 5, 0.5)', in: 'rgba(5, 255, 5, 0.8)'},
STROKE = 'rgba(5, 5, 5, 0.8)',
SIZE = {w: 960, h: 420},
RADIUS = {point: {min: 2, max: 4}, circle: 120},
VELOCITY = {min: 0.15, dev: 0.1},
DENSITY = 0.0015, // particles per sq px;
DURATION = 5500;
// - - - - - canvas - - - - - //
const canvas = d3.select('body').append('canvas')
.attr('width', SIZE.w + 'px')
.attr('height', SIZE.h + 'px');
const context = canvas.node().getContext('2d');
// - - - - - random circle & points - - - - - //
const circle = {
r: RADIUS.circle,
x: SIZE.w / 2,
y: SIZE.h / 2
};
const N = Math.round(SIZE.w * SIZE.h * DENSITY);
const points = d3.range(N).map(() => {
var point = {};
point.r = Math.random() * (RADIUS.point.max - RADIUS.point.min) + RADIUS.point.min;
point.x = Math.round(Math.random() * SIZE.w);
point.y = Math.round(Math.random() * SIZE.h);
point.dx = (Math.random() * VELOCITY.dev + VELOCITY.min) * (Math.round(Math.random()) * 2 - 1);
point.dy = (Math.random() * VELOCITY.dev + VELOCITY.min) * (Math.round(Math.random()) * 2 - 1);
point.position = getPoint(point).position;
point.o = OPACITY;
if (point.position == 'in') getPoint(point).coords;
point.fill = FILL[point.position];
return point;
});
// - - - - - inside points increase radius and opacity - - - - - //
const rScale = d3.scaleThreshold().domain([0.50, 0.90, 0.95, 1]).range([5, 10, 15, 22, 35]);
const opacityScale = d3.scaleLinear().domain([5, 35]).range([0.5, 0.9])
const n = points.filter(d => d.position == 'inside').length;
points.filter(d => d.position == 'inside').forEach((d, i) => {
d.sr = d.r;
d.tr = rScale((i + 1) / n) * (Math.random() * (1.1 - 0.8) + 0.8)
d.so = OPACITY;
d.to = opacityScale(d.tr);
});
// - - - - - trigger animation - - - - - //
var timer = d3.timer(floating);
// - - - - - restart - - - - - //
d3.select('.restart').on('click', () => {
d3.select('.restart').classed('disabled', true);
timer.restart(floating)
});
d3.select('.stop').on('click', () => {
d3.select('.restart').classed('disabled', false);
timer.stop();
});
// - - - - - functions - - - - - //
function floating(elapsed) {
if (elapsed > 50500) {
timer.stop();
d3.select('.restart').classed('disabled', false);
return;
}
const t = Math.min(1, d3.easeCubicIn(elapsed / DURATION));
points.forEach(d => {
// Make inside points bigger and lighter
if (d.position == 'inside' && t !== 1) {
d.o = (d.so * (1 - t) + d.to * t)
d.r = (d.sr * (1 - t) + d.tr * t)
d.fill = `rgba(255, 5, 5, ${d.o})`
}
// Change direction of points scaping
if (getPoint(d).scape) {
d.dx *= -1;
d.dy *= -1;
// Avoid them to get stuck in the border
if (d.position == 'inside' ) getPoint(d).coords;
}
// Float every point
d.x += d.dx;
d.y += d.dy;
});
draw();
}
function getPoint(point) {
return {
get position() {
switch(true) {
case _dist() + point.r < circle.r:
return 'inside'
break;
case _dist() - point.r > circle.r:
return 'outside'
break;
default:
return 'in'
}
},
get coords() {
const angle = Math.atan2(point.y - circle.y, point.x - circle.x),
newRad = _dist() < circle.r ? circle.r - point.r - 0.3: circle.r + point.r + 0.3;
point.x = Math.cos(angle) * newRad + circle.x;
point.y = Math.sin(angle) * newRad + circle.y;
point.position = this.position;
},
get scape() {
switch(true) {
case this.position == 'in':
return true
break;
case point.x + point.r > SIZE.w || point.x - point.r < 0:
return true
break;
case point.y + point.r > SIZE.h || point.y - point.r < 0:
return true
break;
default:
return false
}
}
}
function _diff(c) {
return circle[c] - point[c];
}
function _dist() {
return Math.sqrt(_diff('x') * _diff('x') + _diff('y') * _diff('y'));
}
}
function draw() {
context.clearRect(0, 0, SIZE.w, SIZE.h)
// points
for (let i = 0; i < points.length; ++i) {
context.beginPath();
context.fillStyle = points[i].fill;
context.arc(points[i].x, points[i].y, points[i].r, 0, 2 * Math.PI);
context.fill();
}
// circle
context.beginPath();
context.fillStyle = 'rgba(0, 0, 0, 0)';
context.strokeStyle = STROKE;
context.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI);
context.fill();
context.stroke();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment