Ultimately I like to know which object was being clicked in a canvas, and I wrote the script:
dist.push(Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110));
dist.push(Math.abs(x-ball2.x-88.5) + Math.abs(y-ball2.y-110));
dist.push(Math.abs(x-ball3.x-88.5) + Math.abs(y-ball3.y-110));
function sortNumber(a,b) {
return a - b;
}
dist.sort(sortNumber);
Obviously this only give me the sort of the number but I need it to connect with ball1, ball2, and ball3. I figure I could nest an array for this but I haven't figured out the logic...
Or perhaps my approach was wrong to begin with?
P.S., obviously if I only have three balls I can do this:
var b1d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
var b2d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
var b3d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
dist.push(b1d, b2d, b3d);
function sortNumber(a,b) {
return a - b;
}
dist.sort(sortNumber);
if (dist[0] == b1d) {
alert('b1');
} else if (dist[0] == b2d) {
alert('b2');
} else if (dist[0] == d3d) {
alert('b3');
} else {
alert('####');
}
But if I have hundreds of balls this probably isn't the best way...
How you search depends on what you do with the balls, and how you place them.
Here a simple example that you can click a ball and bring it in the foreground.
We create 200 balls.
To find the correct ball we start searching in an one time sorted array, based on the z-index of the ball (from the balls on the back to the balls on the frond), as you can not click the balls on the back, we search starting from the last element of the array.
In this example, this is a good solution, but in your applications it may not be, it depends on many things, like if the balls overlap, or if the possition is not random.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var getRandomColor = function() {
// Code from : http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var createRandomBall = function(){
// The ball object
var ball = {};
// Random radius (min 5 max 30)
ball.radius = Math.floor((Math.random() * 25) + 5);
ball.radius2 = Math.pow(ball.radius, 2);
// Random x position
ball.x = Math.floor((Math.random() * (canvas.width - ball.radius*2)) + ball.radius);
// Random y position
ball.y = Math.floor((Math.random() * (canvas.height - ball.radius*2)) + ball.radius);
// Random color
ball.color = getRandomColor();
return ball;
}
// Create many balls
var ballList = [];
var tmp_ball;
for (var i = 0; i < 200; i++) {
// Make a random ball
tmp_ball = createRandomBall();
// Add to the list
ballList.push(tmp_ball);
}
// Render the balls
var renderBalls = function(){
var ball;
// For each ball
for (var i = 0; i < ballList.length; i++) {
ball = ballList[i];
// Stroke ball
context.beginPath();
context.arc(ball.x, ball.y, ball.radius - 1, 0, 2 * Math.PI, false);
context.fillStyle = ball.color;
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#000000';
context.stroke();
}
}
// Render balls
renderBalls();
// Add click event
canvas.addEventListener('click', function(event){
// Get x and y of click
var click = {
x : event.clientX - canvas.offsetLeft,
y : event.clientY - canvas.offsetTop
};
var ball = null;
// Find clicked ball
// we search the array from the back,
// because on the back balls are over the frond balls
for (var i = ballList.length - 1; i >= 0; i--) {
if( Math.pow(click.x - ballList[i].x, 2) + Math.pow(click.y - ballList[i].y, 2) <= ballList[i].radius2 ){
ball = i;
break;
}
}
// If no ball found return
if(ball == null){
console.log("No ball clicked");
return;
}
// else ball found
ball = ballList.splice(ball, 1)[0];
// Else position ball on the frond
ballList.push(ball);
// Re-render
renderBalls();
}, false);
*{
padding: 0px;
margin: 0px;
}
#myCanvas{
position: absolute;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
}
<canvas id="myCanvas" width="600" height="200"></canvas>
A general solution is to make a map table, a grid of your canvas, and on each cell add the corresponding balls, so that you can match in which grid box the click was made and check a smaller group of balls.
So for example, lets say that you want when you click, all the balls under the click to change color. Here is an example with mapping the balls on smaller groups, we make a grid of 10 columns and 5 lines. Each ball may be in more than 1 group. We create 400 balls.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var getRandomColor = function() {
// Code from : http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var gridSize = {x:10, y:5};
var gridList = [];
// Rows
for (var i = 0; i < gridSize.y; i++) {
gridList.push([]);
// Columns
for (var j = 0; j < gridSize.x; j++) {
gridList[i].push([]);
}
}
var createRandomBall = function(){
// The ball object
var ball = {};
// Random radius (min 5 max 30)
ball.radius = Math.floor((Math.random() * 25) + 5);
ball.radius2 = Math.pow(ball.radius, 2);
// Random x position
ball.x = Math.floor((Math.random() * (canvas.width - ball.radius*2)) + ball.radius);
// Random y position
ball.y = Math.floor((Math.random() * (canvas.height - ball.radius*2)) + ball.radius);
// Random color
ball.color = getRandomColor();
// Map ball - find cells that the circle overlap
grid = {
x : {
min : Math.floor((ball.x - ball.radius)*gridSize.x/canvas.width),
max : Math.floor((ball.x + ball.radius)*gridSize.x/canvas.width)
},
y : {
min : Math.floor((ball.y - ball.radius)*gridSize.y/canvas.height),
max : Math.floor((ball.y + ball.radius)*gridSize.y/canvas.height)
}
}
for (var y = grid.y.min; y <= grid.y.max; y++) {
for (var x = grid.x.min; x <= grid.x.max; x++) {
gridList[y][x].push(ball);
}
}
return ball;
}
// Create many balls
var ballList = [];
var tmp_ball;
for (var i = 0; i < 400; i++) {
// Make a random ball
tmp_ball = createRandomBall();
// Add to the list
ballList.push(tmp_ball);
}
// Render the balls
var renderBalls = function(){
var ball;
// For each ball
for (var i = 0; i < ballList.length; i++) {
ball = ballList[i];
// Stroke ball
context.beginPath();
context.arc(ball.x, ball.y, ball.radius - 1, 0, 2 * Math.PI, false);
context.fillStyle = ball.color;
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#000000';
context.stroke();
}
for (var i = 0; i < gridSize.x + 1; i++) {
context.beginPath();
context.moveTo((canvas.width/gridSize.x)*i, 0);
context.lineTo((canvas.width/gridSize.x)*i, canvas.height);
context.stroke();
}
for (var i = 0; i < gridSize.y + 1; i++) {
context.beginPath();
context.moveTo(0, (canvas.height/gridSize.y)*i);
context.lineTo(canvas.width, (canvas.height/gridSize.y)*i);
context.stroke();
}
}
// Render balls
renderBalls();
// Add click event
canvas.addEventListener('click', function(event){
// Get x and y of click
var click = {
x : event.clientX - canvas.offsetLeft,
y : event.clientY - canvas.offsetTop
};
var grid = {
x : Math.floor(click.x*gridSize.x/canvas.width),
y : Math.floor(click.y*gridSize.y/canvas.height)
};
var ball = 0;
var smallerList = gridList[grid.y][grid.x];
// Find clicked ball
for (var i = smallerList.length - 1; i >= 0; i--) {
if( Math.pow(click.x - smallerList[i].x, 2) + Math.pow(click.y - smallerList[i].y, 2) <= smallerList[i].radius2 ){
ball++;
smallerList[i].color = getRandomColor();
}
}
console.log("Group["+grid.y+"]["+grid.x+"], " + smallerList.length + " balls in group, clicked " + ball + " balls");
// If no ball found return
if(ball == 0){
return;
}
// Re-render
renderBalls();
}, false);
*{
padding: 0px;
margin: 0px;
}
#myCanvas{
position: absolute;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
}
<canvas id="myCanvas" width="600" height="200"></canvas>
Related
So I've been reading this post which I found is the closest to what I want. Basically, I want two different text elements (two different words) moving around the page randomly and also bouncing off each other when they come in contact.
I have tried to edit the code so that it's black and have only one text element. However how can I also include a second text element (a different word) moving randomly as well?
Also how can I change it to Roboto Mono font?
// Return random RGB color string:
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).slice(0);
}
// Poor man's box physics update for time step dt:
function doPhysics(boxes, width, height, dt) {
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Update positions:
box.x += box.dx * dt;
box.y += box.dy * dt;
// Handle boundary collisions:
if (box.x < 0) {
box.x = 0;
box.dx = -box.dx;
} else if (box.x + box.width > width) {
box.x = width - box.width;
box.dx = -box.dx;
}
if (box.y < 0) {
box.y = 0;
box.dy = -box.dy;
} else if (box.y + box.height > height) {
box.y = height - box.height;
box.dy = -box.dy;
}
}
// Handle box collisions:
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
var box1 = boxes[i];
var box2 = boxes[j];
var dx = Math.abs(box1.x - box2.x);
var dy = Math.abs(box1.y - box2.y);
// Check for overlap:
if (2 * dx < (box1.width + box2.width ) &&
2 * dy < (box1.height + box2.height)) {
// Swap dx if moving towards each other:
if ((box1.x > box2.x) == (box1.dx < box2.dx)) {
var swap = box1.dx;
box1.dx = box2.dx;
box2.dx = swap;
}
// Swap dy if moving towards each other:
if ((box1.y > box2.y) == (box1.dy < box2.dy)) {
var swap = box1.dy;
box1.dy = box2.dy;
box2.dy = swap;
}
}
}
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Initialize random boxes:
var boxes = [];
for (var i = 0; i < 1; i++) {
var box = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
width: 50,
height: 20,
dx: (Math.random() - 0.5) * 0.3,
dy: (Math.random() - 0.5) * 0.3
};
boxes.push(box);
}
// Initialize random color and set up interval:
var color = randomColor();
setInterval(function() {
color = randomColor();
}, 450);
// Update physics at fixed rate:
var last = performance.now();
setInterval(function(time) {
var now = performance.now();
doPhysics(boxes, canvas.width, canvas.height, now - last);
last = now;
}, 50);
// Draw animation frames at optimal frame rate:
function draw(now) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Interpolate position:
var x = box.x + box.dx * (now - last);
var y = box.y + box.dy * (now - last);
context.beginPath();
context.fillStyle = color;
context.font = "40px 'Roboto Mono'";
context.textBaseline = "hanging";
context.fillText("motion", x, y);
context.closePath();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
<!DOCTYPE html>
<html>
<body>
<head>
<link rel="stylesheet" href="test.css">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<canvas id="canvas"></canvas>
<script src="test.js"></script>
</body>
</html>
Multiple text boxes is easy :
Change the initialize boxes part to include more than one.
// Initialize random boxes:
var boxes = [];
var boxCount = 2
for (var i = 0; i < boxCount; i++) {
Second your random color is flawed , try this :
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).substr(hex.length);
}
I don't think that roboto is available in the canvas element. (I could be wrong)
monospace is available though.
Edit
If you want different words you could use another array for them:
var words = ['motion','designer']
for (var i = 0; i < words.length; i++) {
as you create boxes use the words array.
You can't hard code the size of the box if you want have the words to collide. Height should be at least the font height 40px and if you want to get the width of the text box there is a context helping function :
box.width = context.measureText(words[i]).width;
See the snippet for usage :
// Return random RGB color string:
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).substr(hex.length);
}
// Poor man's box physics update for time step dt:
function doPhysics(boxes, width, height, dt) {
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Update positions:
box.x += box.dx * dt;
box.y += box.dy * dt;
// Handle boundary collisions:
if (box.x < 0) {
box.x = 0;
box.dx = -box.dx;
} else if (box.x + box.width > width) {
box.x = width - box.width;
box.dx = -box.dx;
}
if (box.y < 0) {
box.y = 0;
box.dy = -box.dy;
} else if (box.y + box.height > height) {
box.y = height - box.height;
box.dy = -box.dy;
}
}
// Handle box collisions:
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
var box1 = boxes[i];
var box2 = boxes[j];
var dx = Math.abs(box1.x - box2.x);
var dy = Math.abs(box1.y - box2.y);
// Check for overlap:
if (2 * dx < (box1.width + box2.width ) &&
2 * dy < (box1.height + box2.height)) {
// Swap dx if moving towards each other:
if ((box1.x > box2.x) == (box1.dx < box2.dx)) {
var swap = box1.dx;
box1.dx = box2.dx;
box2.dx = swap;
}
// Swap dy if moving towards each other:
if ((box1.y > box2.y) == (box1.dy < box2.dy)) {
var swap = box1.dy;
box1.dy = box2.dy;
box2.dy = swap;
}
}
}
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Initialize random boxes:
var boxes = [];
var words = ["designer","motion","hi"]
for (var i = 0; i < words.length; i++) {
var box = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
width: 50, // Will be dynamic
height: 42,
dx: (Math.random() - 0.5) * 0.3,
dy: (Math.random() - 0.5) * 0.3
};
boxes.push(box);
}
// Initialize random color and set up interval:
var color = randomColor();
setInterval(function() {
color = randomColor();
}, 450);
// Update physics at fixed rate:
var last = performance.now();
setInterval(function(time) {
var now = performance.now();
doPhysics(boxes, canvas.width, canvas.height, now - last);
last = now;
}, 50);
// Draw animation frames at optimal frame rate:
function draw(now) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Interpolate position:
var x = box.x + box.dx * (now - last);
var y = box.y + box.dy * (now - last);
box.width = context.measureText(words[i]).width;
context.beginPath();
context.fillStyle = color;
context.font = "normal 40px monospace";
context.textBaseline = "hanging";
context.fillText(words[i], x, y);
context.closePath();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
<!DOCTYPE html>
<html>
<body>
<head>
<link rel="stylesheet" href="test.css">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<canvas id="canvas"></canvas>
<script src="test.js"></script>
</body>
</html>
I am trying to make Conway's Game of Life in JavaScript, but cannot find what is wrong with a specific function after hours of debugging.
The program works by making a 2d array based of global "row" and "column" variables, and then pushes "Square" objects to each space in the array. The program then sets an interval for the "draw" function for a specific interval, (the global variable "rate").
I apologize if this is hard to understand, but basically, every specific time interval, like every 1000 milliseconds, the program checks every "Square" object in the array, updates the amount of neighbors it has, and then draws it on the screen.
This is where I am stuck; the update function is supposed to check all 8 neighbors that a square has, (or 3-5 if it is an edge square) but it will only check 4 neighbors. No matter what I do, if I click a square to populate it, only the top, top left, top right, and left neighbors of the now populated square will register that their neighbor has become populated.
Other than this bug the code is working fine, and i'm 99% sure the problem is in this one function, because the code will still function as a cellular automata currently, just not as Conway's Game of Life.
var canvas = document.getElementById("life");
var ctx = canvas.getContext('2d');
var timer;
var rate = 1000;
var rows = 20;
var columns = 20;
var width = 20;
var clickX;
var clickY;
var board;
var running = false;
var checkArray = [
[-1, 0, 1, 1, 1, 0, -1, -1],
[-1, -1, -1, 0, 1, 1, 1, 0]
];
var visuals = true;
var gridColor = "#000000";
/*
live cell with < 2 neighbors = death
live cell with 2 or 3 neighbors = live for 1 generation
live cell with > 4 neighbors = death
dead cell with 3 neighbors = live for 1 generation
0 = death
1 = death
2 = continues life if alive
3 = continues life if alive OR brings to life if dead
4 = death
5 = death
6 = death
7 = death
8 = death
*/
window.onload = function() {
makeBoard();
var timer = setInterval(draw, rate);
window.addEventListener("mousedown", clickHandler);
// for(var i = 0; i < 8; i++){
// var checkIndexX = checkArray[0][i];
// var checkIndexY = checkArray[1][i];
// console.log(checkIndexX, checkIndexY);
// }
}
function makeBoard() {
board = new Array(columns);
for (var i = 0; i < rows; i++) {
var intRow = new Array(rows);
for (var j = 0; j < columns; j++) {
intRow[j] = new Square(false, j, i);
}
board[i] = intRow;
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var y = 0; y < rows; y++) {
for (var x = 0; x < columns; x++) {
if (running) {
board[y][x].update();
}
board[y][x].draw();
if (visuals) {
board[y][x].visuals();
}
}
}
drawRunButton();
}
function Square(alive, PARX, PARY) {
this.alive = false;
this.X = PARX;
this.Y = PARY;
this.neighbors = 0;
this.update = function() {
this.neighbors = 0;
for (var i = 0; i < 8; i++) {
var checkIndexX = checkArray[0][i];
var checkIndexY = checkArray[1][i];
if ((this.X + checkIndexX) >= 0 && (this.X + checkIndexX) < columns &&
(this.Y + checkIndexY) >= 0 && (this.Y + checkIndexY) < rows) {
var check = board[this.Y + checkIndexY][this.X + checkIndexX];
// console.log(this.X, this.Y, check.X, check.Y, checkIndexX, checkIndexY);
if (check.alive) {
this.neighbors++;
}
}
}
if (this.alive) {
if (this.neighbors < 2 || this.neighbors > 3) {
this.alive = false;
}
} else {
if (this.neighbors == 3) {
this.alive = true;
}
}
};
this.visuals = function() {
drawVisuals(this.neighbors, this.X * width, this.Y * width);
};
this.draw = function(alive) {
drawSquare(this.alive, this.X * width, this.Y * width, width);
}
}
function clickHandler(e) {
var clickX = e.screenX - 68;
var clickY = e.screenY - 112;
mapClick(clickX, clickY);
manageRun(clickX, clickY);
}
function mapClick(x, y) {
var indexX = Math.floor(x / width);
var indexY = Math.floor(y / width);
if (indexX >= 0 && indexX < columns && indexY >= 0 && indexY < rows) {
board[indexY][indexX].alive = true;
}
}
function manageRun(x, y) {
if (x >= (columns * width) + 5 && x <= (columns * width) + 45 && y >= 5 && y <= 45) {
if (running) {
running = false;
} else {
running = true;
}
console.log(running);
}
}
function drawRunButton() {
drawSquare(false, (columns * width) + 5, 5, 40)
}
function drawSquare(fill, x, y, width) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + width);
ctx.lineTo(x, y + width);
ctx.lineTo(x, y);
if (fill) {
ctx.fillStyle = gridColor;
ctx.fill();
} else {
ctx.strokeStyle = gridColor;
ctx.stroke();
}
}
function drawVisuals(neighbors, x, y) {
ctx.beginPath();
ctx.fillStyle = "#ff0000";
ctx.font = '20px serif';
ctx.fillText(neighbors, x + (width / 3), y + (width / 1.25));
}
<!DOCTYPE html>
<html>
<head>
<title>template.com</title>
<link href="style.css" type="text/css" rel="stylesheet">
</head>
<body>
<canvas id="life" width="1400" height="700" style="border: 1px solid black"></canvas>
</body>
</html>
I'm trying to create a hyperdrive effect, like from Star Wars, where the stars have a motion trail. I've gotten as far as creating the motion trail on a single circle, it still looks like the trail is going down in the y direction and not forwards or positive in the z direction.
Also, how could I do this with (many) randomly placed circles as if they were stars?
My code is on jsfiddle (https://jsfiddle.net/5m7x5zxu/) and below:
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");
var xPos = 180;
var yPos = 100;
var motionTrailLength = 16;
var positions = [];
function storeLastPosition(xPos, yPos) {
// push an item
positions.push({
x: xPos,
y: yPos
});
//get rid of first item
if (positions.length > motionTrailLength) {
positions.pop();
}
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i = positions.length-1; i > 0; i--) {
var ratio = (i - 1) / positions.length;
drawCircle(positions[i].x, positions[i].y, ratio);
}
drawCircle(xPos, yPos, "source");
var k=2;
storeLastPosition(xPos, yPos);
// update position
if (yPos > 125) {
positions.pop();
}
else{
yPos += k*1.1;
}
requestAnimationFrame(update);
}
update();
function drawCircle(x, y, r) {
if (r == "source") {
r = 1;
} else {
r*=1.1;
}
context.beginPath();
context.arc(x, y, 3, 0, 2 * Math.PI, true);
context.fillStyle = "rgba(255, 255, 255, " + parseFloat(1-r) + ")";
context.fill();
}
Canvas feedback and particles.
This type of FX can be done many ways.
You could just use a particle systems and draw stars (as lines) moving away from a central point, as the speed increase you increase the line length. When at low speed the line becomes a circle if you set ctx.lineWidth > 1 and ctx.lineCap = "round"
To add to the FX you can use render feedback as I think you have done by rendering the canvas over its self. If you render it slightly larger you get a zoom FX. If you use ctx.globalCompositeOperation = "lighter" you can increase the stars intensity as you speed up to make up for the overall loss of brightness as stars move faster.
Example
I got carried away so you will have to sift through the code to find what you need.
The particle system uses the Point object and a special array called bubbleArray to stop GC hits from janking the animation.
You can use just an ordinary array if you want. The particles are independent of the bubble array. When they have moved outside the screen they are move to a pool and used again when a new particle is needed. The update function moves them and the draw Function draws them I guess LOL
The function loop is the main loop and adds and draws particles (I have set the particle count to 400 but should handle many more)
The hyper drive is operated via the mouse button. Press for on, let go for off. (It will distort the text if it's being displayed)
The canvas feedback is set via that hyperSpeed variable, the math is a little complex. The sCurce function just limits the value to 0,1 in this case to stop alpha from going over or under 1,0. The hyperZero is just the sCurve return for 1 which is the hyper drives slowest speed.
I have pushed the feedback very close to the limit. In the first few lines of the loop function you can set the top speed if(mouse.button){ if(hyperSpeed < 1.75){ Over this value 1.75 and you will start to get bad FX, at about 2 the whole screen will just go white (I think that was where)
Just play with it and if you have questions ask in the comments.
const ctx = canvas.getContext("2d");
// very simple mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// High performance array pool using buubleArray to separate pool objects and active object.
// This is designed to eliminate GC hits involved with particle systems and
// objects that have short lifetimes but used often.
// Warning this code is not well tested.
const bubbleArray = () => {
const items = [];
var count = 0;
return {
clear(){ // warning this dereferences all locally held references and can incur Big GC hit. Use it wisely.
this.items.length = 0;
count = 0;
},
update() {
var head, tail;
head = tail = 0;
while(head < count){
if(items[head].update() === false) {head += 1 }
else{
if(tail < head){
const temp = items[head];
items[head] = items[tail];
items[tail] = temp;
}
head += 1;
tail += 1;
}
}
return count = tail;
},
createCallFunction(name, earlyExit = false){
name = name.split(" ")[0];
const keys = Object.keys(this);
if(Object.keys(this).indexOf(name) > -1){ throw new Error(`Can not create function name '${name}' as it already exists.`) }
if(!/\W/g.test(name)){
let func;
if(earlyExit){
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ if (items[i++].${name}() === true) { break } }`;
}else{
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ items[i++].${name}() }`;
}
!this.items && (this.items = items);
this[name] = new Function(func);
}else{ throw new Error(`Function name '${name}' contains illegal characters. Use alpha numeric characters.`) }
},
callEach(name){var i = 0; while(i < count){ if (items[i++][name]() === true) { break } } },
each(cb) { var i = 0; while(i < count){ if (cb(items[i], i++) === true) { break } } },
next() { if (count < items.length) { return items[count ++] } },
add(item) {
if(count === items.length){
items.push(item);
count ++;
}else{
items.push(items[count]);
items[count++] = item;
}
return item;
},
getCount() { return count },
}
}
// Helpers rand float, randI random Int
// doFor iterator
// sCurve curve input -Infinity to Infinity out -1 to 1
// randHSLA creates random colour
// CImage, CImageCtx create image and image with context attached
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const sCurve = (v,p) => (2 / (1 + Math.pow(p,-v))) -1;
const randHSLA = (h, h1, s = 100, s1 = 100, l = 50, l1 = 50, a = 1, a1 = 1) => { return `hsla(${randI(h,h1) % 360},${randI(s,s1)}%,${randI(l,l1)}%,${rand(a,a1)})` }
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"),c.width = w,c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w,h), c.ctx = c.getContext("2d"), c);
// create image to hold text
var textImage = CImageCtx(1024, 1024);
var c = textImage.ctx;
c.fillStyle = "#FF0";
c.font = "64px arial black";
c.textAlign = "center";
c.textBaseline = "middle";
const text = "HYPER,SPEED FX,VII,,Battle of Jank,,Hold the mouse,button to increase,speed.".split(",");
text.forEach((line,i) => { c.fillText(line,512,i * 68 + 68) });
const maxLines = text.length * 68 + 68;
function starWarIntro(image,x1,y1,x2,y2,pos){
var iw = image.width;
var ih = image.height;
var hh = (x2 - x1) / (y2 - y1); // Slope of left edge
var w2 = iw / 2; // half width
var z1 = w2 - x1; // Distance (z) to first line
var z2 = (z1 / (w2 - x2)) * z1 - z1; // distance (z) between first and last line
var sk,t3,t3a,z3a,lines, z3, dd = 0, a = 0, as = 2 / (y2 - y1);
for (var y = y1; y < y2 && dd < maxLines; y++) { // for each line
t3 = ((y - y1) * hh) + x1; // get scan line top left edge
t3a = (((y+1) - y1) * hh) + x1; // get scan line bottom left edge
z3 = (z1 / (w2 - t3)) * z1; // get Z distance to top of this line
z3a = (z1 / (w2 - t3a)) * z1; // get Z distance to bottom of this line
dd = ((z3 - z1) / z2) * ih; // get y bitmap coord
a += as;
ctx.globalAlpha = a < 1 ? a : 1;
dd += pos; // kludge for this answer to make text move
// does not move text correctly
lines = ((z3a - z1) / z2) * ih-dd; // get number of lines to copy
ctx.drawImage(image, 0, dd , iw, lines, t3, y, w - t3 * 2, 1.5);
}
}
// canvas settings
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// diagonal distance used to set point alpha (see point update)
var diag = Math.sqrt(w * w + h * h);
// If window size is changed this is called to resize the canvas
// It is not called via the resize event as that can fire to often and
// debounce makes it feel sluggish so is called from main loop.
function resizeCanvas(){
points.clear();
canvas.width = innerWidth;
canvas.height = innerHeight;
w = canvas.width;
h = canvas.height;
cw = w / 2; // center
ch = h / 2;
diag = Math.sqrt(w * w + h * h);
}
// create array of points
const points = bubbleArray();
// create optimised draw function itterator
points.createCallFunction("draw",false);
// spawns a new star
function spawnPoint(pos){
var p = points.next();
p = points.add(new Point())
if (p === undefined) { p = points.add(new Point()) }
p.reset(pos);
}
// point object represents a single star
function Point(pos){ // this function is duplicated as reset
if(pos){
this.x = pos.x;
this.y = pos.y;
this.dead = false;
}else{
this.x = 0;
this.y = 0;
this.dead = true;
}
this.alpha = 0;
var x = this.x - cw;
var y = this.y - ch;
this.dir = Math.atan2(y,x);
this.distStart = Math.sqrt(x * x + y * y);
this.speed = rand(0.01,1);
this.col = randHSLA(220,280,100,100,50,100);
this.dx = Math.cos(this.dir) * this.speed;
this.dy = Math.sin(this.dir) * this.speed;
}
Point.prototype = {
reset : Point, // resets the point
update(){ // moves point and returns false when outside
this.speed *= hyperSpeed; // increase speed the more it has moved
this.x += Math.cos(this.dir) * this.speed;
this.y += Math.sin(this.dir) * this.speed;
var x = this.x - cw;
var y = this.y - ch;
this.alpha = (Math.sqrt(x * x + y * y) - this.distStart) / (diag * 0.5 - this.distStart);
if(this.alpha > 1 || this.x < 0 || this.y < 0 || this.x > w || this.h > h){
this.dead = true;
}
return !this.dead;
},
draw(){ // draws the point
ctx.strokeStyle = this.col;
ctx.globalAlpha = 0.25 + this.alpha *0.75;
ctx.beginPath();
ctx.lineTo(this.x - this.dx * this.speed, this.y - this.dy * this.speed);
ctx.lineTo(this.x, this.y);
ctx.stroke();
}
}
const maxStarCount = 400;
const p = {x : 0, y : 0};
var hyperSpeed = 1.001;
const alphaZero = sCurve(1,2);
var startTime;
function loop(time){
if(startTime === undefined){
startTime = time;
}
if(w !== innerWidth || h !== innerHeight){
resizeCanvas();
}
// if mouse down then go to hyper speed
if(mouse.button){
if(hyperSpeed < 1.75){
hyperSpeed += 0.01;
}
}else{
if(hyperSpeed > 1.01){
hyperSpeed -= 0.01;
}else if(hyperSpeed > 1.001){
hyperSpeed -= 0.001;
}
}
var hs = sCurve(hyperSpeed,2);
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,0,0); // reset transform
//==============================================================
// UPDATE the line below could be the problem. Remove it and try
// what is under that
//==============================================================
//ctx.fillStyle = `rgba(0,0,0,${1-(hs-alphaZero)*2})`;
// next two lines are the replacement
ctx.fillStyle = "Black";
ctx.globalAlpha = 1-(hs-alphaZero) * 2;
//==============================================================
ctx.fillRect(0,0,w,h);
// the amount to expand canvas feedback
var sx = (hyperSpeed-1) * cw * 0.1;
var sy = (hyperSpeed-1) * ch * 0.1;
// increase alpha as speed increases
ctx.globalAlpha = (hs-alphaZero)*2;
ctx.globalCompositeOperation = "lighter";
// draws feedback twice
ctx.drawImage(canvas,-sx, -sy, w + sx*2 , h + sy*2)
ctx.drawImage(canvas,-sx/2, -sy/2, w + sx , h + sy)
ctx.globalCompositeOperation = "source-over";
// add stars if count < maxStarCount
if(points.getCount() < maxStarCount){
var cent = (hyperSpeed - 1) *0.5; // pulls stars to center as speed increases
doFor(10,()=>{
p.x = rand(cw * cent ,w - cw * cent); // random screen position
p.y = rand(ch * cent,h - ch * cent);
spawnPoint(p)
})
}
// as speed increases make lines thicker
ctx.lineWidth = 2 + hs*2;
ctx.lineCap = "round";
points.update(); // update points
points.draw(); // draw points
ctx.globalAlpha = 1;
// scroll the perspective star wars text FX
var scrollTime = (time - startTime) / 5 - 2312;
if(scrollTime < 1024){
starWarIntro(textImage,cw - h * 0.5, h * 0.2, cw - h * 3, h , scrollTime );
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Here's another simple example, based mainly on the same idea as Blindman67, concetric lines moving away from center at different velocities (the farther from center, the faster it moves..) also no recycling pool here.
"use strict"
var c = document.createElement("canvas");
document.body.append(c);
var ctx = c.getContext("2d");
var w = window.innerWidth;
var h = window.innerHeight;
var ox = w / 2;
var oy = h / 2;
c.width = w; c.height = h;
const stars = 120;
const speed = 0.5;
const trailLength = 90;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "#fff"
ctx.fillRect(ox, oy, 1, 1);
init();
function init() {
var X = [];
var Y = [];
for(var i = 0; i < stars; i++) {
var x = Math.random() * w;
var y = Math.random() * h;
X.push( translateX(x) );
Y.push( translateY(y) );
}
drawTrails(X, Y)
}
function translateX(x) {
return x - ox;
}
function translateY(y) {
return oy - y;
}
function getDistance(x, y) {
return Math.sqrt(x * x + y * y);
}
function getLineEquation(x, y) {
return function(n) {
return y / x * n;
}
}
function drawTrails(X, Y) {
var count = 1;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
function anim() {
for(var i = 0; i < X.length; i++) {
var x = X[i];
var y = Y[i];
drawNextPoint(x, y, count);
}
count+= speed;
if(count < trailLength) {
window.requestAnimationFrame(anim);
}
else {
init();
}
}
anim();
}
function drawNextPoint(x, y, step) {
ctx.fillStyle = "#fff";
var f = getLineEquation(x, y);
var coef = Math.abs(x) / 100;
var dist = getDistance( x, y);
var sp = speed * dist / 100;
for(var i = 0; i < sp; i++) {
var newX = x + Math.sign(x) * (step + i) * coef;
var newY = translateY( f(newX) );
ctx.fillRect(newX + ox, newY, 1, 1);
}
}
body {
overflow: hidden;
}
canvas {
position: absolute;
left: 0;
top: 0;
}
I'm working through instructions to construct an interactive particle logo design and can't seem to get to the finished product. This is the logo image file -
I'm using a canvas structure / background. Here's the code -
var canvasInteractive = document.getElementById('canvas-interactive');
var canvasReference = document.getElementById('canvas-reference');
var contextInteractive = canvasInteractive.getContext('2d');
var contextReference = canvasReference.getContext('2d');
var image = document.getElementById('img');
var width = canvasInteractive.width = canvasReference.width = window.innerWidth;
var height = canvasInteractive.height = canvasReference.height = window.innerHeight;
var logoDimensions = {
x: 500,
y: 500
};
var center = {
x: width / 2,
y: height / 2
};
var logoLocation = {
x: center.x - logoDimensions.x / 2,
y: center.y - logoDimensions.y / 2
};
var mouse = {
radius: Math.pow(100, 2),
x: 0,
y: 0
};
var particleArr = [];
var particleAttributes = {
friction: 0.95,
ease: 0.19,
spacing: 6,
size: 4,
color: "#ffffff"
};
function Particle(x, y) {
this.x = this.originX = x;
this.y = this.originY = y;
this.rx = 0;
this.ry = 0;
this.vx = 0;
this.vy = 0;
this.force = 0;
this.angle = 0;
this.distance = 0;
}
Particle.prototype.update = function() {
this.rx = mouse.x - this.x;
this.ry = mouse.y - this.y;
this.distance = this.rx * this.rx + this.ry * this.ry;
this.force = -mouse.radius / this.distance;
if (this.distance < mouse.radius) {
this.angle = Math.atan2(this.ry, this.rx);
this.vx += this.force * Math.cos(this.angle);
this.vy += this.force * Math.sin(this.angle);
}
this.x += (this.vx *= particleAttributes.friction) + (this.originX - this.x) * particleAttributes.ease;
this.y += (this.vy *= particleAttributes.friction) + (this.originY - this.y) * particleAttributes.ease;
};
function init() {
contextReference.drawImage(image, logoLocation.x, logoLocation.y);
var pixels = contextReference.getImageData(0, 0, width, height).data;
var index;
for (var y = 0; y < height; y += particleAttributes.spacing) {
for (var x = 0; x < width; x += particleAttributes.spacing) {
index = (y * width + x) * 4;
if (pixels[++index] > 0) {
particleArr.push(new Particle(x, y));
}
}
}
};
init();
function update() {
for (var i = 0; i < particleArr.length; i++) {
var p = particleArr[i];
p.update();
}
};
function render() {
contextInteractive.clearRect(0, 0, width, height);
for (var i = 0; i < particleArr.length; i++) {
var p = particleArr[i];
contextInteractive.fillStyle = particleAttributes.color;
contextInteractive.fillRect(p.x, p.y, particleAttributes.size, particleAttributes.size);
}
};
function animate() {
update();
render();
requestAnimationFrame(animate);
}
animate();
document.body.addEventListener("mousemove", function(event) {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
document.body.addEventListener("touchstart", function(event) {
mouse.x = event.changedTouches[0].clientX;
mouse.y = event.changedTouches[0].clientY;
}, false);
document.body.addEventListener("touchmove", function(event) {
event.preventDefault();
mouse.x = event.targetTouches[0].clientX;
mouse.y = event.targetTouches[0].clientY;
}, false);
document.body.addEventListener("touchend", function(event) {
event.preventDefault();
mouse.x = 0;
mouse.y = 0;
}, false);
html,
body {
margin: 0px;
position: relative;
background-color: #000;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
img {
display: none;
width: 70%;
height: 400px;
position: absolute;
left: 50%;
transform: translate(-50%, 30%);
}
<html>
<body>
<canvas id="canvas-interactive"></canvas>
<canvas id="canvas-reference"></canvas>
<img src="https://i.stack.imgur.com/duv9h.png" alt="..." id="img">
</body>
</html>
My understanding is the image file has to be set to display: none; and then the image needs to be re-drawn using the javascript commands but I'm not sure if this image is compatible or not. When finished I want the image on a white background.
By way of an example the end design needs to resemble this - Logo particle design
Particle positions from bitmap.
To get the FX you want you need to create a particle system. This is just an array of objects, each with a position, the position where they want to be (Home), a vector defining their current movement, and the colour.
You get each particle's home position and colour by reading pixels from the image. You can access pixel data by rendering an image on a canvas and the using ctx.getImageData to get the pixel data (Note image must be on same domain or have CORS headers to access pixel data). As you read each pixel in turn, if not transparent, create a particle for that pixel and set it colour and home position from the pixels colour and position.
Use requestAnimationFrame to call a render function that every frame iterates all the particles moving them by some set of rules that give you the motion you are after. Once you have move each particle, render them to the canvas using simple shapes eg fillRect
Mouse interaction
To have interaction with the mouse you will need to use mouse move events to keep track of the mouse position relative to the canvas you are rendering to. As you update each particle you also check how far it is from the mouse. You can then push or pull the particle from or to the mouse (depending on the effect you want.
Rendering speed will limit the particle count.
The only issue with these types of FX is that you will be pushing the rendering speed limits as the particle count goes up. What may work well on one machine, will run very slow on another.
To avoid being too slow, and not looking good on some machines you should consider keeping an eye on the frame rate and reducing the particle count if it runs slow. To compensate you can increase the particle size or even reduce the canvas resolution.
The bottleneck is the actual rendering of each particle. When you get to large numbers the path methods really grinds down. If you want really high numbers you will have to render pixels directly to the bitmap, using the same method as reading but in reverse of course.
Example simple particles read from bitmap.
The example below uses text rendered to a canvas to create the particles, and to use an image you would just draw the image rather than the text. The example is a bit overkill as I ripped it from an old answer of mine. It is just as an example of the various ways to get stuff done.
const ctx = canvas.getContext("2d");
const Vec = (x, y) => ({x, y});
const setStyle = (ctx,style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) }
const createImage = (w,h) => {var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i}
const textList = ["Particles"];
var textPos = 0;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
var started = false;
requestAnimationFrame(update);
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
function onResize(){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
if (!started) { startIt() }
}
function update(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight){ onResize() }
else { ctx.clearRect(0,0,w,h) }
particles.update();
particles.draw();
requestAnimationFrame(update);
}
function createParticles(text){
createTextMap(
text, 60, "Arial",
{ fillStyle : "#FF0", strokeStyle : "#F00", lineWidth : 2, lineJoin : "round", },
{ top : 0, left : 0, width : canvas.width, height : canvas.height }
)
}
// This function starts the animations
function startIt(){
started = true;
const next = ()=>{
var text = textList[(textPos++ ) % textList.length];
createParticles(text);
setTimeout(moveOut,text.length * 100 + 12000);
}
const moveOut = ()=>{
particles.moveOut();
setTimeout(next,2000);
}
setTimeout(next,0);
}
// the following function create the particles from text using a canvas
// the canvas used is displayed on the main canvas top left fro reference.
var tCan = createImage(100, 100); // canvas used to draw text
function createTextMap(text,size,font,style,fit){
const hex = (v)=> (v < 16 ? "0" : "") + v.toString(16);
tCan.ctx.font = size + "px " + font;
var width = Math.ceil(tCan.ctx.measureText(text).width + size);
tCan.width = width;
tCan.height = Math.ceil(size *1.2);
var c = tCan.ctx;
c.font = size + "px " + font;
c.textAlign = "center";
c.textBaseline = "middle";
setStyle(c,style);
if (style.strokeStyle) { c.strokeText(text, width / 2, tCan.height / 2) }
if (style.fillStyle) { c.fillText(text, width / 2, tCan.height/ 2) }
particles.empty();
var data = c.getImageData(0,0,width,tCan.height).data;
var x,y,ind,rgb,a;
for(y = 0; y < tCan.height; y += 1){
for(x = 0; x < width; x += 1){
ind = (y * width + x) << 2; // << 2 is equiv to * 4
if(data[ind + 3] > 128){ // is alpha above half
rgb = `#${hex(data[ind ++])}${hex(data[ind ++])}${hex(data[ind ++])}`;
particles.add(Vec(x, y), Vec(x, y), rgb);
}
}
}
particles.sortByCol
var scale = Math.min(fit.width / width, fit.height / tCan.height);
particles.each(p=>{
p.home.x = ((fit.left + fit.width) / 2) + (p.home.x - (width / 2)) * scale;
p.home.y = ((fit.top + fit.height) / 2) + (p.home.y - (tCan.height / 2)) * scale;
})
.findCenter() // get center used to move particles on and off of screen
.moveOffscreen() // moves particles off the screen
.moveIn(); // set the particles to move into view.
}
// basic particle
const particle = { pos : null, delta : null, home : null, col : "black", }
// array of particles
const particles = {
items : [], // actual array of particles
mouseFX : { power : 12,dist :110, curve : 2, on : true },
fx : { speed : 0.3, drag : 0.6, size : 4, jiggle : 1 },
// direction 1 move in -1 move out
direction : 1,
moveOut () {this.direction = -1; return this},
moveIn () {this.direction = 1; return this},
length : 0,
each(callback){ // custom iteration
for(var i = 0; i < this.length; i++){ callback(this.items[i],i) }
return this;
},
empty() { this.length = 0; return this },
deRef(){ this.items.length = 0; this.length = 0 },
sortByCol() { this.items.sort((a,b) => a.col === b.col ? 0 : a.col < b.col ? 1 : -1 ) },
add(pos, home, col){ // adds a particle
var p;
if(this.length < this.items.length){
p = this.items[this.length++];
p.home.x = home.x;
p.home.y = home.y;
p.delta.x = 0;
p.delta.y = 0;
p.col = col;
}else{
this.items.push( Object.assign({}, particle,{ pos, home, col, delta : Vec(0,0) } ) );
this.length = this.items.length
}
return this;
},
draw(){ // draws all
var p, size, sizeh;
sizeh = (size = this.fx.size) / 2;
for(var i = 0; i < this.length; i++){
p = this.items[i];
ctx.fillStyle = p.col;
ctx.fillRect(p.pos.x - sizeh, p.pos.y - sizeh, size, size);
}
},
update(){ // update all particles
var p,x,y,d;
const mP = this.mouseFX.power;
const mD = this.mouseFX.dist;
const mC = this.mouseFX.curve;
const fxJ = this.fx.jiggle;
const fxD = this.fx.drag;
const fxS = this.fx.speed;
for(var i = 0; i < this.length; i++){
p = this.items[i];
p.delta.x += (p.home.x - p.pos.x ) * fxS + (Math.random() - 0.5) * fxJ;
p.delta.y += (p.home.y - p.pos.y ) * fxS + (Math.random() - 0.5) * fxJ;
p.delta.x *= fxD;
p.delta.y *= fxD;
p.pos.x += p.delta.x * this.direction;
p.pos.y += p.delta.y * this.direction;
if(this.mouseFX.on){
x = p.pos.x - mouse.x;
y = p.pos.y - mouse.y;
d = Math.sqrt(x * x + y * y);
if(d < mD){
x /= d;
y /= d;
d /= mD;
d = (1-Math.pow(d, mC)) * mP;
p.pos.x += x * d;
p.pos.y += y * d;
}
}
}
return this;
},
findCenter(){ // find the center of particles maybe could do without
var x,y;
y = x = 0;
this.each(p => { x += p.home.x; y += p.home.y });
this.center = Vec(x / this.length, y / this.length);
return this;
},
moveOffscreen(){ // move start pos offscreen
var dist,x,y;
dist = Math.sqrt(this.center.x * this.center.x + this.center.y * this.center.y);
this.each(p => {
var d;
x = p.home.x - this.center.x;
y = p.home.y - this.center.y;
d = Math.max(0.0001,Math.sqrt(x * x + y * y)); // max to make sure no zeros
p.pos.x = p.home.x + (x / d) * dist;
p.pos.y = p.home.y + (y / d) * dist;
});
return this;
},
}
canvas { position : absolute; top : 0px; left : 0px; background : black;}
<canvas id="canvas"></canvas>
Use png saved as PNG-8 and and allow cross-origin
I saw the cool article from Bricks and mortar and thought I'd try it out.
I battled with it for an eternity, thinking that my js was wrong... Turns out that the image has to be saved as a PNG-8 without dither instead of a PNG-24.
Then make sure that you add the crossOrigin="Anonymous" attribute to the image tag:
<img crossOrigin="Anonymous" id="img" src="[link to wherever you host the image]" alt="logo">
I also hid the reference canvas by adding the following styles:
canvas#canvas-reference {
display: none;
}
I also added a debounce and resize function, so it's responsive.
The result:
See Demo with inverted logo
function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
Ball = (function() {
// constructor
function Ball(x,y,radius,color){
this.center = {x:x, y:y};
this.radius = radius;
this.color = color;
this.dx = 2;
this.dy = 2;
this.boundaryHeight = $('#ground').height();
this.boundaryWidth = $('#ground').width();
this.dom = $('<p class="circle"></p>').appendTo('#ground');
// the rectange div a circle
this.dom.width(radius*2);
this.dom.height(radius*2);
this.dom.css({'border-radius':radius,background:color});
this.placeAtCenter(x,y);
}
// Place the ball at center x, y
Ball.prototype.placeAtCenter = function(x,y){
this.dom.css({top: Math.round(y- this.radius), left: Math.round(x - this.radius)});
this.center.x = Math.round(x);
this.center.y = Math.round(y);
};
Ball.prototype.setColor = function(color) {
if(color) {
this.dom.css('background',color);
} else {
this.dom.css('background',this.color);
}
};
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
return Ball;
})();
var number_of_balls = 5;
var balls = [];
$('document').ready(function(){
for (i = 0; i < number_of_balls; i++) {
var boundaryHeight = $('#ground').height();
var boundaryWidth = $('#ground').width();
var y = randomXToY(30,boundaryHeight - 50);
var x = randomXToY(30,boundaryWidth - 50);
var radius = randomXToY(15,30);
balls.push(new Ball(x,y,radius, '#'+Math.floor(Math.random()*16777215).toString(16)));
}
loop();
});
loop = function(){
for (var i = 0; i < balls.length; i++){
balls[i].move();
}
setTimeout(loop, 8);
};
I have never used in oops concepts in javascript. How do I change the ball color when the balls touches each other?
This is the link : http://jsbin.com/imofat/1/edit
You currently don't have any interaction with the balls. What you can do is checking whether two balls are "inside" each other, and change colors in that case: http://jsbin.com/imofat/1491/.
// calculates distance between two balls
var d = function(a, b) {
var dx = b.center.x - a.center.x;
var dy = b.center.y - a.center.y;
return Math.sqrt(dx*dx + dy*dy);
};
and:
// for each ball
for(var i = 0; i < balls.length; i++) {
// check against rest of balls
for(var j = i + 1; j < balls.length; j++) {
var a = balls[i];
var b = balls[j];
// distance is smaller than their radii, so they are inside each other
if(d(a, b) < a.radius + b.radius) {
// set to some other color using your random color code
a.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
b.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
}
}
}
Still, there are things for improvement:
Balls are changing colors as long as they are inside each other, not just once.
If you want them to "touch", you might want to implement some kind of bouncing effect to make it more realistic.