Javascript - fill() is filling using the wrong colour - javascript

I'm drawing on an HTML canvas using Javascript. I'm having an issue where one of my shapes isn't getting filled with the correct colour. It's a complicated situation but I'll do my best to simplify it.
The shape that is causing me grief is representing a plane on a radar screen. I have a diamond (which I draw using lineTo commands), a rectangle which will hold some information about that plane, and a line connecting the two. The plane has a property selected, which is set to false by default, but if you click on the diamond or rectangle, it selects that plane and sets its selected property to true.
When the plane is not selected, I want the shape to be drawn with a red outline, and filled with a black background. This is currently working. However, when the plane is selected, I want the diamond to be filled in with red. I have coded this scenario but when I click on a plane to select it, the fill colour of the diamond stays black, and the border gets bigger.
I can't figure out why this is happening. I'm guessing it has something to do with the previous settings of the canvas carrying over from the last time I drew a shape, but I set these values each time, so I can't work it out.
I have created a JSFiddle here: JSFiddle and here is the same code snippet
//---------------------------- MARK: Declarations ----------------------------\\
var canvas = document.getElementById("gameCanvas");
//canvas.width = window.innerWidth;
//canvas.height = window.innerHeight;
canvas.width = 1280;
canvas.height = 627;
var ctx = canvas.getContext("2d");
var timer = 0;
var seconds = 0;
var textToDisplay = "";
var strLevel = "";
var intPlaneInfoBoxSelected = -1;
var intPlaneInfoBoxSelectedXOffset = 0;
var intPlaneInfoBoxSelectedYOffset = 0;
var border = new Border();
var intLessWidth = 0;
var intLessHeight = 0;
var aRunways = [];
var aWayPoints = [];
var aPlanes = [];
var aAircraftTypes = [];
var aAirlineName = [];
var colourBrightGreen = "#1EFF00";
var colourWaypointBorder = "#FFFFFF";
var colourWaypointBackground = "#188C08";
var intWaypointLineWidth = 5;
var intRunwayWaypointLineWidth = 4;
var intInfoBoxLineWidth = 3;
var intEntrySpeed = 0.1;
//---------------------------- MARK: Object Creation ----------------------------\\
function WayPoint() {
this.topLeft = new Point();
this.widthHeight = 15;
this.name = "";
this.inbound = false;
this.outbound = false;
this.border = "";
this.highlighted = false;
}
function Runway() {
this.topLeft = new Point();
this.height = 0;
this.width = 0;
this.highlighted = false;
this.name = "";
this.wayPointTopLeft = new Point();
this.wayPointWidthHeight = 0;
}
function Plane() {
this.flightNumber = "";
this.status = "";
this.selected = false;
this.airRoute = [];
this.heading = 0;
this.speed = 0;
this.topPoint = new Point();
this.widthHeight = 20;
this.trailing1TopLeft = new Point();
this.trailing2TopLeft = new Point();
this.trailing3TopLeft = new Point();
this.trailing4TopLeft = new Point();
this.infoBoxTopLeft = new Point();
this.infoBoxWidth = 60;
this.infoBoxHeight = 30;
this.dx = 3;
this.dy = 3;
}
function AircraftType() {
this.type = "";
this.minSpeed = 0;
}
function Point() {
this.x = 0;
this.y = 0;
}
function Border() {
this.topLeft = new Point();
this.width = 0;
this.height = 0;
this.borderTop = 0;
this.borderBottom = 0;
this.borderLeft = 0;
this.borderRight = 0;
this.lineThickness = 10;
this.lineColour = "#1EFF00";
}
//---------------------------- MARK: Event Listeners ----------------------------\\
document.addEventListener("click", mouseClickHandler, false);
document.addEventListener("mousedown", mouseDownHandler, false);
document.addEventListener("mouseup", mouseUpHandler, false);
document.addEventListener("mousemove", mouseMoveHandler, false);
function mouseClickHandler(e) {
// Get Mouse Position
var rectCanvas = canvas.getBoundingClientRect();
var positionX = e.clientX;
var positionY = e.clientY;
var booClickedOnWaypoint = false;
var booClickedOnRunway = false;
var booClickedOnPlane = false;
for (i = 0; i < aPlanes.length; i++) {
var intPlaneLeft = aPlanes[i].topPoint.x - (aPlanes[i].widthHeight / 2);
var intPlaneRight = aPlanes[i].topPoint.x + (aPlanes[i].widthHeight / 2);
var intPlaneTop = aPlanes[i].topPoint.y;
var intPlaneBottom = aPlanes[i].topPoint.y + aPlanes[i].widthHeight;
var intInfoBoxLeft = aPlanes[i].infoBoxTopLeft.x - (intInfoBoxLineWidth / 2);
var intInfoBoxRight = aPlanes[i].infoBoxTopLeft.x + aPlanes[i].infoBoxWidth + (intInfoBoxLineWidth / 2);
var intInfoBoxTop = aPlanes[i].infoBoxTopLeft.y - (intInfoBoxLineWidth / 2);
var intInfoBoxBottom = aPlanes[i].infoBoxTopLeft.y + aPlanes[i].infoBoxHeight + (intInfoBoxLineWidth / 2);
if (((positionX >= intPlaneLeft) && (positionX <= intPlaneRight) && (positionY >= intPlaneTop) && (positionY <= intPlaneBottom)) || ((positionX >= intInfoBoxLeft) && (positionX <= intInfoBoxRight) && (positionY >= intInfoBoxTop) && (positionY <= intInfoBoxBottom))) {
aPlanes[i].selected = true;
booClickedOnPlane = true;
} else {
aPlanes[i].selected = false;
}
}
}
function mouseDownHandler(e) {
var positionX = e.clientX;
var positionY = e.clientY;
for (i = 0; i < aPlanes.length; i++) {
var intInfoBoxLeft = aPlanes[i].infoBoxTopLeft.x - (intInfoBoxLineWidth / 2);
var intInfoBoxRight = aPlanes[i].infoBoxTopLeft.x + aPlanes[i].infoBoxWidth + (intInfoBoxLineWidth / 2);
var intInfoBoxTop = aPlanes[i].infoBoxTopLeft.y - (intInfoBoxLineWidth / 2);
var intInfoBoxBottom = aPlanes[i].infoBoxTopLeft.y + aPlanes[i].infoBoxHeight + (intInfoBoxLineWidth / 2);
if ((positionX >= intInfoBoxLeft) && (positionX <= intInfoBoxRight) && (positionY >= intInfoBoxTop) && (positionY <= intInfoBoxBottom)) {
intPlaneInfoBoxSelected = i;
intPlaneInfoBoxSelectedXOffset = positionX - aPlanes[i].infoBoxTopLeft.x;
intPlaneInfoBoxSelectedYOffset = positionY - aPlanes[i].infoBoxTopLeft.y;
}
}
}
function mouseUpHandler(e) {
intPlaneInfoBoxSelected = -1;
}
function mouseMoveHandler(e) {
var positionX = e.clientX;
var positionY = e.clientY;
if (intPlaneInfoBoxSelected > -1) {
aPlanes[intPlaneInfoBoxSelected].infoBoxTopLeft.x = positionX - intPlaneInfoBoxSelectedXOffset;
aPlanes[intPlaneInfoBoxSelected].infoBoxTopLeft.y = positionY - intPlaneInfoBoxSelectedYOffset;
}
}
//---------------------------- MARK: Setup ----------------------------\\
function SetupArrays() {}
function SetupLevel() {
if (strLevel == "YSSY") {
//Waypoints
addWaypoint("LEFT", {
x: border.borderLeft,
y: 100
}, true, false);
}
}
function SetupCanvas() {
strLevel = "YSSY";
}
function LoadAircraftTypes() {
}
function LoadAirlineNames() {}
//---------------------------- MARK: Draw Existing Things ----------------------------\\
function drawPlanes() {
for (i = 0; i < aPlanes.length; i++) {
// Line
ctx.lineWidth = 4;
ctx.fillStyle = 'red';
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo((aPlanes[i].topPoint.x), (aPlanes[i].topPoint.y + (aPlanes[i].widthHeight / 2)));
ctx.lineTo((aPlanes[i].infoBoxTopLeft.x + (aPlanes[i].infoBoxWidth / 2)), (aPlanes[i].infoBoxTopLeft.y + (aPlanes[i].infoBoxHeight / 2)));
ctx.stroke();
// Plane
ctx.fillStyle = 'red';
ctx.strokeStyle = 'red';
if (aPlanes[i].selected == true) {
var pointX = aPlanes[i].topPoint.x;
var pointY = aPlanes[i].topPoint.y;
var width = aPlanes[i].widthHeight;
ctx.beginPath();
ctx.moveTo(pointX, pointY);
ctx.lineTo(pointX + (width / 2), pointY + (width / 2));
ctx.lineTo(pointX, pointY + width);
ctx.lineTo(pointX - (width / 2), pointY + (width / 2));
ctx.closePath();
ctx.fill();
} else {
var lineThickness = 3;
var pointX = aPlanes[i].topPoint.x;
var pointY = aPlanes[i].topPoint.y + lineThickness;
var width = aPlanes[i].widthHeight - (lineThickness * 2);
ctx.beginPath();
ctx.lineWidth = lineThickness;
ctx.moveTo(pointX, pointY);
ctx.lineTo(pointX + (width / 2), pointY + (width / 2));
ctx.lineTo(pointX, pointY + width);
ctx.lineTo(pointX - (width / 2), pointY + (width / 2));
ctx.closePath();
ctx.stroke();
}
// Flight Information Box
ctx.rect(aPlanes[i].infoBoxTopLeft.x, aPlanes[i].infoBoxTopLeft.y, aPlanes[i].infoBoxWidth, aPlanes[i].infoBoxHeight);
ctx.strokeStyle = 'red';
ctx.fillStyle = 'black';
ctx.lineWidth = clone(intInfoBoxLineWidth);
ctx.fill();
ctx.stroke();
}
}
function drawRunways() {
}
function drawText() {
}
function DrawWaypoints() {
}
function DrawCanvas() {
}
//---------------------------- MARK: Create Motion ----------------------------\\
function movePlanes() {
//TODO
if (aPlanes.length > 0) {
for (i = 0; i < aPlanes.length; i++) {
aPlanes[i].topPoint.x += aPlanes[i].dx;
aPlanes[i].infoBoxTopLeft.x += aPlanes[i].dx;
aPlanes[i].topPoint.y += aPlanes[i].dy;
aPlanes[i].infoBoxTopLeft.y += aPlanes[i].dy;
}
}
}
//---------------------------- MARK: Collision Detection ----------------------------\\
function detectLanded() {
}
//---------------------------- MARK: Create New Things ----------------------------\\
function addPlane() {
var tempPlane = new Plane();
var astrInboundOutbound = ["Inbound", "Outbound"];
var strRouteDirection = astrInboundOutbound[random(astrInboundOutbound.length)];
strRouteDirection = "Inbound";
if (strRouteDirection == "Inbound") {
// Start at an inbound waypoint
var astrInboundWaypoints = [];
for (var i = 0; i < aWayPoints.length; i++) {
if (aWayPoints[i].inbound == true) {
astrInboundWaypoints.push(aWayPoints[i]);
}
};
var intArrayPosition = random(astrInboundWaypoints.length);
var selectedWaypoint = astrInboundWaypoints[intArrayPosition];
tempPlane.topPoint = clone(selectedWaypoint.topLeft);
tempPlane.topPoint.x += (selectedWaypoint.widthHeight / 2);
tempPlane.topPoint.y += ((selectedWaypoint.widthHeight - tempPlane.widthHeight) / 2);
switch (selectedWaypoint.border) {
case "Left":
tempPlane.dx = intEntrySpeed;
tempPlane.dy = 0;
break;
case "Right":
tempPlane.dx = -1 * intEntrySpeed;
tempPlane.dy = 0;
break;
case "Top":
tempPlane.dx = 0;
tempPlane.dy = intEntrySpeed;
break;
case "Bottom":
tempPlane.dx = 0;
tempPlane.dy = -1 * intEntrySpeed;
break;
}
} else {
// Start at the upwind end of a runway
// TODO
}
tempPlane.infoBoxTopLeft = {
x: (tempPlane.topPoint.x - (tempPlane.infoBoxWidth / 2)),
y: (tempPlane.topPoint.y - tempPlane.infoBoxHeight - 20)
};
aPlanes.push(tempPlane);
}
function addRunway(name, topLeft, width, height) {
}
function addWaypoint(name, topLeft, inbound, outbound) {
var tempWaypoint = new WayPoint();
tempWaypoint.name = name;
tempWaypoint.topLeft = topLeft;
tempWaypoint.inbound = inbound;
tempWaypoint.outbound = outbound;
if (tempWaypoint.topLeft.x == border.borderLeft) {
tempWaypoint.border = "Left";
tempWaypoint.topLeft.x = border.borderLeft - (tempWaypoint.widthHeight / 2);
}
if (tempWaypoint.topLeft.y == border.borderTop) {
tempWaypoint.border = "Top";
tempWaypoint.topLeft.y = border.borderTop - (tempWaypoint.widthHeight / 2);
}
if (tempWaypoint.topLeft.x == border.borderRight) {
tempWaypoint.border = "Right";
tempWaypoint.topLeft.x = border.borderRight - (tempWaypoint.widthHeight / 2);
}
if (tempWaypoint.topLeft.y == border.borderBottom) {
tempWaypoint.border = "Bottom";
tempWaypoint.topLeft.y = border.borderBottom - (tempWaypoint.widthHeight / 2);
}
if (tempWaypoint.border != "") {
// Plus half the line width one all sides
tempWaypoint.topLeft.x -= (intWaypointLineWidth / 2);
tempWaypoint.topLeft.y -= (intWaypointLineWidth / 2);
tempWaypoint.widthHeight += intWaypointLineWidth;
}
aWayPoints.push(tempWaypoint);
}
//---------------------------- MARK: Timer ----------------------------\\
function timerTick() {
timer += 1;
if (timer % 1000 == 0) {
seconds++;
}
if (timer == 1) {
SetupArrays();
SetupCanvas();
SetupLevel();
}
if (timer == 300) {
addPlane();
}
/*if(timer==5){
document.location.reload();
}*/
}
//---------------------------- MARK: Supplimentary Routines ----------------------------\\
function random(intNumber) {
return Math.floor((Math.random() * intNumber));
}
function getTextWidth(text, font) {
ctx.font = font;
var metrics = ctx.measureText(text);
return metrics.width;
}
function getTextHeight(text, font) {
ctx.font = font;
var metrics = ctx.measureText(text);
return metrics.height;
}
function clone(obj) {
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
var copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
//---------------------------- MARK: DRAW ----------------------------\\
function draw() {
// All movement and display
// Call functions to make things happen, listeners will work on their own, no need to call
ctx.clearRect(0, 0, canvas.width, canvas.height);
movePlanes();
drawPlanes();
requestAnimationFrame(draw);
}
//---------------------------- MARK: Other ----------------------------\\
setInterval(timerTick, 1);
draw();
//---------------------------- END OF SCRIPT ----------------------------\\
<canvas id="gameCanvas"></canvas>
The function that draws the plane is called drawPlanes(), and I have put in a dozen or so blank lines after it so you can reach it quickly when scrolling.
You will see that when you run the code, the plane shape starts 'flying' from the left to right, and that it is in its unselected state. However, clicking on it to select it doesn't fill it in like it should, it just enlarges the shape size and moves the border out.
Can anyone help me out?

Turns out this issue had quite a simple fix. My problem was that I wasn't using beginPath() or closePath() correctly. I was able to fix my issues by putting a beginPath() before modifying the parameters for each shape, and a closePath() when I'd finished drawing it.
Here is my code after fixing the issue:
// Line
ctx.beginPath();
ctx.lineWidth = 4;
ctx.fillStyle = 'red';
ctx.strokeStyle = 'red';
ctx.moveTo((aPlanes[i].topPoint.x), (aPlanes[i].topPoint.y + (aPlanes[i].widthHeight / 2)));
ctx.lineTo((aPlanes[i].infoBoxTopLeft.x + (aPlanes[i].infoBoxWidth / 2)), (aPlanes[i].infoBoxTopLeft.y + (aPlanes[i].infoBoxHeight / 2)));
ctx.stroke();
ctx.closePath();
// Plane
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.strokeStyle = 'red';
if(aPlanes[i].selected == true) {
var pointX = aPlanes[i].topPoint.x;
var pointY = aPlanes[i].topPoint.y;
var width = aPlanes[i].widthHeight;
ctx.beginPath();
ctx.moveTo(pointX, pointY);
ctx.lineTo(pointX + (width / 2), pointY + (width / 2));
ctx.lineTo(pointX, pointY + width);
ctx.lineTo(pointX - (width / 2), pointY + (width / 2));
//ctx.closePath();
ctx.fill();
} else {
var lineThickness = 3;
var pointX = aPlanes[i].topPoint.x;
var pointY = aPlanes[i].topPoint.y + lineThickness;
var width = aPlanes[i].widthHeight - (lineThickness * 2);
ctx.beginPath();
ctx.lineWidth = lineThickness;
ctx.moveTo(pointX, pointY);
ctx.lineTo(pointX + (width / 2), pointY + (width / 2));
ctx.lineTo(pointX, pointY + width);
ctx.lineTo(pointX - (width / 2), pointY + (width / 2));
//ctx.closePath();
ctx.stroke();
}
ctx.closePath();
// Flight Information Box
ctx.beginPath();
ctx.rect(aPlanes[i].infoBoxTopLeft.x, aPlanes[i].infoBoxTopLeft.y, aPlanes[i].infoBoxWidth, aPlanes[i].infoBoxHeight);
ctx.strokeStyle = 'red';
ctx.fillStyle = 'black';
ctx.lineWidth = clone(intInfoBoxLineWidth);
ctx.fill();
ctx.stroke();
ctx.closePath();
Hopefully this will help someone!

Not 100% sure what you are trying to do so this is my best guess.
You missed a ctx.beginPath() at the bottom of the draw function. This caused the diamond to be rerendered. And in the selected if clause you were not calling fill, and the unselected else clause not calling stroke. But then I am not sure what you wanted.
Here is the code that makes it do what you describe in the question. Be careful with variable declarations. The i in the for loop was undeclared so was a global. If you called a function that did the same you would have had a very hard to trace bug.
function drawPlanes() {
var i,pointX,pointY,width,lineThickness; // Always declare the variables with var, const or let
lineThickness = 3;// <<================= Moved this out of loop as it did not seem to change
for (i = 0; i < aPlanes.length; i++) {
// Line
ctx.lineWidth = 4;
ctx.fillStyle = 'red';
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo((aPlanes[i].topPoint.x), (aPlanes[i].topPoint.y + (aPlanes[i].widthHeight / 2)));
ctx.lineTo((aPlanes[i].infoBoxTopLeft.x + (aPlanes[i].infoBoxWidth / 2)), (aPlanes[i].infoBoxTopLeft.y + (aPlanes[i].infoBoxHeight / 2)));
ctx.stroke();
if (aPlanes[i].selected == true) {
// set fill and stroke style
ctx.fillStyle = 'red'; // <<=================== I added this line
ctx.strokeStyle = 'red'; // <<=================== I added this line
ctx.lineWidth = lineThickness; // <<=================== I added this line
pointX = aPlanes[i].topPoint.x;
pointY = aPlanes[i].topPoint.y;
width = aPlanes[i].widthHeight;
ctx.beginPath();
ctx.moveTo(pointX, pointY);
ctx.lineTo(pointX + (width / 2), pointY + (width / 2));
ctx.lineTo(pointX, pointY + width);
ctx.lineTo(pointX - (width / 2), pointY + (width / 2));
ctx.closePath();
ctx.stroke(); // <<=================== I added this line
ctx.fill();
} else {
ctx.fillStyle = 'black'; // <<=================== I added this line
ctx.strokeStyle = 'red'; // <<=================== I added this line
ctx.lineWidth = lineThickness; // <<=================== I added this line
pointX = aPlanes[i].topPoint.x;
pointY = aPlanes[i].topPoint.y + lineThickness;
width = aPlanes[i].widthHeight - (lineThickness * 2);
ctx.beginPath();
ctx.moveTo(pointX, pointY);
ctx.lineTo(pointX + (width / 2), pointY + (width / 2));
ctx.lineTo(pointX, pointY + width);
ctx.lineTo(pointX - (width / 2), pointY + (width / 2));
ctx.closePath();
ctx.stroke();
ctx.fill(); // <<=================== I added this line
}
// Flight Information Box
ctx.beginPath(); // <<=================== I added this
ctx.rect(aPlanes[i].infoBoxTopLeft.x, aPlanes[i].infoBoxTopLeft.y, aPlanes[i].infoBoxWidth, aPlanes[i].infoBoxHeight);
ctx.strokeStyle = 'red';
ctx.fillStyle = 'black';
ctx.lineWidth = clone(intInfoBoxLineWidth); // <<==?????? you are cloning a number. Not sure why maybe you have something else in mind.
ctx.fill();
ctx.stroke();
}
}

Related

How can I reverse the direction of this square after it reaches a certain value?

I'm trying to create an idle animation where the red rectangle moves back and forth slightly in a loop. For some reason once it reaches the specified threshhold instead of proceeding to move in the opposite direction, it just stops.
What did I do wrong?
<canvas id="myCanvas" width="1500" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// Spaceship structure
var shipWidth = 250;
var shipHeight = 100;
// Canvas parameters
var cWidth = canvas.width;
var cHeight = canvas.height;
// Positioning variables
var centerWidthPosition = (cWidth / 2) - (shipWidth / 2);
var centerHeightPosition = (cHeight / 2) - (shipHeight / 2);
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
function drawShip(){
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.fillStyle = "#FF0000";
ctx.fillRect(centerWidthPosition,centerHeightPosition,shipWidth,shipHeight);
centerWidthPosition--;
if (centerWidthPosition < 400){
++centerWidthPosition;
}
requestAnimationFrame(drawShip);
}
drawShip();
</script>
#TheAmberlamps explained why it's doing that. Here I offer you a solution to achieve what I believe you are trying to do.
Use a velocity variable that changes magnitude. X position always increases by velocity value. Velocity changes directions at screen edges.
// use a velocity variable
var xspeed = 1;
// always increase by velocity
centerWidthPosition += xspeed;
// screen edges are 0 and 400 in this example
if (centerWidthPosition > 400 || centerWidthPosition < 0){
xspeed *= -1; // change velocity direction
}
I added another condition in your if that causes the object to bounce back and forth. Remove the selection after || if you don't want it doing that.
Your function is caught in a loop; once centerWidthPosition reaches 399 your conditional makes it increment back up to 400, and then it decrements back to 399.
here is another one as a brain teaser - how would go by making this animation bounce in the loop - basically turn text into particles and then reverse back to text and reverse back to particles and back to text and so on and on and on infinitely:
var random = Math.random;
window.onresize = function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.onresize();
var ctx = canvas.getContext('2d');
ctx.font = 'bold 50px "somefont"';
ctx.textBaseline = 'center';
ctx.fillStyle = 'rgba(255,255,255,1)';
var _particles = [];
var particlesLength = 0;
var currentText = "SOMETEXT";
var createParticle = function createParticle(x, y) {_particles.push(new Particle(x, y));};
var checkAlpha = function checkAlpha(pixels, i) {return pixels[i * 4 + 3] > 0;};
var createParticles = function createParticles() {
var textSize = ctx.measureText(currentText);
ctx.fillText(currentText,Math.round((canvas.width / 2) - (textSize.width / 2)),Math.round(canvas.height / 2));
var imageData = ctx.getImageData(1, 1, canvas.width, canvas.height);
var pixels = imageData.data;
var dataLength = imageData.width * imageData.height;
for (var i = 0; i < dataLength; i++) {
var currentRow = Math.floor(i / imageData.width);
var currentColumn = i - Math.floor(i / imageData.height);
if (currentRow % 2 || currentColumn % 2) continue;
if (checkAlpha(pixels, i)) {
var cy = ~~(i / imageData.width);
var cx = ~~(i - (cy * imageData.width));
createParticle(cx, cy);
}}
particlesLength = _particles.length;
};
var Point = function Point(x, y) {
this.set(x, y);
};
Point.prototype = {
set: function (x, y) {
x = x || 0;
y = y || x || 0;
this._sX = x;
this._sY = y;
this.reset();
},
add: function (point) {
this.x += point.x;
this.y += point.y;
},
multiply: function (point) {
this.x *= point.x;
this.y *= point.y;
},
reset: function () {
this.x = this._sX;
this.y = this._sY;
return this;
},
};
var FRICT = new Point(0.98);//set to 0 if no flying needed
var Particle = function Particle(x, y) {
this.startPos = new Point(x, y);
this.v = new Point();
this.a = new Point();
this.reset();
};
Particle.prototype = {
reset: function () {
this.x = this.startPos.x;
this.y = this.startPos.y;
this.life = Math.round(random() * 300);
this.isActive = true;
this.v.reset();
this.a.reset();
},
tick: function () {
if (!this.isActive) return;
this.physics();
this.checkLife();
this.draw();
return this.isActive;
},
checkLife: function () {
this.life -= 1;
this.isActive = !(this.life < 1);
},
draw: function () {
ctx.fillRect(this.x, this.y, 1, 1);
},
physics: function () {
if (performance.now()<nextTime) return;
this.a.x = (random() - 0.5) * 0.8;
this.a.y = (random() - 0.5) * 0.8;
this.v.add(this.a);
this.v.multiply(FRICT);
this.x += this.v.x;
this.y += this.v.y;
this.x = Math.round(this.x * 10) / 10;
this.y = Math.round(this.y * 10) / 10;
}
};
var nextTime = performance.now()+3000;
createParticles();
function clearCanvas() {
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
(function clearLoop() {
clearCanvas();
requestAnimationFrame(clearLoop);
})();
(function animLoop(time) {
ctx.fillStyle = 'rgba(255,255,255,1)';
var isAlive = true;
for (var i = 0; i < particlesLength; i++) {
if (_particles[i].tick()) isAlive = true;
}
requestAnimationFrame(animLoop);
})();
function resetParticles() {
for (var i = 0; i < particlesLength; i++) {
_particles[i].reset();
}}

arrow with modification point

Hi I'm trying to reproduce a draw.io effect. When you draw an arrow it display a blue point in the middle of the arrow that allows you to create angle between the two lines and it display two blue point that allow you to do the same with the two new line. I have added image below. It'll be easier to understand.
I wonder how to code dynamically these blue points that allow to "break" the line
var ctx = tempcanvas.getContext('2d'),
mainctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
x1,
y1,
isDown = false;
ctx.translate(0.5, 0.5);
tempcanvas.onmousedown = function(e) {
var rect = canvas.getBoundingClientRect();
x1 = e.clientX - rect.left;
y1 = e.clientY - rect.top;
isDown = true;
}
tempcanvas.onmouseup = function() {
isDown = false;
mainctx.drawImage(tempcanvas, 0, 0);
ctx.clearRect(0, 0, w, h);
}
tempcanvas.onmousemove = function(e) {
if (!isDown) return;
var rect = canvas.getBoundingClientRect(),
x2 = e.clientX - rect.left,
y2 = e.clientY - rect.top;
var p0={x1,y1};
var p1={x2,y2};
ctx.clearRect(0, 0, w, h);
drawLineWithArrowhead(p0,p1,25);
}
function drawLineWithArrowhead(p0,p1,headLength){
var PI=Math.PI;
var degreesInRadians225=225*PI/180;
var degreesInRadians135=135*PI/180;
var dx=p1.x2-p0.x1;
var dy=p1.y2-p0.y1;
var angle=Math.atan2(dy,dx);
// calc arrowhead points
var x225=p1.x2+headLength*Math.cos(angle+degreesInRadians225);
var y225=p1.y2+headLength*Math.sin(angle+degreesInRadians225);
var x135=p1.x2+headLength*Math.cos(angle+degreesInRadians135);
var y135=p1.y2+headLength*Math.sin(angle+degreesInRadians135);
ctx.beginPath();
// draw the line from p0 to p1
ctx.moveTo(p0.x1,p0.y1);
ctx.lineTo(p1.x2,p1.y2);
// draw partial arrowhead at 225 degrees
ctx.moveTo(p1.x2,p1.y2);
ctx.lineTo(x225,y225);
// draw partial arrowhead at 135 degrees
ctx.moveTo(p1.x1,p1.y1);
ctx.lineTo(x135,y135);
// stroke the line and arrowhead
ctx.stroke();
}
canvas {position:absolute;left:0;top:0}
#canvas {background:#eef}
<canvas id="canvas" width=400 height=400></canvas>
<canvas id="tempcanvas" width=400 height=400></canvas>
Example snippet
Sorry out of time (Weekend and all) to write a detailed explanation and no point wasting the code, so hope it helps.
const ctx = canvas.getContext("2d");
ctx.bounds = canvas.getBoundingClientRect();
const P2 = (x = 0, y = 0) => ({x, y});
const points = [];
const lineStyle = "#000";
const nearLineStyle = "#0AF";
const lineWidth = 2;
const nearLineWidth = 3;
const pointStyle = "#000";
const nearPointStyle = "#0AF";
const pointLineWidth = 1;
const nearPointLineWidth = 2;
const arrowSize = 18;
const pointSize = 5;
const nearPointSize = 15;
const checkerSize = 256; // power of two
const checkerCol1 = "#CCC";
const checkerCol2 = "#EEE";
const MIN_SELECT_DIST = 20; // in pixels;
var w = canvas.width, h = canvas.height;
var cw = w / 2, ch = h / 2;
var cursor = "default";
var toolTip = "";
const mouse = { x: 0, y: 0, button: 0 };
const drag = {dragging: false};
requestAnimationFrame(update);
function mouseEvents(e) {
mouse.x = e.pageX - ctx.bounds.left - scrollX;
mouse.y = e.pageY - ctx.bounds.top - scrollY;
if (e.type === "mousedown") { mouse.button |= 1 << (e.which - 1) }
else if (e.type === "mouseup") { mouse.button &= ~(1 << (e.which - 1)) }
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
const checkerboard = (()=> {
const s = checkerSize, s2 = s / 2;
const c = document.createElement("canvas");
c.height = c.width = checkerSize;
const ctx = c.getContext("2d", {alpha: false});
ctx.fillStyle = checkerCol1;
ctx.fillRect(0,0,s, s);
ctx.fillStyle = checkerCol2;
ctx.fillRect(0,0,s2,s2);
ctx.fillRect(s2,s2,s2,s2);
ctx.globalAlpha = 0.25;
var ss = s2;
while(ss > 8) {
ctx.fillStyle = ctx.createPattern(c, "repeat");
ctx.setTransform(1/8,0,0,1/8,0,0);
ctx.fillRect(0,0,s * 8,s * 8);
ss /= 2;
}
return ctx.createPattern(c, "repeat");
})();
function nearestPointLine(points, point, minDist){ // fills nearest object with nearest point and line to point if within minDist.
var i = 0, p1, dist;
nearest.reset(minDist);
const v1 = P2();
const v2 = P2();
const v3 = P2();
for (const p of points) {
v2.x = point.x - p.x;
v2.y = point.y - p.y;
dist = (v2.x * v2.x + v2.y * v2.y) ** 0.5;
if(dist < nearest.point.dist) {
nearest.point.dist = dist;
nearest.point.p = p;
nearest.point.idx = i;
}
if (p1) {
v1.x = p1.x - p.x;
v1.y = p1.y - p.y;
v2.x = point.x - p.x;
v2.y = point.y - p.y;
const u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
if (u >= 0 && u <= 1) { // is closest poin on line segment
v3.x = p.x + v1.x * u;
v3.y = p.y + v1.y * u;
//ctx.fillRect(v3.x, v3.y, 5, 5)
dist = ((v3.y - point.y) ** 2 + (v3.x - point.x) ** 2) ** 0.5;
if(dist < nearest.line.dist) {
nearest.line.dist = dist;
nearest.line.p1 = p1;
nearest.line.p2 = p;
nearest.line.idx = i;
nearest.line.onLine.x = v3.x;
nearest.line.onLine.y = v3.y;
}
}
}
p1 = p;
i ++;
}
if (nearest.point.idx > -1 && nearest.point.dist / 2 <= nearest.line.dist) {
nearest.active = nearest.point;
nearest.near = true;
} else if (nearest.line.idx > -1) {
nearest.active = nearest.line;
nearest.near = true;
}
}
function drawLine(p1, p2) {
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
}
function drawLineArrow(p1, p2) {
var nx = p1.x - p2.x;
var ny = p1.y - p2.y;
const d =( nx * nx + ny * ny) ** 0.5;
if(d > arrowSize) {
nx /= d;
ny /= d;
ctx.setTransform(-nx, -ny, ny, -nx, p2.x, p2.y);
ctx.beginPath()
ctx.fillStyle = ctx.strokeStyle;
ctx.moveTo(0, 0);
ctx.lineTo(-arrowSize, arrowSize / 2);
ctx.lineTo(-arrowSize, -arrowSize / 2);
ctx.fill();
ctx.setTransform(1,0,0,1,0,0);
}
}
function drawPoint(p, size = pointSize) {
ctx.rect(p.x - size / 2, p.y - size / 2, size, size);
}
function drawLines(points) {
var p1;
ctx.strokeStyle = lineStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath()
for(const p of points) {
if (p1) { drawLine(p1 ,p) }
p1 = p;
}
ctx.stroke();
if(points.length > 1) {
drawLineArrow(points[points.length - 2], p1);
}
}
function drawPoints(points) {
ctx.strokeStyle = pointStyle;
ctx.lineWidth = pointLineWidth;
ctx.beginPath()
for(const p of points) { drawPoint(p) }
ctx.stroke();
}
function sizeCanvas() {
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
ctx.bounds = canvas.getBoundingClientRect();
}
}
const nearest = {
point: { isPoint: true },
line: { onLine: P2() },
reset(minDist) {
nearest.point.dist = minDist;
nearest.point.idx = -1;
nearest.line.dist = minDist;
nearest.line.idx = -1;
nearest.active = null;
nearest.near = false;
},
draw() {
const a = nearest.active;
if (a) {
if (a.isPoint) {
ctx.strokeStyle = nearPointStyle;
ctx.lineWidth = nearPointLineWidth;
ctx.beginPath()
drawPoint(a.p, nearPointSize);
ctx.stroke();
} else {
ctx.strokeStyle = nearLineStyle;
ctx.lineWidth = nearLineWidth;
ctx.beginPath()
drawLine(a.p1, a.p2);
ctx.stroke();
ctx.strokeStyle = nearPointStyle;
ctx.lineWidth = nearPointLineWidth;
ctx.beginPath()
drawPoint(a.onLine, nearPointSize);
ctx.stroke();
}
}
}
}
function update() {
cursor = "crosshair";
toolTip = "";
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
sizeCanvas();
ctx.fillStyle = checkerboard;
ctx.fillRect(0, 0, w, h);
if (!drag.dragging) {
nearestPointLine(points, mouse, MIN_SELECT_DIST);
if (nearest.near && nearest.active.isPoint) { cursor = "move"; toolTip = "Drag to move point"}
else if (nearest.near) { cursor = "crosshair"; toolTip = "Click/drag to cut and drag new point" }
else {
if (points.length < 2) {
cursor = "crosshair";
toolTip ="Click to add point";
} else {
cursor = "default";
toolTip = "";
}
}
}
drawLines(points);
drawPoints(points);
nearest.draw();
if((mouse.button & 1) === 1) {
if (!drag.dragging) {
if(points.length < 2 && !nearest.near) {
points.push(P2(mouse.x, mouse.y));
mouse.button = 0;
} else if (nearest.near) {
if (nearest.active.isPoint) {
drag.point = nearest.active.p;
} else {
drag.point = P2(nearest.active.onLine.x, nearest.active.onLine.y);
points.splice(nearest.active.idx, 0, drag.point);
nearestPointLine(points, drag.point, 20);
}
drag.offX = drag.point.x - mouse.x;
drag.offY = drag.point.y - mouse.y;
drag.dragging = true;
}
}
if(drag.dragging) {
drag.point.x = drag.offX + mouse.x;
drag.point.y = drag.offY + mouse.y;
drag.point.x = drag.point.x < 1 ? 1 : drag.point.x > w - 2 ? w - 2 : drag.point.x;
drag.point.y = drag.point.y < 1 ? 1 : drag.point.y > h - 2 ? h - 2 : drag.point.y;
cursor = "none";
}
} else if((mouse.button & 1) === 0) {
drag.dragging = false;
drag.point = null;
}
canvas.title = toolTip;
canvas.style.cursor = cursor;
requestAnimationFrame(update);
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

HTML5 canvas particle explosion

I'm trying to get this particle explosion working. It's working but it looks like some frames does not get rendered. If I click many times to call several explosions it starts to uhm.. "lag/stutter". Is there something I have forgotten to do? It may look like the browser hangs when I click many times. Is it too much to have 2 for loops inside each other?
Attached my code so you can see.
Just try to click many times, and you will see the problem visually.
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = Date.now();
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
return;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
You are returning from iterating over the particles if one is too small. This causes the other particles of that explosion to render only in the next frame.
I have a working version:
// Request animation frame
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
const background = '#333'; // Background color
const particlesPerExplosion = 20;
const particlesMinSpeed = 3;
const particlesMaxSpeed = 6;
const particlesMinSize = 1;
const particlesMaxSize = 3;
const explosions = [];
let fps = 60;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length === 0) {
return;
}
for (let i = 0; i < explosions.length; i++) {
const explosion = explosions[i];
const particles = explosion.particles;
if (particles.length === 0) {
explosions.splice(i, 1);
return;
}
const particlesAfterRemoval = particles.slice();
for (let ii = 0; ii < particles.length; ii++) {
const particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size <= 0) {
particlesAfterRemoval.splice(ii, 1);
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
explosion.particles = particlesAfterRemoval;
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
let xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(
new explosion(xPos, yPos)
);
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (let i = 0; i < particlesPerExplosion; i++) {
this.particles.push(
new particle(x, y)
);
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
let num;
if (positive === false) {
num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1;
} else {
num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function (e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>* {margin:0;padding:0;overflow:hidden;}</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
Loops, break and continue.
The problem was caused when you checked for empty particle arrays and when you found a particle to remove.
The bugs
The following two statements and blocks caused the problem
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
return;
}
The returns stopped the rendering of particles, so you would sometimes return before drawing a single particle was rendered just because the first explosion was empty or first particle was too small.
Continue and break
You can use the continue token in javascript to skip the rest of a for, while, do loop
for(i = 0; i < 100; i++){
if(test(i)){
// need to skip this iteration
continue;
}
// more code
// more code
// continue skips all the code upto the closing }
} << continues to here and if i < 100 the loop continues on.
Or you can completely break out of the loop with break
for(i = 0; i < 100; i++){
if(test(i)){
// need to exit the for loop
break;
}
// more code
// more code
// break skips all the code to the first line after the closing }
}
<< breaks to here and if i remains the value it was when break was encountered
The fix
if (particles.length == 0) {
explosions.splice(i, 1);
continue;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
continue;
}
Your example with the fix
Your code with the fix. Befor I found it I started changing stuff.
Minor stuff.
requestAnimationFrame passes a time in milliseconds so to an accuracy of micro seconds.
You were setting then incorrectly and would have been losing frames. I changed the timing to use the argument time and then is just set to the time when a frame is drawn.
There are some other issues, nothing major and more of a coding style thing. You should capitalise objects created with new
function Particle(...
not
function particle(...
and your random is a overly complex
function randInt(min, max = min - (min = 0)) {
return Math.floor(Math.random() * (max - min) + min);
}
or
function randInt(min,max){
max = max === undefined ? min - (min = 0) : max;
return Math.floor(Math.random() * (max - min) + min);
}
randInt(100); // int 0 - 100
randInt(10,20); // int 10-20
randInt(-100); // int -100 to 0
randInt(-10,20); // int -10 to 20
this.xv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.yv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.size = randInt(particlesMinSize, particlesMaxSize);
And if you are using the same name in variables a good sign to create an object
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
Could be
const settings = {
particles : {
speed : {min : 3, max : 6 },
size : {min : 1 : max : 3 },
explosionCount : 20,
},
background : "#000",
}
Anyways your code.
var c = canvas;
var ctx = c.getContext('2d');
// Set full-screen
c.width = innerWidth;
c.height = innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = 0; // Zero start time
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
// as time is passed you need to start with requestAnimationFrame
requestAnimationFrame(draw);
function draw(time) { //requestAnimationFrame frame passes the time
requestAnimationFrame(draw);
delta = time - then;
if (delta > interval) {
then = time
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
//return;
continue;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
// return;
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>

Draw random coloured circles on canvas

I am trying to create a animation where circles run from right to left. The circles' colours are selected randomly by a function. I have created a fiddle where one circle runs from right to left. Now my function creates a random colour. This function is executed every second and the circle changes its colour every second, instead of a new circle with the random picked colour become created. How can I change it so that it draws a new circle every second on the canvas and doesn't only change the colour of the circle?
This is my function:
function getRandomElement(array) {
if (array.length == 0) {
return undefined;
}
return array[Math.floor(Math.random() * array.length)];
}
var circles = [
'#FFFF00',
'#FF0000',
'#0000FF'
];
function drawcircles() {
ctx.beginPath();
ctx.arc(ballx * 108, canvasHeight / 2, x*5, 0, 2*Math.PI, false);
ctx.fillStyle = getRandomElement(circles);
ctx.fill();
ctx.closePath;
}
Comments on your question:
You are changing the color of the fillStyle to a random color at each frame. This is the reason why it keeps "changing" color. Set it to the color of the circle:
context.fillStyle = circle.color;
make circles with x, y, diameter, bounciness, speed, and color using an array
draw and update them with requestAnimationFrame (mine is a custom function)
My answer:
I made this just last night, where some circles follow the cursor and "bounce" off the edges of the screen. I tested the code and it works.
I might post a link later, but here is all of the code right now...
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas"></canvas>
<script>
var lastTime = 0;
function requestMyAnimationFrame(callback, time)
{
var t = time || 16;
var currTime = new Date().getTime();
var timeToCall = Math.max(0, t - (currTime - lastTime));
var id = window.setTimeout(function(){ callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
var circles = [];
var mouse =
{
x: 0,
y: 0
}
function getCoordinates(x, y)
{
return "(" + x + ", " + y + ")";
}
function getRatio(n, d)
{
// prevent division by 0
if (d === 0 || n === 0)
{
return 0;
}
else
{
return n/d;
}
}
function Circle(x,y,d,b,s,c)
{
this.x = x;
this.y = y;
this.diameter = Math.round(d);
this.radius = Math.round(d/2);
this.bounciness = b;
this.speed = s;
this.color = c;
this.deltaX = 0;
this.deltaY = 0;
this.drawnPosition = "";
this.fill = function()
{
context.beginPath();
context.arc(this.x+this.radius,this.y+this.radius,this.radius,0,Math.PI*2,false);
context.closePath();
context.fill();
}
this.clear = function()
{
context.fillStyle = "#ffffff";
this.fill();
}
this.draw = function()
{
if (this.drawnPosition !== getCoordinates(this.x, this.y))
{
context.fillStyle = this.color;
// if commented, the circle will be drawn if it is in the same position
//this.drawnPosition = getCoordinates(this.x, this.y);
this.fill();
}
}
this.keepInBounds = function()
{
if (this.x < 0)
{
this.x = 0;
this.deltaX *= -1 * this.bounciness;
}
else if (this.x + this.diameter > canvas.width)
{
this.x = canvas.width - this.diameter;
this.deltaX *= -1 * this.bounciness;
}
if (this.y < 0)
{
this.y = 0;
this.deltaY *= -1 * this.bounciness;
}
else if (this.y+this.diameter > canvas.height)
{
this.y = canvas.height - this.diameter;
this.deltaY *= -1 * this.bounciness;
}
}
this.followMouse = function()
{
// deltaX/deltaY will currently cause the circles to "orbit" around the cursor forever unless it hits a wall
var centerX = Math.round(this.x + this.radius);
var centerY = Math.round(this.y + this.radius);
if (centerX < mouse.x)
{
// circle is to the left of the mouse, so move the circle to the right
this.deltaX += this.speed;
}
else if (centerX > mouse.x)
{
// circle is to the right of the mouse, so move the circle to the left
this.deltaX -= this.speed;
}
else
{
//this.deltaX = 0;
}
if (centerY < mouse.y)
{
// circle is above the mouse, so move the circle downwards
this.deltaY += this.speed;
}
else if (centerY > mouse.y)
{
// circle is under the mouse, so move the circle upwards
this.deltaY -= this.speed;
}
else
{
//this.deltaY = 0;
}
this.x += this.deltaX;
this.y += this.deltaY;
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
}
function getRandomDecimal(min, max)
{
return Math.random() * (max-min) + min;
}
function getRoundedNum(min, max)
{
return Math.round(getRandomDecimal(min, max));
}
function getRandomColor()
{
// array of three colors
var colors = [];
// go through loop and add three integers between 0 and 255 (min and max color values)
for (var i = 0; i < 3; i++)
{
colors[i] = getRoundedNum(0, 255);
}
// return rgb value (RED, GREEN, BLUE)
return "rgb(" + colors[0] + "," + colors[1] + ", " + colors[2] + ")";
}
function createCircle(i)
{
// diameter of circle
var minDiameter = 25;
var maxDiameter = 50;
// bounciness of circle (changes speed if it hits a wall)
var minBounciness = 0.2;
var maxBounciness = 0.65;
// speed of circle (how fast it moves)
var minSpeed = 0.3;
var maxSpeed = 0.45;
// getRoundedNum returns a random integer and getRandomDecimal returns a random decimal
var x = getRoundedNum(0, canvas.width);
var y = getRoundedNum(0, canvas.height);
var d = getRoundedNum(minDiameter, maxDiameter);
var c = getRandomColor();
var b = getRandomDecimal(minBounciness, maxBounciness);
var s = getRandomDecimal(minSpeed, maxSpeed);
// create the circle with x, y, diameter, bounciness, speed, and color
circles[i] = new Circle(x,y,d,b,s,c);
}
function makeCircles()
{
var maxCircles = getRoundedNum(2, 5);
for (var i = 0; i < maxCircles; i++)
{
createCircle(i);
}
}
function drawCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].draw();
ii++;
}
}
}
function clearCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].clear();
ii++;
}
}
}
function updateCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].keepInBounds();
circles[i].followMouse();
ii++;
}
}
}
function update()
{
requestMyAnimationFrame(update,10);
updateCircles();
}
function draw()
{
requestMyAnimationFrame(draw,1000/60);
context.clearRect(0,0,canvas.width,canvas.height);
drawCircles();
}
function handleError(e)
{
//e.preventDefault();
//console.error(" ERROR ------ " + e.message + " ------ ERROR ");
}
window.addEventListener("load", function()
{
window.addEventListener("error", function(e)
{
handleError(e);
});
window.addEventListener("resize", function()
{
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
});
window.addEventListener("mousemove", function(e)
{
mouse.x = e.layerX || e.offsetX;
mouse.y = e.layerY || e.offsetY;
});
makeCircles();
update();
draw();
});
</script>
</body>
</html>

Getting mouse position within canvas

I am trying to modified this effect to work within my canvas. However, I can't seem to get the mouse position to work properly. The hover area isn't contained to my canvas.
Here's a CSSdeck of how i tried to implement it: http://cssdeck.com/labs/ukktjtis
Effect:
function hoverText(){
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
keyword = "MacroPlay Games",
imageData,
density = 3,
mouse = {},
hovered = false,
colors = ["0,120,232", "8,200,255", "30,140,255"],
minDist = 20,
bounceFactor = 0.7;
var W = window.innerWidth, H = window.innerHeight;
canvas.width = W;
canvas.height = H;
document.addEventListener("mousemove", function(e) {
mouse.x = e.pageX-50;
mouse.y = e.pageY+200;
}, false);
// Particle Object
var Particle = function() {
this.w = Math.random() * 10.5;
this.h = Math.random() * 10.5;
this.x = -W;
this.y = -H;
this.free = false;
this.vy = -5 + parseInt(Math.random() * 10) / 2;
this.vx = -4 + parseInt(Math.random() * 8);
// Color
this.a = Math.random();
this.color = colors[parseInt(Math.random()*colors.length)];
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
this.draw = function() {
ctx.fillStyle = "rgba("+this.color+","+this.a+")";
ctx.fillRect(this.x, this.y, this.w, this.h);
}
};
var particles = [];
// Draw the text
function drawText() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#000000";
ctx.font = "100px 'Arial', sans-serif";
ctx.textAlign = "center";
ctx.fillText(keyword, W/2, H/2);
}
// Clear the canvas
function clear() {
ctx.clearRect(0, 0, W, H);
}
// Get pixel positions
function positionParticles() {
// Get the data
imageData = ctx.getImageData(0, 0, W, W);
data = imageData.data;
// Iterate each row and column
for (var i = 0; i < imageData.height; i += density) {
for (var j = 0; j < imageData.width; j += density) {
// Get the color of the pixel
var color = data[((j * ( imageData.width * 4)) + (i * 4)) - 1];
// If the color is black, draw pixels
if (color == 255) {
particles.push(new Particle());
particles[particles.length - 1].setPosition(i, j);
}
}
}
}
drawText();
positionParticles();
// Update
function update() {
clear();
for(i = 0; i < particles.length; i++) {
var p = particles[i];
if(mouse.x > p.x && mouse.x < p.x + p.w && mouse.y > p.y && mouse.y < p.y + p.h)
hovered = true;
if(hovered == true) {
var dist = Math.sqrt((p.x - mouse.x)*(p.x - mouse.x) + (p.y - mouse.y)*(p.y - mouse.y));
if(dist <= minDist)
p.free = true;
if(p.free == true) {
p.y += p.vy;
p.vy += 0.15;
p.x += p.vx;
// Collision Detection
if(p.y + p.h > H) {
p.y = H - p.h;
p.vy *= -bounceFactor;
// Friction applied when on the floor
if(p.vx > 0)
p.vx -= 0.1;
else
p.vx += 0.1;
}
if(p.x + p.w > W) {
p.x = W - p.w;
p.vx *= -bounceFactor;
}
if(p.x < 0) {
p.x = 0;
p.vx *= -0.5;
}
}
}
ctx.globalCompositeOperation = "lighter";
p.draw();
}
}
(function animloop(){
requestAnimFrame(animloop);
update();
})();
}
It's highly advised you use jquery (or some js lib) to avoid cross-browser issues like getting the mouse position.
You can easily get the mouse position in any browser using jquery like this:
// get the position of the canvas relative to the web page
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// then in the mouse handler, get the exact mouse position like this:
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
}
// tell the browser to send mousedown events to the handleMouseDown function
$("#canvas").mousedown(function(e){handleMouseDown(e);});
I personally prefer a library like hammer.js. I've use it for all my projects - check it out, it's pretty good.

Categories

Resources