I have a requirement to move many dots here and there inside a canvas.
Hence I created several arcs with different radius and placed them at random places.
var context = document.getElementById('stage').getContext('2d');
var radian = Math.PI / 180;
var x = 40;
var y = 40;
var r = 20;
var colorPoints = [];
var frames = 50;
var currentFrame = 0;
var toggle = false;
var iconsLoaded = false;
context.beginPath();
context.arc(x,y, r, 0 * radian, 360 * radian, false)
context.fill();
var drawMultipleCurves = function(ctx){
if(!iconsLoaded){
for (let i = 0; i < 600; i++) {
ctx.beginPath();
ctx.filter = 'blur(5px)';
ctx.fillStyle = '#B835FF';
colorPoints.push({x: Math.floor((Math.random() * 700) + 0), xMove: Math.floor((Math.random() * 2) + 0) , yMove: Math.floor((Math.random() * 2) + 0) , y: Math.floor((Math.random() * 700) + 0), radius: Math.floor((Math.random() * 20) + 5)});
ctx.arc(colorPoints[colorPoints.length - 1].x, colorPoints[colorPoints.length - 1].y, colorPoints[colorPoints.length - 1].radius, 0 * radian, 360 * radian, false);
ctx.fill();
ctx.closePath();
iconsLoaded = true;
}
}
else{
for(let i =0;i< colorPoints.length; i++){
if(frames === currentFrame ){
toggle = !toggle;
currentFrame = 0;
}
if(!toggle){
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x + 5 : colorPoints[i].x = colorPoints[i].x - 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y + 5 : colorPoints[i].y = colorPoints[i].y - 5;
}
else{
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x - 5 : colorPoints[i].x = colorPoints[i].x + 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y - 5 : colorPoints[i].y = colorPoints[i].y + 5;
}
ctx.beginPath();
ctx.arc(colorPoints[i].x, colorPoints[i].y, colorPoints[i].radius, 0 * radian, 360 * radian, false);
context.closePath( );
ctx.fill();
currentFrame = currentFrame + 1;
}
}
}
var animate = function(){
setTimeout(()=>{
context.clearRect(0,0,400,400);
context.beginPath();
drawMultipleCurves(context);
context.fill();
requestAnimationFrame(animate)
}, 1000/30)
}
requestAnimationFrame(animate)
<canvas id="stage" width="400" height="400">
<p>Your browser doesn't support canvas.</p>
</canvas>
Above is the code that I have tried.
I have first created and placed several dots at random places with random radius.
When I created them I saved all these random places in an array 'colorPoints'
Now I'm looping into this array and moving all the dots everytime 'requestAnimation' is called.
I'm able to achieve my animation of moving the dots randomly but as I have used 800 dots and then saving them into an array and then again looping them to move their position, the animation is not looking smooth.
It looks like it is moving and strucking. How can I achieve this animation smoothly?
Thanks in advance :)
Render "fill" once per style
Your code is slowing down due to where you placed fill (same if you use stroke)
When you have many objects with the same style call fill only once per frame for each object.
You had something like
for (const c of circles) {
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, TAU)
ctx.fill();
}
With a filter active the fill command forces the filter to be reset, which for blur is complex.
Rather add all the arcs then fill.
ctx.beginPath();
for (const c of circles) {
ctx.moveTo(c.x + c.r, c.y);
ctx.arc(c.x, c.y, c.r, 0, TAU)
}
ctx.fill();
The move ctx.moveTo(c.x + c.r, c.y); is used to close the previous arc.
You can also close the arc with ctx.closePath but this can be a lot slower when you have many arcs in the path buffer.
// slower than using moveTo
ctx.beginPath();
for (const c of circles) {
ctx.arc(c.x, c.y, c.r, 0, TAU)
ctx.closePath();
}
ctx.fill();
Example
Example draws 600 arcs using the blur filter as it only calls fill once per frame. This should run smooth on all but the most low end devices.
See function drawCircles
requestAnimationFrame(animate);
const ctx = canvas.getContext('2d');
const W = canvas.width;
const BLUR = 5;
const CIRCLE_COUNT = 600;
const MIN_RADIUS = BLUR;
const MAX_RADIUS = 30;
const MAX_DELTA = 1;
const MAX_CIR_R = MAX_RADIUS + BLUR;
const MOVE_SIZE = MAX_CIR_R * 2 + W;
const TAU = 2 * Math.PI;
const setOf = (c, cb, i = 0, a = []) => { while(i < c) { a.push(cb(i++)) } return a };
const rnd = (m, M) => Math.random() * (M - m) + m;
const style = {
filter: "blur(" + BLUR + "px)",
fillStyle: '#B835FF',
};
var currentStyle;
function setStyle(ctx, style) {
if (currentStyle !== style) {
Object.assign(ctx, style);
currentStyle = style;
}
}
const circle = {
get x() { return rnd(-MAX_CIR_R, W + MAX_CIR_R) },
get y() { return rnd(-MAX_CIR_R, W + MAX_CIR_R) },
get dx() { return rnd(-MAX_DELTA, MAX_DELTA) },
get dy() { return rnd(-MAX_DELTA, MAX_DELTA) },
get r() { return rnd(MIN_RADIUS, MAX_RADIUS) },
move() {
var x = this.x + this.dx + MOVE_SIZE + MAX_CIR_R;
var y = this.y + this.dy + MOVE_SIZE + MAX_CIR_R;
this.x = x % MOVE_SIZE - MAX_CIR_R;
this.y = y % MOVE_SIZE - MAX_CIR_R;
}
};
const circles = setOf(CIRCLE_COUNT, () => Object.assign({}, circle));
function drawCircles(circles, ctx, style) {
setStyle(ctx, style);
ctx.beginPath();
for (const c of circles) {
ctx.moveTo(c.x + c.r, c.y);
ctx.arc(c.x, c.y, c.r, 0, TAU);
}
ctx.fill();
}
function updateCircles(circles) {
for (const c of circles) { c.move(); }
}
function animate() {
ctx.clearRect(0,0,W, W);
updateCircles(circles);
drawCircles(circles, ctx, style);
requestAnimationFrame(animate);
}
<canvas id="canvas" width="600" height="600"> </canvas>
If you have several colors, group all the same colors so you can keep the number of fill calls as low as possible.
There are many ways to get the same effect with many colors (each circle a different color) but will need more setup code.
The CanvasRenderingContext2D blur filter is quite heavy - especially if you use it on a canvas consisting of 600 circles. That means on every screen update it has to re-draw 600 circles and apply a blur filter afterwards.
The usual approach is a little different. Initially you create a master texture with a blurred circle. This texture can then be re-used and drawn onto the canvas using the drawImage() method. To vary the size of the circles there is no radius anymore though. We can get the same effect by using a scale instead.
Here's an example:
var context = document.getElementById('stage').getContext('2d');
var radian = Math.PI / 180;
var x = 40;
var y = 40;
var r = 20;
var colorPoints = [];
var frames = 50;
var currentFrame = 0;
var toggle = false;
var iconsLoaded = false;
var texture = document.createElement("canvas");
var textureContext = texture.getContext("2d");
texture.width = 80;
texture.height = 80;
textureContext.filter = 'blur(5px)';
textureContext.fillStyle = '#B835FF';
textureContext.arc(texture.width / 2, texture.height / 2, 25, 0 * radian, 360 * radian, false);
textureContext.fill();
textureContext.closePath();
var drawMultipleCurves = function(ctx) {
if (!iconsLoaded) {
for (let i = 0; i < 600; i++) {
colorPoints.push({
x: Math.floor((Math.random() * 700) + 0),
xMove: Math.floor((Math.random() * 2) + 0),
yMove: Math.floor((Math.random() * 2) + 0),
y: Math.floor((Math.random() * 700) + 0),
scale: 0.2 + Math.random() * 0.8
});
iconsLoaded = true;
}
} else {
for (let i = 0; i < colorPoints.length; i++) {
if (frames === currentFrame) {
toggle = !toggle;
currentFrame = 0;
}
if (!toggle) {
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x + 5 : colorPoints[i].x = colorPoints[i].x - 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y + 5 : colorPoints[i].y = colorPoints[i].y - 5;
} else {
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x - 5 : colorPoints[i].x = colorPoints[i].x + 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y - 5 : colorPoints[i].y = colorPoints[i].y + 5;
}
ctx.drawImage(texture, colorPoints[i].x, colorPoints[i].y, texture.width * colorPoints[i].scale, texture.height * colorPoints[i].scale);
currentFrame = currentFrame + 1;
}
}
}
var animate = function() {
setTimeout(() => {
context.clearRect(0, 0, 400, 400);
context.beginPath();
drawMultipleCurves(context);
context.fill();
requestAnimationFrame(animate)
})
}
requestAnimationFrame(animate)
<canvas id="stage" width="400" height="400">
<p>Your browser doesn't support canvas.</p>
</canvas>
I've been trying to add a (code pen) animation on my website and I'm honestly not sure what I'm missing on this one. I have tried running it in jsfiddle as well and it tells me that delaunay is not defined. https://codepen.io/hduffin1/pen/QOMZJg I'm not too sure what I'm doing wrong since the code works inside of code pen and I have been able to replicate other ones that I've tried using from code pen but for whatever reason, I can't seem to get this one to work.
Html
<canvas id="stars" width="300" height="300"></canvas>
CSS
html,
body {
margin: 0;
padding: 0;
}
body {
background-color: #31102f; //#280B29
background: radial-gradient(
ellipse at center,
rgba(49, 16, 47, 1) 0%,
rgba(40, 11, 41, 1) 100%
);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 16rem;
height: 100vh;
z-index: 1;
}
JS
/**
* Stars
* Inspired by Steve Courtney's poster art for Celsius GS's Drifter - http://celsiusgs.com/drifter/posters.php
* by Cory Hughart - http://coryhughart.com
*/
// Settings
var particleCount = 40,
flareCount = 10,
motion = 0.05,
tilt = 0.05,
color = '#FFEED4',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 1, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.05,
renderParticles = true,
renderParticleGlare = true,
renderFlares = true,
renderLinks = true,
renderMesh = false,
flicker = true,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 1;
var canvas = document.getElementById('stars'),
//orbits = document.getElementById('orbits'),
context = canvas.getContext('2d'),
mouse = { x: 0, y: 0 },
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {x: 0, y: 0},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
// Fade in background
/*
var background = document.getElementById('background'),
bgImg = new Image(),
bgURL = '/img/background.jpg';
bgImg.onload = function() {
//console.log('background loaded');
background.style.backgroundImage = 'url("'+bgURL+'")';
background.className += ' loaded';
}
bgImg.src = bgURL;
*/
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x*c, p.y*c]);
}
//console.log(JSON.stringify(points));
// Delaunay triangulation
//var Delaunay = require('delaunay-fast');
vertices = Delaunay.triangulate(points);
//console.log(JSON.stringify(vertices));
// Create an array of "triangles" (groups of 3 indices)
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
//console.log(JSON.stringify(triangles));
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
//console.log(JSON.stringify(particles));
if (renderFlares) {
// Create flare positions
for (i = 0; i < flareCount; i++) {
flares.push(new Flare());
}
}
// Motion mode
//if (Modernizr && Modernizr.deviceorientation) {
if ('ontouchstart' in document.documentElement && window.DeviceOrientationEvent) {
console.log('Using device orientation');
window.addEventListener('deviceorientation', function(e) {
mouse.x = (canvas.clientWidth / 2) - ((e.gamma / 90) * (canvas.clientWidth / 2) * 2);
mouse.y = (canvas.clientHeight / 2) - ((e.beta / 90) * (canvas.clientHeight / 2) * 2);
//console.log('Center: x:'+(canvas.clientWidth/2)+' y:'+(canvas.clientHeight/2));
//console.log('Orientation: x:'+mouse.x+' ('+e.gamma+') y:'+mouse.y+' ('+e.beta+')');
}, true);
}
else {
// Mouse move listener
console.log('Using mouse movement');
document.body.addEventListener('mousemove', function(e) {
//console.log('moved');
mouse.x = e.clientX;
mouse.y = e.clientY;
});
}
// Random motion
if (randomMotion) {
//var SimplexNoise = require('simplex-noise');
//var simplex = new SimplexNoise();
}
// Animation loop
(function animloop(){
requestAnimFrame(animloop);
resize();
render();
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
//console.log('NOISE x:'+nPos.x+' y:'+nPos.y);
}
// Clear
context.clearRect(0, 0, canvas.width, canvas.height);
if (blurSize > 0) {
context.shadowBlur = blurSize;
context.shadowColor = color;
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
if (renderMesh) {
// Render all lines
context.beginPath();
for (var v = 0; v < vertices.length-1; v++) {
// Splits the array into triplets
if ((v + 1) % 3 === 0) { continue; }
var p1 = particles[vertices[v]],
p2 = particles[vertices[v+1]];
//console.log('Line: '+p1.x+','+p1.y+'->'+p2.x+','+p2.y);
var pos1 = position(p1.x, p1.y, p1.z),
pos2 = position(p2.x, p2.y, p2.z);
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x, pos2.y);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
}
if (renderLinks) {
// Possibly start a new link
if (random(0, linkChance) == linkChance) {
var length = random(linkLengthMin, linkLengthMax);
var start = random(0, particles.length-1);
startLink(start, length);
}
// Render existing links
// Iterate in reverse so that removing items doesn't affect the loop
for (var l = links.length-1; l >= 0; l--) {
if (links[l] && !links[l].finished) {
links[l].render();
}
else {
delete links[l];
}
}
}
if (renderFlares) {
// Render flares
for (var j = 0; j < flareCount; j++) {
flares[j].render();
}
}
/*
if (orbitTilt) {
var tiltX = -(((canvas.clientWidth / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * tilt),
tiltY = (((canvas.clientHeight / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * tilt);
orbits.style.transform = 'rotateY('+tiltX+'deg) rotateX('+tiltY+'deg)';
}
*/
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
function startLink(vertex, length) {
//console.log('LINK from '+vertex+' (length '+length+')');
links.push(new Link(vertex, length));
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0,4);
this.color = color;
this.opacity = random(0.1,1,true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
if (flicker) {
var newVal = random(-0.5, 0.5, true);
this.flicker += (newVal - this.flicker) / flickerSmoothing;
if (this.flicker > 0.5) this.flicker = 0.5;
if (this.flicker < -0.5) this.flicker = -0.5;
o += this.flicker;
if (o > 1) o = 1;
if (o < 0) o = 0;
}
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
/*
context.ellipse(pos.x, pos.y, r * 30, r, 90 * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
*/
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
var Flare = function() {
this.x = random(-0.25, 1.25, true);
this.y = random(-0.25, 1.25, true);
this.z = random(0,2);
this.color = color;
this.opacity = random(0.001, 0.01, true);
};
Flare.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * flareSizeMultiplier) + flareSizeBase) * (sizeRatio() / 1000);
// Feathered circles
/*
var grad = context.createRadialGradient(x+r,y+r,0,x+r,y+r,r);
grad.addColorStop(0, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(0.8, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = grad;
context.beginPath();
context.fillRect(x, y, r*2, r*2);
context.closePath();
*/
context.beginPath();
context.globalAlpha = this.opacity;
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.globalAlpha = 1;
};
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
Link.prototype.render = function() {
// Stages:
// 0. Vertex collection
// 1. Render line reaching from vertex to vertex
// 2. Fade out
// 3. Finished (delete me)
var i, p, pos, points;
switch (this.stage) {
// VERTEX COLLECTION STAGE
case 0:
// Grab the last member of the link
var last = particles[this.verts[this.verts.length-1]];
//console.log(JSON.stringify(last));
if (last && last.neighbors && last.neighbors.length > 0) {
// Grab a random neighbor
var neighbor = last.neighbors[random(0, last.neighbors.length-1)];
// If we haven't seen that particle before, add it to the link
if (this.verts.indexOf(neighbor) == -1) {
this.verts.push(neighbor);
}
// If we have seen that article before, we'll just wait for the next frame
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (0)');
this.stage = 3;
this.finished = true;
}
if (this.verts.length >= this.length) {
// Calculate all distances at once
for (i = 0; i < this.verts.length-1; i++) {
var p1 = particles[this.verts[i]],
p2 = particles[this.verts[i+1]],
dx = p1.x - p2.x,
dy = p1.y - p2.y,
dist = Math.sqrt(dx*dx + dy*dy);
this.distances.push(dist);
}
//console.log('Distances: '+JSON.stringify(this.distances));
//console.log('verts: '+this.verts.length+' distances: '+this.distances.length);
//console.log(this.verts[0]+' moving to stage 1');
this.stage = 1;
}
break;
// RENDER LINE ANIMATION STAGE
case 1:
if (this.distances.length > 0) {
points = [];
//var a = 1;
// Gather all points already linked
for (i = 0; i < this.linked.length; i++) {
p = particles[this.linked[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
var linkSpeedRel = linkSpeed * 0.00001 * canvas.width;
this.traveled += linkSpeedRel;
var d = this.distances[this.linked.length-1];
// Calculate last point based on linkSpeed and distance travelled to next point
if (this.traveled >= d) {
this.traveled = 0;
// We've reached the next point, add coordinates to array
//console.log(this.verts[0]+' reached vertex '+(this.linked.length+1)+' of '+this.verts.length);
this.linked.push(this.verts[this.linked.length]);
p = particles[this.linked[this.linked.length-1]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
if (this.linked.length >= this.verts.length) {
//console.log(this.verts[0]+' moving to stage 2 (1)');
this.stage = 2;
}
}
else {
// We're still travelling to the next point, get coordinates at travel distance
// http://math.stackexchange.com/a/85582
var a = particles[this.linked[this.linked.length-1]],
b = particles[this.verts[this.linked.length]],
t = d - this.traveled,
x = ((this.traveled * b.x) + (t * a.x)) / d,
y = ((this.traveled * b.y) + (t * a.y)) / d,
z = ((this.traveled * b.z) + (t * a.z)) / d;
pos = position(x, y, z);
//console.log(this.verts[0]+' traveling to vertex '+(this.linked.length+1)+' of '+this.verts.length+' ('+this.traveled+' of '+this.distances[this.linked.length]+')');
points.push([pos.x, pos.y]);
}
this.drawLine(points);
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (1)');
this.stage = 3;
this.finished = true;
}
break;
// FADE OUT STAGE
case 2:
if (this.verts.length > 1) {
if (this.fade < linkFade) {
this.fade++;
// Render full link between all vertices and fade over time
points = [];
var alpha = (1 - (this.fade / linkFade)) * linkOpacity;
for (i = 0; i < this.verts.length; i++) {
p = particles[this.verts[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
this.drawLine(points, alpha);
}
else {
//console.log(this.verts[0]+' moving to stage 3 (2a)');
this.stage = 3;
this.finished = true;
}
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (2b)');
this.stage = 3;
this.finished = true;
}
break;
// FINISHED STAGE
case 3:
default:
this.finished = true;
break;
}
};
Link.prototype.drawLine = function(points, alpha) {
if (typeof alpha !== 'number') alpha = linkOpacity;
if (points.length > 1 && alpha > 0) {
//console.log(this.verts[0]+': Drawing line '+alpha);
context.globalAlpha = alpha;
context.beginPath();
for (var i = 0; i < points.length-1; i++) {
context.moveTo(points[i][0], points[i][1]);
context.lineTo(points[i+1][0], points[i+1][1]);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
context.globalAlpha = 1;
}
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
//value = simplex.noise2D(nScale * cosA + nScale, nScale * sinA + nScale),
//rad = nRad + value;
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
When I entered 'https://codepen.io/hduffin1/pen/QOMZJg', 'delaunay.js' is included in the setting.
Add the following script and it should work.
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
/**
* Stars
* Inspired by Steve Courtney's poster art for Celsius GS's Drifter - http://celsiusgs.com/drifter/posters.php
* by Cory Hughart - http://coryhughart.com
*/
// Settings
var particleCount = 40,
flareCount = 10,
motion = 0.05,
tilt = 0.05,
color = '#FFEED4',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 1, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.05,
renderParticles = true,
renderParticleGlare = true,
renderFlares = true,
renderLinks = true,
renderMesh = false,
flicker = true,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 1;
var canvas = document.getElementById('stars'),
//orbits = document.getElementById('orbits'),
context = canvas.getContext('2d'),
mouse = { x: 0, y: 0 },
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {x: 0, y: 0},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
// Fade in background
/*
var background = document.getElementById('background'),
bgImg = new Image(),
bgURL = '/img/background.jpg';
bgImg.onload = function() {
//console.log('background loaded');
background.style.backgroundImage = 'url("'+bgURL+'")';
background.className += ' loaded';
}
bgImg.src = bgURL;
*/
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x*c, p.y*c]);
}
//console.log(JSON.stringify(points));
// Delaunay triangulation
//var Delaunay = require('delaunay-fast');
vertices = Delaunay.triangulate(points);
//console.log(JSON.stringify(vertices));
// Create an array of "triangles" (groups of 3 indices)
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
//console.log(JSON.stringify(triangles));
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
//console.log(JSON.stringify(particles));
if (renderFlares) {
// Create flare positions
for (i = 0; i < flareCount; i++) {
flares.push(new Flare());
}
}
// Motion mode
//if (Modernizr && Modernizr.deviceorientation) {
if ('ontouchstart' in document.documentElement && window.DeviceOrientationEvent) {
console.log('Using device orientation');
window.addEventListener('deviceorientation', function(e) {
mouse.x = (canvas.clientWidth / 2) - ((e.gamma / 90) * (canvas.clientWidth / 2) * 2);
mouse.y = (canvas.clientHeight / 2) - ((e.beta / 90) * (canvas.clientHeight / 2) * 2);
//console.log('Center: x:'+(canvas.clientWidth/2)+' y:'+(canvas.clientHeight/2));
//console.log('Orientation: x:'+mouse.x+' ('+e.gamma+') y:'+mouse.y+' ('+e.beta+')');
}, true);
}
else {
// Mouse move listener
console.log('Using mouse movement');
document.body.addEventListener('mousemove', function(e) {
//console.log('moved');
mouse.x = e.clientX;
mouse.y = e.clientY;
});
}
// Random motion
if (randomMotion) {
//var SimplexNoise = require('simplex-noise');
//var simplex = new SimplexNoise();
}
// Animation loop
(function animloop(){
requestAnimFrame(animloop);
resize();
render();
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
//console.log('NOISE x:'+nPos.x+' y:'+nPos.y);
}
// Clear
context.clearRect(0, 0, canvas.width, canvas.height);
if (blurSize > 0) {
context.shadowBlur = blurSize;
context.shadowColor = color;
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
if (renderMesh) {
// Render all lines
context.beginPath();
for (var v = 0; v < vertices.length-1; v++) {
// Splits the array into triplets
if ((v + 1) % 3 === 0) { continue; }
var p1 = particles[vertices[v]],
p2 = particles[vertices[v+1]];
//console.log('Line: '+p1.x+','+p1.y+'->'+p2.x+','+p2.y);
var pos1 = position(p1.x, p1.y, p1.z),
pos2 = position(p2.x, p2.y, p2.z);
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x, pos2.y);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
}
if (renderLinks) {
// Possibly start a new link
if (random(0, linkChance) == linkChance) {
var length = random(linkLengthMin, linkLengthMax);
var start = random(0, particles.length-1);
startLink(start, length);
}
// Render existing links
// Iterate in reverse so that removing items doesn't affect the loop
for (var l = links.length-1; l >= 0; l--) {
if (links[l] && !links[l].finished) {
links[l].render();
}
else {
delete links[l];
}
}
}
if (renderFlares) {
// Render flares
for (var j = 0; j < flareCount; j++) {
flares[j].render();
}
}
/*
if (orbitTilt) {
var tiltX = -(((canvas.clientWidth / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * tilt),
tiltY = (((canvas.clientHeight / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * tilt);
orbits.style.transform = 'rotateY('+tiltX+'deg) rotateX('+tiltY+'deg)';
}
*/
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
function startLink(vertex, length) {
//console.log('LINK from '+vertex+' (length '+length+')');
links.push(new Link(vertex, length));
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0,4);
this.color = color;
this.opacity = random(0.1,1,true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
if (flicker) {
var newVal = random(-0.5, 0.5, true);
this.flicker += (newVal - this.flicker) / flickerSmoothing;
if (this.flicker > 0.5) this.flicker = 0.5;
if (this.flicker < -0.5) this.flicker = -0.5;
o += this.flicker;
if (o > 1) o = 1;
if (o < 0) o = 0;
}
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
/*
context.ellipse(pos.x, pos.y, r * 30, r, 90 * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
*/
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
var Flare = function() {
this.x = random(-0.25, 1.25, true);
this.y = random(-0.25, 1.25, true);
this.z = random(0,2);
this.color = color;
this.opacity = random(0.001, 0.01, true);
};
Flare.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * flareSizeMultiplier) + flareSizeBase) * (sizeRatio() / 1000);
// Feathered circles
/*
var grad = context.createRadialGradient(x+r,y+r,0,x+r,y+r,r);
grad.addColorStop(0, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(0.8, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = grad;
context.beginPath();
context.fillRect(x, y, r*2, r*2);
context.closePath();
*/
context.beginPath();
context.globalAlpha = this.opacity;
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.globalAlpha = 1;
};
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
Link.prototype.render = function() {
// Stages:
// 0. Vertex collection
// 1. Render line reaching from vertex to vertex
// 2. Fade out
// 3. Finished (delete me)
var i, p, pos, points;
switch (this.stage) {
// VERTEX COLLECTION STAGE
case 0:
// Grab the last member of the link
var last = particles[this.verts[this.verts.length-1]];
//console.log(JSON.stringify(last));
if (last && last.neighbors && last.neighbors.length > 0) {
// Grab a random neighbor
var neighbor = last.neighbors[random(0, last.neighbors.length-1)];
// If we haven't seen that particle before, add it to the link
if (this.verts.indexOf(neighbor) == -1) {
this.verts.push(neighbor);
}
// If we have seen that particle before, we'll just wait for the next frame
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (0)');
this.stage = 3;
this.finished = true;
}
if (this.verts.length >= this.length) {
// Calculate all distances at once
for (i = 0; i < this.verts.length-1; i++) {
var p1 = particles[this.verts[i]],
p2 = particles[this.verts[i+1]],
dx = p1.x - p2.x,
dy = p1.y - p2.y,
dist = Math.sqrt(dx*dx + dy*dy);
this.distances.push(dist);
}
//console.log('Distances: '+JSON.stringify(this.distances));
//console.log('verts: '+this.verts.length+' distances: '+this.distances.length);
//console.log(this.verts[0]+' moving to stage 1');
this.stage = 1;
}
break;
// RENDER LINE ANIMATION STAGE
case 1:
if (this.distances.length > 0) {
points = [];
//var a = 1;
// Gather all points already linked
for (i = 0; i < this.linked.length; i++) {
p = particles[this.linked[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
var linkSpeedRel = linkSpeed * 0.00001 * canvas.width;
this.traveled += linkSpeedRel;
var d = this.distances[this.linked.length-1];
// Calculate last point based on linkSpeed and distance travelled to next point
if (this.traveled >= d) {
this.traveled = 0;
// We've reached the next point, add coordinates to array
//console.log(this.verts[0]+' reached vertex '+(this.linked.length+1)+' of '+this.verts.length);
this.linked.push(this.verts[this.linked.length]);
p = particles[this.linked[this.linked.length-1]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
if (this.linked.length >= this.verts.length) {
//console.log(this.verts[0]+' moving to stage 2 (1)');
this.stage = 2;
}
}
else {
// We're still travelling to the next point, get coordinates at travel distance
// http://math.stackexchange.com/a/85582
var a = particles[this.linked[this.linked.length-1]],
b = particles[this.verts[this.linked.length]],
t = d - this.traveled,
x = ((this.traveled * b.x) + (t * a.x)) / d,
y = ((this.traveled * b.y) + (t * a.y)) / d,
z = ((this.traveled * b.z) + (t * a.z)) / d;
pos = position(x, y, z);
//console.log(this.verts[0]+' traveling to vertex '+(this.linked.length+1)+' of '+this.verts.length+' ('+this.traveled+' of '+this.distances[this.linked.length]+')');
points.push([pos.x, pos.y]);
}
this.drawLine(points);
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (1)');
this.stage = 3;
this.finished = true;
}
break;
// FADE OUT STAGE
case 2:
if (this.verts.length > 1) {
if (this.fade < linkFade) {
this.fade++;
// Render full link between all vertices and fade over time
points = [];
var alpha = (1 - (this.fade / linkFade)) * linkOpacity;
for (i = 0; i < this.verts.length; i++) {
p = particles[this.verts[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
this.drawLine(points, alpha);
}
else {
//console.log(this.verts[0]+' moving to stage 3 (2a)');
this.stage = 3;
this.finished = true;
}
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (2b)');
this.stage = 3;
this.finished = true;
}
break;
// FINISHED STAGE
case 3:
default:
this.finished = true;
break;
}
};
Link.prototype.drawLine = function(points, alpha) {
if (typeof alpha !== 'number') alpha = linkOpacity;
if (points.length > 1 && alpha > 0) {
//console.log(this.verts[0]+': Drawing line '+alpha);
context.globalAlpha = alpha;
context.beginPath();
for (var i = 0; i < points.length-1; i++) {
context.moveTo(points[i][0], points[i][1]);
context.lineTo(points[i+1][0], points[i+1][1]);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
context.globalAlpha = 1;
}
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
//value = simplex.noise2D(nScale * cosA + nScale, nScale * sinA + nScale),
//rad = nRad + value;
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
html,
body {
margin: 0;
padding: 0;
}
body {
background-color: #31102F;
background: radial-gradient(ellipse at center, #31102f 0%, #280b29 100%);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 16rem;
height: 100vh;
z-index: 1;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<canvas id="stars" width="300" height="300"></canvas>
this might be somewhat difficult but i wil still ask, so i made a starfield ,now what i want to do is to have my stars( a pair each) connected to eachother by a line ,now this line will expand as the stars move forward and disappear when the stars move out of the canvas .any help would be appreciated here this is difficult i have the logic but i seem unable to follow the correct way to implement it
function randomRange(minVal, maxVal) {
return Math.floor(Math.random() * (maxVal - minVal - 1)) + minVal;
}
function initStars() {
for (var i = 0; i < stars.length; i++) {
stars[i] = {
x: randomRange(-25, 25),
y: randomRange(-25, 25),
z: randomRange(1, MAX_DEPTH)
}
}
}
function degToRad(deg) {
radians = (deg * Math.PI / 180) - Math.PI / 2;
return radians;
}
function animate() {
var halfWidth = canvas.width / 2;
var halfHeight = canvas.height / 2;
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++) {
stars[i].z -= 0.2;
if (stars[i].z <= 0) {
stars[i].x = randomRange(-25, 25);
stars[i].y = randomRange(-25, 25);
stars[i].z = MAX_DEPTH;
}
var k = 128.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;
if (px >= 0 && px <= 1500 && py >= 0 && py <= 1500) {
var size = (1 - stars[i].z / 32.0) * 5;
var shade = parseInt((1 - stars[i].z / 32.0) * 750);
ctx.fillStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
ctx.beginPath();
ctx.arc(px, py, size, degToRad(0), degToRad(360));
ctx.fill();
}
}
}
function animate() {
var halfWidth = canvas.width / 2;
var halfHeight = canvas.height / 2;
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++) {
stars[i].z -= 0.2;
if (stars[i].z <= 0) {
stars[i].x = randomRange(-25, 25);
stars[i].y = randomRange(-25, 25);
stars[i].z = MAX_DEPTH;
}
var k = 128.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;
if (px >= 0 && px <= 1500 && py >= 0 && py <= 1500) {
var size = (1 - stars[i].z / 32.0) * 5;
var shade = parseInt((1 - stars[i].z / 32.0) * 750);
ctx.fillStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
ctx.beginPath();
ctx.arc(px, py, size, degToRad(0), degToRad(360));
ctx.fill();
}
}
}
<!DOCTYPE html5>
<html>
<head>
<title>stars</title>
<script src="convergis.js"></script>
<script>
MAX_DEPTH = 32;
var canvas, ctx;
var stars = new Array(500);
window.onload = function() {
canvas = document.getElementById("tutorial");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
initStars();
setInterval(animate,17);
}
}
</script>
</head>
<body>
<canvas id='tutorial' width='1500' height='1500'>
</canvas>
</body>
</html>
You could just say you want a lightspeed effect!
One way very cheap way to do it is to paint the background with some transparency. You can also render a set of points close together in order to make the illusion of the effect.
The good way to do it is shaders since they will allow you to add glow and some other nice image trickery that will make it look better. Here is a good example: https://www.shadertoy.com/view/Xdl3D2
Below I used the canvas api lineTo and even with a fixed line width, it's a pretty good final result.
var MAX_DEPTH = 64;
var LINELENGTH = 0.1;
var stars = new Array(500);
var canvas = document.getElementById("tutorial");
canvas.width = innerWidth;
canvas.height = innerHeight;
var ctx = canvas.getContext("2d");
initStars();
setInterval(animate,17);
function randomRange(minVal, maxVal) {
return Math.floor(Math.random() * (maxVal - minVal - 1)) + minVal;
}
function initStars() {
for (var i = 0; i < stars.length; i++) {
stars[i] = {
x: randomRange(-25, 25),
y: randomRange(-25, 25),
z: randomRange(1, MAX_DEPTH)
}
}
}
function degToRad(deg) {
radians = (deg * Math.PI / 180) - Math.PI / 2;
return radians;
}
function animate() {
var halfWidth = canvas.width / 2;
var halfHeight = canvas.height / 2;
ctx.fillStyle = "rgba(0,0,0,1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < stars.length; i++) {
stars[i].z -= 0.5;
if (stars[i].z <= 0) {
stars[i].x = randomRange(-25, 25);
stars[i].y = randomRange(-25, 25);
stars[i].z = MAX_DEPTH;
}
var k = 254.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;
if (px >= 0 && px <= 1500 && py >= 0 && py <= 1500) {
var size = (1 - stars[i].z / 32.0) * 2;
var shade = parseInt((1 - stars[i].z / 32.0) * 750);
ctx.strokeStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
ctx.lineWidth = size;
ctx.beginPath();
ctx.moveTo(px,py);
var ox = size * (px - halfWidth) * LINELENGTH;
var oy = size * (py - halfHeight) * LINELENGTH;
ctx.lineTo(px + ox, py + oy);
ctx.stroke();
}
}
}
<canvas id='tutorial' width='1500' height='1500'></canvas>
I have been creating a clone of agar.io and I don't understand why the circles start vibrating when they touch each other. Below is my code:
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var dist = Math.sqrt(Math.pow(blob2.x - blob1.x, 2) + Math.pow(blob2.y - blob1.y, 2));
if (dist < blob1.mass + blob2.mass) {
if (this.blobs[i].x < this.blobs[j].x) {
this.blobs[i].x--;
} else if (this.blobs[i].x > this.blobs[j].x) {
this.blobs[i].x++;
}
if (this.blobs[i].y < this.blobs[j].y) {
this.blobs[i].y--;
} else if ((this.blobs[i].y > this.blobs[j].y)) {
this.blobs[i].y++;
}
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
Separating circles
Your separation code was not correct. Use the vector between them to get the new pos.
The vector between them
To find if two circles are intercepting find the length of the vector from one to the next
The two circles.
var cir1 = {x : 100, y : 100, r : 120}; // r is the radius
var cir2 = {x : 250, y : 280, r : 150}; // r is the radius
The vector from cir2 to cir1
var vx = cir2.x - cir1.x;
var vy = cir2.y - cir1.y;
The length of the vector
var len = Math.sqrt(x * x + y * y);
// or use the ES6 Math.hypot function
/* var len = Math.hypot(x,y); */
The circles overlap if the sum of the radii is greater than the length of the vector between them
if(cir1.r + cir2.r > len){ // circles overlap
Normalise the vector
If they overlap you need to move one away from the other. There are many ways to do this, the simplest way is to move one circle along the line between them.
First normalise the vector from cir1 to cir2 by dividing by its (vector) length.
vx \= len;
vy \= len;
Note that the length could be zero. If this happens then you will get NaN in further calculations. If you suspect you may get one circle at the same location as another the easiest way to deal with the zero move one circle a little.
// replace the two lines above with
if(len === 0){ // circles are on top of each other
vx = 1; // move the circle (abstracted into the vector)
}else{
vx \= len; // normalise the vector
vy \= len;
}
Move circle/s to just touch
Now you have the normalised vector which is 1 unit long you can make it any length you need by multiplying the two scalars vx, vy with the desired length which in this case is the sum of the two circles radii.
var mx = vx * (cir1.r + cir2.r); // move distance
var my = vy * (cir1.r + cir2.r);
.Only use one of the following methods.
You can now position one of the circles the correct distance so that they just touch
// move cir1
cir1.x = cir2.x - mx;
cir1.y = cir2.y - my;
Or move the second circle
cir2.x = cir1.x + mx;
cir2.y = cir1.y + my;
Or move both circles but you will have to first find the proportional center between the two
var pLen = cir1.r / (cir1.r + cir2.r); // find the ratio of the radii
var cx = cir1.x + pLen * vx * len; // find the proportional center between
var cy = cir1.y + pLen * vy * len; // the two circles
Then move both circles away from that point by their radii
cir1.x = cx - vx * cir1.r; // move circle 1 away from the shared center
cir1.y = cy - vy * cir1.r;
cir2.x = cx + vx * cir2.r; // move circle 2 away from the shared center
cir2.y = cy + vy * cir2.r;
DEMO
Copy of OP's snippet with mods to fix problem by moving the the first circle blob1 away from the second blob2 and assuming they will never be at the same spot (no divide by zero)
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var x = blob2.x - blob1.x; // get the vector from blob1 to blob2
var y = blob2.y - blob1.y; //
var dist = Math.sqrt(x * x + y * y); // get the distance between the two blobs
if (dist < blob1.mass + blob2.mass) { // if the distance is less than the 2 radius
// if there is overlap move blob one along the line between the two the distance of the two radius
x /= dist; // normalize the vector. This makes the vector 1 unit long
y /= dist;
// multiplying the normalised vector by the correct distance between the two
// and subtracting that distance from the blob 2 give the new pos of
// blob 1
blob1.x = blob2.x - x * (blob1.mass + blob2.mass);
blob1.y = blob2.y - y * (blob1.mass + blob2.mass);
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var dist = Math.sqrt(Math.pow(blob2.x - blob1.x, 2) + Math.pow(blob2.y - blob1.y, 2));
if (dist < blob1.mass + blob2.mass) {
if (this.blobs[i].x < this.blobs[j].x) {
this.blobs[i].x--;
} else if (this.blobs[i].x > this.blobs[j].x) {
this.blobs[i].x++;
}
if (this.blobs[i].y < this.blobs[j].y) {
this.blobs[i].y--;
} else if ((this.blobs[i].y > this.blobs[j].y)) {
this.blobs[i].y++;
}
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
I am trying to create a blackhole simulation, where all the balls that are outside of it go away from it at a given speed and those that fall on it are dragged towards the circle until they reach the center of it, where they would stop and disappear, here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>blackhole simulation escape velocity</title>
<script>
var canvas, ctx;
var blackhole;
var circle;
var circles = new Array();
var G = 6.67e-11, //gravitational constant
pixel_G = G / 1e-11,
c = 3e8, //speed of light (m/s)
M = 12e31, // masseof the blackhole in kg (60 solar masses)
pixel_M = M / 1e32
Rs = (2 * G * M) / 9e16, //Schwarzchild radius
pixel_Rs = Rs / 1e3, // scaled radius
ccolor = 128;
function update() {
var pos, i, distance, somethingMoved = false;
for (i = 0; i < circles.length; i++) {
pos = circles[i].position;
distance = Math.sqrt(((pos.x - 700) * (pos.x - 700)) + ((pos.y - 400) * (pos.y - 400)));
if (distance > pixel_Rs-5 ) {
var delta = new Vector2D(0, 0);
var forceDirection = Math.atan2(pos.y - 400, pos.x - 700);
var evelocity = Math.sqrt((2 * pixel_G * pixel_M) / (distance * 1e-2));
delta.x += Math.cos(forceDirection) * evelocity;
delta.y += Math.sin(forceDirection) * evelocity;
pos.x += delta.x;
pos.y += delta.y;
somethingMoved = true;
} else {
var delta2 = new Vector2D (0,0);
var forceDirection2 = Math.atan2(pos.y - 400, pos.x - 700);
var g = (pixel_G*pixel_M)/(distance*distance*1e2);
delta2.x += Math.cos(forceDirection2)*g;
delta2.y += Math.sin(forceDirection2)*g;
pos.x -= delta2.x;
pos.y -= delta2.y;
somethingMoved = true;
circles[i].color -= 1;
if (pos.x == 700 && pos.y == 400){
somethingMoved = false;
};
}
}
if (somethingMoved) {
drawEverything();
requestAnimationFrame(update);
};
}
function drawEverything() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
blackhole.draw(ctx);
for (var i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
}
}
function init(event) {
canvas = document.getElementById("space");
ctx = canvas.getContext('2d');
blackhole = new Ball(pixel_Rs, { x: 700,
y: 400 }, 0);
for (var i = 0; i < 200; i++) {
var vec2D = new Vector2D(Math.floor(Math.random() * 1400), Math.floor(Math.random() * 800));
circle = new Ball(5, vec2D, ccolor);
circles.push(circle);
}
drawEverything();
requestAnimationFrame(update);
}
function Ball(radius, position, color) {
this.radius = radius;
this.position = position;
this.color = color;
}
Ball.prototype.draw = function(ctx) {
var c=parseInt(this.color);
ctx.fillStyle = 'rgba(' + c + ',' + c + ',' + c + ',1)';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
};
function Vector2D(x, y) {
this.x = x;
this.y = y;
}
function onClick (){
canvas = document.getElementById ('space');
ctx = canvas.getContext ('2d')
canvas.addEventListener ("mousedown", init, false)
blackhole = new Ball (5, {x: 700,
y: 400 }, 0);
blackhole.draw (ctx) ;
}
window.onload = onClick;
</script>
<style>
body {
background-color:#021c36 ;
margin: 0px;
}
</style>
</head>
<body>
<canvas id = "space", width = "1400", height = "800">
</canvas>
</body>
</html>
Now as you can see, I created a second variable called delta2, but the problem is that it can't update the position of the circles, which in term makes it impossible to move the circle, can someone tell me what is wrong. Also, how can I make the big black circle after a certain amount of time, i know i probably should create a timer, but i don't know how they work
The gravity is too weak. I put a pseudo gravity to demonstrate.
var canvas, ctx;
var blackhole;
var circle;
var circles = new Array();
var bh = {
w:500,
h:300
};
bh.cx = Math.floor(bh.w/2);
bh.cy = Math.floor(bh.h/2)
var G = 6.67e-11, //gravitational constant
pixel_G = G / 1e-11,
c = 3e8, //speed of light (m/s)
M = 12e31, // masseof the blackhole in kg (60 solar masses)
pixel_M = M / 1e32
Rs = (2 * G * M) / 9e16, //Schwarzchild radius
pixel_Rs = Rs / 1e3, // scaled radius
ccolor = 128;
function update() {
var pos, i, distance, somethingMoved = false;
for (i = 0; i < circles.length; i++) {
pos = circles[i].position;
distance = Math.sqrt(((pos.x - bh.cx) * (pos.x - bh.cx)) + ((pos.y - bh.cy) * (pos.y - bh.cy)));
if (distance > pixel_Rs - 5) {
var delta = new Vector2D(0, 0);
var forceDirection = Math.atan2(pos.y - bh.cy, pos.x - bh.cx);
var evelocity = Math.sqrt((2 * pixel_G * pixel_M) / (distance * 1e-2));
delta.x += Math.cos(forceDirection) * evelocity;
delta.y += Math.sin(forceDirection) * evelocity;
pos.x += delta.x;
pos.y += delta.y;
somethingMoved = true;
} else {
var delta2 = new Vector2D(0, 0);
var forceDirection2 = Math.atan2(pos.y - bh.cy, pos.x - bh.cx);
// FIX THIS!!!
var g = 1;//(pixel_G * pixel_M) / (distance * distance * 1e2);
delta2.x += Math.cos(forceDirection2) * g;
delta2.y += Math.sin(forceDirection2) * g;
pos.x -= delta2.x;
pos.y -= delta2.y;
somethingMoved = true;
circles[i].color -= 1;
if (pos.x == bh.cx && pos.y == bh.cy) {
somethingMoved = false;
};
}
}
if (somethingMoved) {
drawEverything();
requestAnimationFrame(update);
};
}
function drawEverything() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
blackhole.draw(ctx);
for (var i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
}
}
function init(event) {
canvas = document.getElementById("space");
canvas.width = bh.w;
canvas.height = bh.h;
ctx = canvas.getContext('2d');
blackhole = new Ball(5, { //pixel_Rs, {
x: bh.cx,
y: bh.cy
}, 0);
for (var i = 0; i < 200; i++) {
var vec2D = new Vector2D(Math.floor(Math.random() * bh.w), Math.floor(Math.random() * bh.h));
circle = new Ball(5, vec2D, ccolor);
circles.push(circle);
}
drawEverything();
requestAnimationFrame(update);
}
function Ball(radius, position, color) {
this.radius = radius;
this.position = position;
this.color = color;
}
Ball.prototype.draw = function(ctx) {
var c = parseInt(this.color);
ctx.fillStyle = 'rgba(' + c + ',' + c + ',' + c + ',1)';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
};
function Vector2D(x, y) {
this.x = x;
this.y = y;
}
function onClick() {
canvas = document.getElementById('space');
ctx = canvas.getContext('2d')
canvas.addEventListener("mousedown", init, false)
blackhole = new Ball(5, {
x: bh.cx,
y: bh.cy
}, 0);
blackhole.draw(ctx);
}
window.onload = onClick;
body {
background-color: #021c36;
margin: 0px;
}
<canvas id="space" , width="700" , height="400"></canvas>