Canvas points bounded inisde / outside a circle
Based on Mike Bostock Circles. Canvas transition taken from this Bocoup's article
license: gpl-3.0 |
Canvas points bounded inisde / outside a circle
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> | |
</div> | |
<script src="//d3js.org/d3.v5.min.js"></script> | |
<script> | |
// - - - - - constant values - - - - - // | |
const FILLIN = 'rgba(255, 3, 5, 0.8)', | |
FILLOUT = 'rgba(5, 3, 5, 0.5)', | |
STROKE = 'rgba(5, 5, 5, 0.8)', | |
SIZE = {w: 960, h: 400}, | |
RADIUS = {point: {min: 1, max: 3}, circle: 120}, | |
VELOCITY = {min: 0.15, dev: 0.15}, | |
DENSITY = 0.0025, // particles per sq px; | |
DURATION = 750; | |
// - - - - - 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: Math.round(Math.random() * (SIZE.w - 2 * RADIUS.circle) + RADIUS.circle), | |
y: Math.round(Math.random() * (SIZE.h - 2 * RADIUS.circle) + RADIUS.circle) | |
}; | |
const N = Math.round(SIZE.w * SIZE.h * DENSITY); | |
const points = d3.range(N).map(function(d) { | |
var point = {}; | |
point.r = Math.round(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.inside = pointInCircle(point); | |
point.fill = !point.inside ? FILLOUT : FILLIN; | |
return point; | |
}); | |
// - - - - - trigger animation - - - - - // | |
var timer = d3.timer(floating); | |
// - - - - - restart - - - - - // | |
d3.select('.restart').on('click', () => { | |
d3.select('.restart').classed('disabled', true); | |
timer.restart(floating) | |
}) | |
// - - - - - functions - - - - - // | |
function floating(elapsed) { | |
if (elapsed > 3000) { | |
timer.stop(); | |
d3.select('.restart').classed('disabled', false); | |
return; | |
} | |
points.forEach(d => { | |
if(!d.inside) { | |
d.x += d.dx; if (d.x > SIZE.w || d.x < 0 || pointInCircle(d)) d.dx *= -1; | |
d.y += d.dy; if (d.y > SIZE.h || d.y < 0 || pointInCircle(d)) d.dy *= -1; | |
} else { | |
d.x += d.dx; if (!pointInCircle(d)) d.dx *= -1; | |
d.y += d.dy; if (!pointInCircle(d)) d.dy *= -1; | |
} | |
}); | |
draw(); | |
} | |
function pointInCircle(point) { | |
return _dist() <= circle.r ? true : false; | |
function _dist() { | |
let dist = Math.sqrt(_diff('x') * _diff('x') + _diff('y') * _diff('y')); | |
switch (point.inside) { | |
case false: | |
return dist - point.r; | |
break; | |
case true: | |
return dist + point.r; | |
break; | |
default: | |
// update position of nodes that fall IN the circumference | |
if (dist > circle.r && dist - point.r < circle.r) { | |
point.x = point.x < circle.x ? point.x - point.r : point.x + point.r; | |
point.y = point.y < circle.y ? point.y - point.r : point.y + point.r; | |
} else if (dist <= circle.r && dist + point.r >= circle.r) { | |
point.x = point.x < circle.x ? point.x + point.r : point.x - point.r; | |
point.y = point.y < circle.y ? point.y + point.r : point.y - point.r; | |
} | |
return dist ; | |
} | |
} | |
function _diff(c) { | |
return circle[c] - point[c]; | |
} | |
} | |
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> |