Related
I'm still getting used to objects, but no matter what I do I can't get my objects to reverse their x or y. It doesn't even register that any of them have hit a canvas wall, they just go off screen.
Here's my code:
"use strict";
//variables
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var randomX = 0; //in for loop use Math.random()
var randomY = 0; //in for loop use Math.random()
var ballRadius = 10;
var ballX = canvas.width/2;
var ballY = canvas.height-30;
var ballMove = [];
var ballObject = [];
//declares the objects x and y's
for(var i = 0; i < 10; i++){
ballObject[i] = { x: 0, y: 0 };
ballMove[i] = { x:1, y:-1 };
randomX = (Math.random() * (canvas.width-ballRadius)) +1;
randomY = (Math.random() * (canvas.height-ballRadius)) +1;
ballObject[i].x = randomX;
ballObject[i].y = randomY;
}
function draw()
{
ctx.clearRect(0,0,canvas.width, canvas.height);
//didn't actually need this for loop but was experimenting
for(var i = 0; i<10; i++){
drawBall(i);
}
moveBall();
//this method should check the x and y of the balls.
bounceBall();
}
// basic ball drawing function
function drawBall(i)
{
ctx.beginPath();
ctx.arc(ballObject[i].x, ballObject[i].y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
//extremely basic move function (just adds to x and y)
function moveBall()
{
for(var i = 0; i < 10; i++){
ballObject[i].x += ballMove[i].x;
ballObject[i].y += ballMove[i].y;
}
}
//WHY IT NO WORK
//for whatever reason this function doesn't reverse the x and y
function bounceBall()
{
for(var i = 0; i < 10; i++)
{
if (ballObject[i].x == canvas.width-ballRadius || ballObject[i].x == 0+ballRadius){
alert("i've hit a wall");
ballMove[i].x = -ballMove[i].x;
}
if (ballObject[i].y == canvas.height-ballRadius || ballObject[i].y == 0+ballRadius){
alert("i've hit a wall");
ballMove[i].y = -ballMove[i].y;
}
}
}
setInterval(draw, 10);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Gamedev Canvas Workshop</title>
<style>
* { padding: 0; margin: 15; }
canvas { background: #eee; display: block; margin: 0 auto; }
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script src="ballPop.js">
</script>
</body>
</html>
I've kinda made a mess of my code trying to use objects and make them bounce.
I made a brick break game after following some tut online and the bounce worked fine - however that same concept doesn't seem to work with objects.
This likely has to do with the fact that your ball coordinates are floating point values. For example:
> (Math.random() * (200 - 3)) + 1
179.1025927176495
When you do your checks in bounceBall you may get better results by checking if the ball would exceed the boundary
function bounceBall()
{
for(var i = 0; i < 10; i++)
{
if (ballObject[i].x >= canvas.width-ballRadius || ballObject[i].x <= 0+ballRadius) {
alert("i've hit a wall");
ballMove[i].x = -ballMove[i].x;
}
if (ballObject[i].y >= canvas.height-ballRadius || ballObject[i].y <= 0+ballRadius){
alert("i've hit a wall");
ballMove[i].y = -ballMove[i].y;
}
}
}
Note: == changed to >= and <=
I am trying to draw some letters to a canvas in a very specific way - able to target individual letters and apply an alpha.
These words need to be centered on the baseline and aligned center in the canvas and filled with strokeText rather than fill style.
The text also needs to be line broken resulting in eg;
Now, I have tried several ways of getting this out - it works fine (without the fade) when writing out the words (as full words) - however when I attempt to write them out as individual letters I cannot center them correctly. My code is below omitting the alpha on the specific letters, which once I can center things correctly shouldn't be an issue!
I realize the issue is I am trying to draw each letter separately centered at 0 on the canvas and adding letter spacing for each letter, but given the different size of the middle line I cannot figure a way to have them centered!
var can = document.querySelector('canvas'),
ctx = can.getContext('2d');
function drawStroked(text, fontSize, color, offsetX, offsetY) {
let line = text.split('\n');
this.ctx.font = fontSize + 'px ' + 'TimesNewRoman';
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 2;
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
let positionX = this.ctx.canvas.width/3;
let positionY = this.ctx.canvas.height/4;
if(offsetX !== 0) {
positionX += offsetX;
}
if(offsetY !== 0) {
positionY += offsetY;
}
for (var i = 0; i < line.length; i++) {
for (var j = 0; j < line[i].length; j++) {
let letterSpacing = 0;
let lineHeight = positionY;
if(line[i][j] === line[i].length) {
lineHeight = lineHeight * i;
}
this.ctx.strokeText(line[i][j], positionX + (letterSpacing + (j*130)), positionY + (i*fontSize));
}
}
}
drawStroked('THIS\nIS THE\nTEXT', 100, '#000', 0, 0);
<canvas width="1000" height="1000"></canvas>
Resulting output & Finished code thanks to Blindman67!
const Hero = class {
constructor(pos, canvas) {
this.position = document.getElementById(pos);
this.canvas = document.getElementById(canvas);
this.height = document.getElementsByClassName('home')[0].clientHeight;
this.width = this.position.offsetWidth;
this.ctx = this.canvas.getContext('2d');
this.title = 'THIS\nIS THE\nTEXT';
this.canvas.width = this.width;
this.canvas.height = this.height;
this._init_ui();
}
// Draw text to text canvas
_init_ui() {
// BLUE
this.drawStroked(300, '#1816ff', -3, 2, 0.95, [1, 5, 9, 12]);
// GREEN
this.drawStroked(300, '#1bff32', 0, 0, 0.95, [1, 5, 9, 12]);
// RED
this.drawStroked(300, '#ff162f', 3, -2, 0.95, [1, 5, 9, 12]);
}
drawStroked(fontSize, color, offsetX, offsetY, textVertSpacing, fade) {
// Random Char's to scramble through --- to do
// let chars = '!<>-_\\/[]{}—=+*^?#________';
// The words
let line = this.title.split('\n');
// Set the font + size
this.ctx.font = fontSize + 'px ' + 'Kommissar';
// Set the colour - NEED TO ADD ALPHA LOGIC
this.ctx.strokeStyle = color;
// Set the stroke width
this.ctx.lineWidth = 1;
// Set the baseline
this.ctx.textBaseline = 'middle';
// Set the align
this.ctx.textAlign = 'center';
let positionX = this.width/2;
let positionY = this.height/4;
positionX += offsetX;
positionY += offsetY;
let charIndex = 0;
for (var i = 0; i < line.length; i++) {
// get the width of the whole line
let width = this.ctx.measureText(line[i]).width;
console.log(width);
// use the width to find start
var textPosX = positionX - width / 2;
for (let j = 0; j < line[i].length; j++) {
// get char
let char = line[i][j];
// get its width
let cWidth = this.ctx.measureText(char).width;
// check if char needs to fade
if (fade.indexOf(charIndex) > -1) {
this.ctx.globalAlpha = 0.2;
} else {
this.ctx.globalAlpha = 1;
}
// draw the char offset by half its width (center)
this.ctx.strokeText(char, textPosX + cWidth / 2, positionY);
// move too the next pos
textPosX += cWidth;
// count the char
charIndex += 1;
}
// move down one line
positionY += fontSize * textVertSpacing;
}
}
};
export default Hero;
Use ctx.measureText
You need to use ctx.measureText and get the the width of each character, then you can space them correctly.
Correct char spacing
Because you have alignment center, you have to move the character half its width, then draw it and then move half the width again. The spacing between character's centers is half the width of each added. So if a "I" is 20 pixels wide and a "W" is 60 then the space between them is 10 + 30 = 40;
Fade characters
To do the fade I passed an array with the index of the characters to fade. Each Time I draw a character I count it. To check if a character should fade I check the index array for the character count. If they match then fade that character.
See the example for more information
Simple example...
...of what I think you want. I added two red lines to make sure the alignment was correct.
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#FDD"; // mark the center
ctx.fillRect(canvas.width / 2 | 0, 0, 1, canvas.height);
ctx.fillRect(0, canvas.height / 2 | 0, canvas.width, 1);
ctx.fillStyle = "black";
// textVertSpacing is fraction of FontSize
// fade is the index of characters to fade, including spaces
// centerX and y is center of all text
function drawStroked(text, fontSize, color, centerX, centerY, textVertSpacing, fade) {
let line = text.split('\n');
ctx.font = fontSize + 'px ' + 'TimesNewRoman';
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
// to count each character
var charIndex = 0;
// find the top ypos and then move down half a char space
var yPos = centerY - fontSize * line.length * 0.5 * textVertSpacing + fontSize * textVertSpacing / 2;
for (var i = 0; i < line.length; i++) {
// get the width of the whole line
var width = ctx.measureText(line[i]).width;
// use the width to find start
var textPosX = centerX - width / 2;
for (var j = 0; j < line[i].length; j++) {
// get char
var char = line[i][j];
// get its width
var cWidth = ctx.measureText(char).width;
// check if char needs to fade
if (fade.indexOf(charIndex) > -1) {
ctx.globalAlpha = 0.5;
} else {
ctx.globalAlpha = 1;
}
// draw the char offset by half its width (center)
ctx.fillText(char, textPosX + cWidth / 2, yPos);
// move too the next pos
textPosX += cWidth;
// count the char
charIndex += 1
}
// move down one line
yPos += fontSize * textVertSpacing;
}
}
drawStroked('THIS\nIS THE\nTEXT', 60, '#000', canvas.width / 2, canvas.height / 2, 0.9, [2, 4, 8, 12]);
<canvas id="canvas" width="500" height="200"></canvas>
Update
Added some flicker to text by adding an animation loop and calling the text rendering function every few frames. The flicker is done by randomizing the alpha. See snippet below for more info.
requestAnimationFrame(animLoop);
const flickerRate = 4; // change alpha every 4 frames
var frameCount = 0;
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#FDD"; // mark the center
ctx.fillRect(canvas.width / 2 | 0, 0, 1, canvas.height);
ctx.fillRect(0, canvas.height / 2 | 0, canvas.width, 1);
ctx.fillStyle = "black";
function drawStroked(text, fontSize, color, centerX, centerY, textVertSpacing, fade) {
let line = text.split('\n');
ctx.font = fontSize + 'px ' + 'TimesNewRoman';
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
var charIndex = 0;
var yPos = centerY - fontSize * line.length * 0.5 * textVertSpacing + fontSize * textVertSpacing / 2;
for (var i = 0; i < line.length; i++) {
var width = ctx.measureText(line[i]).width;
var textPosX = centerX - width / 2;
for (var j = 0; j < line[i].length; j++) {
var char = line[i][j];
var cWidth = ctx.measureText(char).width;
ctx.globalAlpha = fade.indexOf(charIndex) > -1 ? Math.random()* 0.5+0.25 : 1;
ctx.fillText(char, textPosX + cWidth / 2, yPos);
textPosX += cWidth;
charIndex += 1
}
yPos += fontSize * textVertSpacing;
}
}
function animLoop(){
if((frameCount % flickerRate) === 0){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawStroked('THIS\nIS THE\nTEXT', 60, '#000', canvas.width / 2, canvas.height / 2, 0.9, [2, 4, 8, 12]);
}
frameCount ++;
requestAnimationFrame(animLoop);
}
<canvas id="canvas" width="500" height="200"></canvas>
Using this in a webpack setup so sorry it doesnt run!
// Code in UTIL
getRandomInt(max) {
return Math.floor(Math.random() * (max - 0 + 1)) + 0;
};
const $window = $(window);
let running = false;
const Hero = {
init() {
this.home = $('#home');
this.position = $('#hero');
this.canvas = $('#title');
this.ctx = this.canvas[0].getContext('2d');
this.width = this.position.width();
this.height = this.home.height();
this.ctx.lineWidth = 1.5;
this.fontSize = null;
this.letterSpacing = null;
if(this.position.lenth === 0) {
return;
}
if(running) {
return;
}
// Set hero opacity to 0 for animation
// $('#hero').css('opacity', 0);
this.size();
$window.on('resize', () => {
clearTimeout(this.debounce);
this.debounce = setTimeout( () => {
this.height = this.home.height();
this.width = this.position.width();
this.size();
}, 50);
});
},
size() {
running = true;
this.canvas[0].width = this.width;
this.canvas[0].height = this.height;
if(this.width < 1000) {
this.fontSize = 150;
this.letterSpacing = 5;
} else {
this.fontSize = 300;
this.letterSpacing = 30;
}
},
animate(frames) {
var frameCount = frames || 0;
const flickerRate = 4;
const fade = [Utils.getRandomInt(13), Utils.getRandomInt(13)];
if((frameCount % flickerRate) === 0){
this.ctx.clearRect(0, 0, this.width, this.height);
// Blue
this.drawStroked(this.fontSize, '#0426ff', -2, 2, true, fade);
// Green
this.drawStroked(this.fontSize, '#04ffae', 1, 2, true, fade);
// Pink
this.drawStroked(this.fontSize, '#ff29ad', 0, 0, true, fade);
// White
this.drawStroked(this.fontSize, '#fff', 0, 0, true, fade);
}
frameCount ++;
console.log(frameCount);
// requestAnimationFrame(this.animate);
setTimeout(() => {
this.animate(frameCount);
}, 0.5);
},
drawStroked(fontSize, color, offsetX, offsetY, flicker, fade) {
let line = 'CODE\nIN THE\nDARK'.split('\n'),
chars = line.join('');
// Set the font + size
this.ctx.font = fontSize + 'px ' + 'Kommissar';
// Set the colour
this.ctx.strokeStyle = color;
// Set the baseline
this.ctx.textBaseline = 'middle';
// Set the align
this.ctx.textAlign = 'center';
let letterSpacing = this.letterSpacing,
positionX = (this.width/2 + letterSpacing) + offsetX,
positionY = (this.height/4) + offsetY,
charIndex = 0;
for (var i = 0; i < line.length; i++) {
// get the width of the whole line
let width = this.ctx.measureText(line[i]).width;
// use the width to find start
var textPosX = positionX - width / 2;
for (let j = 0; j < line[i].length; j++) {
// get char
let char = line[i][j];
// get its width
let cWidth = this.ctx.measureText(char).width;
// check if char needs to fade
if(flicker) {
this.ctx.globalAlpha = fade.indexOf(charIndex) > -1 ? Math.random() * 0.5 + 0.25 : 0;
} else {
this.ctx.globalAlpha = 1;
}
// draw the char offset by half its width (center)
this.ctx.shadowColor = color;
this.ctx.shadowBlur = 15;
this.ctx.strokeText(char, textPosX + cWidth / 2, positionY);
// move too the next pos
textPosX += cWidth;
// count the char
charIndex += 1;
}
// move down one line
positionY += fontSize * 1.05;
}
}
};
export default Hero;
#home {
width: 100%;
#hero {
position: absolute;
z-index: 5;
top: 0;
left: 0;
width: 100%;
padding: 30px 0;
> canvas {
margin: 0 auto;
display: block;
}
}
}
<div id="home">
<div id="hero">
<canvas id="title"></canvas>
</div>
</div>
I'm building a dynamic radar chart, I got the code reviewed and followed the recommendation from fellow SO member.
This is how far I've come, but seem to have hit a roadblock:
var canv = document.getElementById('canvas');
var canv1 = document.getElementById('canvas1');
var point_xy = document.getElementById('point_xy');
var tipCanvas = document.getElementById("tip");
var tipCtx = tipCanvas.getContext("2d");
var point_xy_cords = [
[]
];
var pentagon_one = 24;
var pentagon_two = 18;
var pentagon_three = 12;
var pentagon_four = 6;
var pentagon_five = 0;
var circles = [];
var contx = canv.getContext('2d');
var contx1 = canv1.getContext('2d');
var offsetX = canv1.offsetLeft;
var offsetY = canv1.offsetTop;
contx.clearRect(0, 0, canv.width, canv.height);
function drawShape(ctx, x, y, points, radius1, radius2, alpha0) {
//points: number of points (or number of sides for polygons)
//radius1: "outer" radius of the star
//radius2: "inner" radius of the star (if equal to radius1, a polygon is drawn)
//angle0: initial angle (clockwise), by default, stars and polygons are 'pointing' up
var radius_size = radius1;
var i, angle, radius;
if (radius2 !== radius1) {
points = 2 * points;
}
for (var i = 0; i <= 5; i++) {
var temp = [];
contx1.beginPath();
for (var j = 0; j <= 4; j++) {
angle = j * 2 * Math.PI / points - Math.PI / 2 + alpha0;
radius = j % 2 === 0 ? radius_size : radius_size;
temp[j] = [(x + radius_size * Math.cos(angle)), (y + radius_size * Math.sin(angle))];
ctx.lineTo(temp[j][0], temp[j][1]);
}
ctx.closePath();
style(ctx);
radius_size = radius_size - 20;
point_xy_cords.push(temp);
}
point_xy.textContent = "[1] = " + point_xy_cords[1] + " y = " + point_xy_cords[1][1];
}
function style(ctx, fill) {
ctx.strokeStyle = "rgba(0, 109, 0, 1)";
ctx.lineWidth = 2;
if (fill) {
ctx.fillStyle = "rgba(74, 157, 33, 0.6)";
ctx.fill();
} else {
ctx.stroke()
}
//contx.fill();
}
var radius = 2;
var Circle = function(x, y, radius) {
this.left = x - radius;
this.top = y - radius;
this.right = x + radius;
this.bottom = y + radius;
this.point_clicked = [];
this.clicked = function(){
points[1][0] = x; //hardcoded part
points[1][1] = y; //hardcoded part
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
this.draw = function(ctx) {
//Draw all points
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI, false);
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(74, 157, 33, 1)";
ctx.fill();
ctx.stroke();
}
this.containsPoint = function(x,y){
return (x < this.right && x > this.left && y > this.top && y < this.bottom);
}
};
//Draw background
function drawBackgroundPentagons(ctx) {
drawShape(ctx, 120, 120, 5, 100, 100, 0);
}
drawBackgroundPentagons(contx1);
//Draw all the points
function drawPoints(){
for (var x = 1; x <= 5; x++){
for (var y = 0; y <= 4; y++){
var circle = new Circle(point_xy_cords[x][y][0], point_xy_cords[x][y][1], 8);
circle.draw(contx1);
circles.push(circle);
}
}
}
drawPoints();
function drawMainPentagon(ctx, points) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var x = 1; x <= 4; x++) {
ctx.lineTo(points[x][0], points[x][1]);
}
style(ctx, "fill");
ctx.closePath();
}
points = point_xy_cords[1];
drawMainPentagon(contx1, points);
function handleMouseDown(e, message) {
point_xy.textContent = (message);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canv1.onmousedown = function(e) {
var pos = getMousePos(canv1, e);
var clickedX = pos.x;
var clickedY = pos.y;
var tooltipText = "nothing";
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
if (circle.containsPoint(clickedX, clickedY)) {
circle.clicked();
return;
}
}
tooltip("points[0]", clickedX, clickedY);
};
function tooltip(text, clickedX, clickedY) {
tipCtx.fillStyle = "black";
tipCtx.fillRect(0, 0, canvas.width, canvas.height);
tipCtx.fillStyle = "white";
tipCtx.fillText(text, 5, 10);
tipCanvas.style.left = (clickedX + 15) + "px";
tipCanvas.style.top = (clickedY - 26) + "px";
}
canv1.onmouseover = function(e) {
return null;
}
canv1.onmouseout = function(e) {
return null;
}
canv1.onmousemove = function(e) {
return null;
}
#tip {
left: -200px;
top: 100px;
position: absolute;
float: left;
maxWidth: 200px;
backgroundColor: rgba(0, 0, 0, 0.8);
border: rgba(45, 65, 45, 1);
borderRadius: 5px;
color: #f9f9f9;
fontSize: 14px;
padding: 5px;
textAlign: left;
}
<div id="canvasesdiv" style="position:relative; width:400px; height:300px">
<canvas id="tip" width=100 height=100 style="z-index: 3;"></canvas>
<canvas id="canvas" style="z-index: 1;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<canvas id="canvas1" style="z-index: 2;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<div id='point_xy'></div>
If you click a point, it is suppose to move the point of the highlighted pentagon to the clicked point. It works, except I can't figure out what conditions to add in order to move the correct corner of the highlighted pentagon. In the above code I have hardcoded it, so that no matter which point you click, it will move point at index 0.
Any direction would be appreciated.
So what you want to do is let each circle know what spoke or radii it belongs to. Something like this:
var Circle = function(x, y, radius, spoke, value) {
this.x = x;
this.y = y;
this.radius = radius;
this.spoke = spoke;
this.value = value;
Now create them something like:
function drawPoints() {
for (var value = 1; value <= 5; value++){
for (var spoke = 0; spoke <= 4; spoke++){
var circle = new Circle(point_xy_cords[value][spoke][0], point_xy_cords[value][spoke][1], 8, spoke, value);
circle.draw(contx1);
circles.push(circle);
}
}
}
I changed the variable names to something meaningful. One note here is that you mix code to create the circles and code to draw them. You don't want to do this. Create them once on initialization and redraw them as changes are made (clicking). You don't want to re-create the circles every time you redraw.
Lastly change this:
// Circle
this.clicked = function(){
points[this.spoke][0] = this.x;
points[this.spoke][1] = this.y;
updateCanvas();
}
And elsewhere:
function updateCanvas() {
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
If I can make a suggestion, start with the simplest code you can. Start just by displaying the circles and pentagons, get that working cleanly and build onto it. Try and keep logic separate in your code. There are several places where you create objects and initialize arrays (like coords) while you are drawing which is both unnecssary but also means that you do it over and over instead of just once. There is a also lot of code here that is unnecessary.
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>
Right now I have some shapes (I only included the triangle as they're all generated the same way) that are generated when the user spins the mouse wheel. I want to leave a trail behind the shapes that slowly disappear. I've looked around and tried a few different ways but I can't seem to get any of them to work. Here's the code:
<html>
<head>
<script>
var canvas;
var context;
var triangles = [];
var timer;
function init() {
canvas = document.getElementById('canvas');
context = canvas.getContext("2d");
resizeCanvas();
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
canvas.onwheel = function(event) {
handleClick(event.clientX, event.clientY);
};
var timer = setInterval(resizeCanvas, 30);
}
function Triangle(x,y,triangleColor) {
this.x = x;
this.y = y;
this.triangleColor = triangleColor;
this.vx = Math.random() * 30 - 15;
this.vy = Math.random() * 30 - 15;
this.time = 100;
}
function handleClick(x,y) {
var colors = [[0,170,255], [230,180,125], [50,205,130]];
var triangleColor = colors[Math.floor(Math.random()*colors.length)];
triangles.push(new Triangle(x,y,triangleColor));
for (var i=0; i<triangles.length; i++) {
drawTriangle(triangles[i]);
}
}
function drawTriangle(triangle) {
context.beginPath();
context.moveTo(triangle.x,triangle.y);
context.lineTo(triangle.x+25,triangle.y+25);
context.lineTo(triangle.x+25,triangle.y-25);
var c = triangle.triangleColor
context.fillStyle = 'rgba(' + c[0] + ', ' + c[1] + ', ' + c[2] + ', ' + (triangle.time / 100) + ')';
context.fill();
}
function resizeCanvas() {
canvas.width = window.innerWidth-20;
canvas.height = window.innerHeight-20;
fillBackgroundColor();
for (var i=0; i<triangles.length; i++) {
var t = triangles[i];
drawTriangle(t);
if (t.x + t.vx > canvas.width || t.x + t.vx < 0)
t.vx = -t.vx
if (t.y + t.vy > canvas.height || t.y + t.vy < 0)
t.vy = -t.vy
if (t.time === 0) {
triangles.splice(i,1);
}
t.time -= 1;
t.x += t.vx;
t.y += t.vy;
}
}
function fillBackgroundColor() {
context.fillStyle = "black";
context.fillRect(0,0,canvas.width,canvas.height);
}
window.onload = init;
</script>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
</html>
The way I managed to do this is by storing past locations of my objects (for example 10 past locations, and draw line from one to the next one.
1.Add arrays pastX and pastY to your triangle object.
this.pastX = [];
this.pastY = [];
2.Each tick put every element 1 place further and add current location as first.
Right before you update triangle positions
for(var k = t.pastX.length; k > 0; k--){
if(k < 10){
t.pastX[k] = pastX[k-1];
t.pastY[k] = pastY[k-1];
}
t.pastX[0] = t.x;
t.pastY[0] = t.y;
draw lines between them like this (from 0 to 1, from 1 to 2 and so on);
for(var k = 0; k < t.pastX.length - 1; k++){
//draw line from pastX[k], pastY[k] to pastX[k + 1], pastY[k + 1]
}
Hope I helped you.