i am trying to animate 3 different shapes with setTimeout , my question is how can i use multiple setTimeout to animate 3 different shapes also is there a better way to do this maybe using setInterval
window.onload = draw;
var x = 5;
var y = 5;
radius = 50;
var x2 = 50;
var y2 = 120;
var x3 = 53;
var y3 = 230;
var context;
var loopTimer;
function draw(){
var canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
context.save();
context.clearRect(0,0,720,550);
rectangle(x,y);
circle(x2,y2);
circle2(x3,y3);
}
function rectangle(x,y){
//drawing a rectangle
context.fillStyle = "rgba(93,119,188,237)";
context.clearRect(0,0,720,550);
context.rect(x, y, 50, 50);
context.fill();
context.lineWidth = 7;
context.strokeStyle = 'yellow';
context.stroke();
x += 1;
loopTimer = setTimeout('rectangle('+x+','+y+')',50);
}
function circle(x2,y2){
//darwong a circle
context.beginPath();
context.clearRect(0,0,720,550);
context.fillStyle = "#0000ff";
//Draw a circle of radius 20 at the current coordinates on the canvas.
context.arc(x2, y2, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
x2 += 1;
loopTimer = setTimeout('circle('+x2+','+y2+')',20);
}
function circle2(x3,y3){
//drawing a second circle
context.beginPath();
context.clearRect(0,0,720,550);
context.fillStyle = 'green';
context.arc(x3, y3, radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
context.lineWidth = 5;//border around the circle
context.strokeStyle = 'red';
context.stroke();
x3 += 1;
loopTimer = setTimeout('circle2('+x3+','+y3+')',20);
}
Animating objects
When doing digital animation there is never need for more than one single timer.
The key is to bind properties to the objects being animation such as its position, speed (or steps), color, shape and so forth.
The logic step therefor is to create custom objects that we can collect this information and use an update function to do all the updates for us in a single step within the loop.
Example
ONLINE DEMO HERE
Lets create an object collection where we store all our objects:
var animObjects = [];
Nothing fancy about that - just an array.
A single loop
To show how simple this can get I will show the loop itself first, then dissect the object. The loop simply iterates through our collection and calls the update method on each object:
function loop() {
/// clear canvas before redrawing all objects
ctx.clearRect(0, 0, demo.width, demo.height);
/// loop through the object collection and update each object
for(var i = 0, animObject; animObject = animObjects[i]; i++) {
animObject.update();
}
/// use this instead of setTimeout/setInterval (!)
requestAnimationFrame(loop);
}
Now, you noticed probably that we used requestAnimationFrame (rAF) here instead of setTimeout or setInterval. rAF allows us to synchronize to the monitor's update frequency whereas setTimout/setInterval cannot. In addition rAF works more efficient than the other two which will benefit us if we need to animate a lot of stuff.
The animation object
Now lets take a look at the object, how come we only need to call update and things animate?
As we saw earlier we create a animated circle object simply by calling:
var animObject = new animCircle(context, x, y, radius, color, stepX, stepY);
This allows us to set which context we want to use (in case we use several layers of canvas), the start position, color and number of steps per frame. Note that these properties can be changed during the animation (e.g. change radius and color).
The object itself looks like this -
function animCircle(ctx, x, y, r, color, stepX, stepY) {
/// keep a reference to 'this' context for external calls
var me = this;
/// make the parameters public so we can alter them
this.x = x;
this.y = y;
this.radius = r;
this.color = color;
this.stepX = stepX;
this.stepY = stepY;
/// this will update the object by incrementing x and y
this.update = function() {
me.x += me.stepX;
me.y += me.stepY;
/// additional updates can be inserted here
render();
}
/// and this takes care of drawing the object with current settings
function render() {
ctx.beginPath();
ctx.arc(me.x, me.y, me.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = me.color;
ctx.fill();
}
return this;
}
That's all there is to it!
The objects update() method will do all the calculations for us as well as call the internal render() method.
You can create many of these objects at various positions and speeds and animate all of them from a single loop.
I only created an object for the circle to keep things simple. You should be able to create an object for rectangle and what have you by using this as a base. You can of course extent the object to keep track of other things as well such as stroke color, angles and so forth.
I also made the objects bounce off the canvas' edges for the sake of demo. Please adjust as needed.
If i get the question right... why you just dont create three different functions like drawCircle? drawWhatever and create three setTimeouts? or something like that... ?
why you use so manny ' ? there is no reason to do that in:
var loopTimer = setTimeout('draw('+x+','+y+')',20);
Related
I am trying to create a 2d platformer, and the code is the base of it.
For some unknown reason, my final function draw mixes the other functions' properties (especially colour, and line width etc.).
If there is a type reason ( "this." is functioning inappropriate etc.)
I want to know for further projects.
Any good answer will be fully appreciated!.
/* main.js */
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d")
function Shooter() {
this.x = 100;
this.y = 500;
this.size = 50;
this.color = "blue";
this.borderColor = "black";
this.borderWidth = 5;
this.draw = function() {
ctx.fillRect(this.x, this.y, this.size, this.size);
ctx.strokeRect(this.x, this.y, this.size, this.size);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
}
}
function Gun() {
this.x = sh.x + sh.size / 2 + 10;
this.y = sh.y + sh.size / 2;
this.color = "grey";
this.borderColor = "brown";
this.borderWidth = 1;
this.width = 20;
this.height = 10;
this.draw = function() {
ctx.fillRect(this.x,this.y,this.width,this.height);
ctx.strokeRect(this.x,this.y,this.width,this.height);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
}
}
function Bullet() {
this.x = sh.x + sh.size * 2;
this.y = sh.y + sh.size / 2;
this.color = "orange";
this.radius = 5;
this.vx = 20;
this.borderColor = "green";
this.borderWidth = 2;
this.draw = function() {
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.stroke();
}
}
var sh = new Shooter();
var g = new Gun();
var b = new Bullet();
function draw() {
sh.draw();
g.draw();
b.draw();
requestAnimationFrame(draw);
}
draw();
/* main.css */
html, body {
overflow: hidden;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="main.css" />
</head>
<body>
<canvas id="canvas" width="1536px" height="754px"></canvas>
<!-- device innerWidth and innerHeight -->
<!-- make fullScreen to see the issue -->
<script src="main.js"></script>
</body>
</html>
The problem is that you first draw the shape and after that you set the fill and the stroke. Doing so you set the fill and stroke for the next shape.
In my code I'm using ctx.translate(0,-400) because otherwise the canvas would have been too large. Remove this line when setting your canvas size.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//setting the canvas size
canvas.width = 400;
canvas.height = 200;
ctx.translate(0,-400);
function Shooter() {
this.x = 100;
this.y = 500;
this.size = 50;
this.color = "blue";
this.borderColor = "black";
this.borderWidth = 5;
this.draw = function() {
// first set the colors for this shape
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
// then fill and stroke the shape
ctx.fillRect(this.x, this.y, this.size, this.size);
ctx.strokeRect(this.x, this.y, this.size, this.size);
}
}
function Gun() {
this.x = sh.x + sh.size / 2 + 10;
this.y = sh.y + sh.size / 2;
this.color = "grey";
this.borderColor = "brown";
this.borderWidth = 1;
this.width = 20;
this.height = 10;
this.draw = function() {
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.fillRect(this.x,this.y,this.width,this.height); ctx.strokeRect(this.x,this.y,this.width,this.height);
}
}
function Bullet() {
this.x = sh.x + sh.size * 2;
this.y = sh.y + sh.size / 2;
this.color = "orange";
this.radius = 5;
this.vx = 20;
this.borderColor = "green";
this.borderWidth = 2;
this.draw = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.strokeStyle = this.borderColor;
ctx.lineWidth = this.borderWidth;
ctx.fill();
ctx.stroke();
}
}
var sh = new Shooter();
var g = new Gun();
var b = new Bullet();
function draw() {
sh.draw();
g.draw();
b.draw();
requestAnimationFrame(draw);
}
draw();
canvas{border:1px solid}
<canvas id="canvas"></canvas>
Some extra points.
The existing answer will solve your problem, however I have some time and noticed some points about your code that can be improved.
Performance is king
When writing games (Or for that matter animating content) you will get to a point where the complexity of the animation (number of animated and drawn items) gets to a stage where the device can no longer do it at full frame rate. This become more of a problems as you try to cover a larger range of devices.
To get the most speed per line of code in Javascript you need to be aware of some simple rules in regard to objects and how they are created and destroyed (free memory for other objects).
JavaScript is managed
This means that as a programmer you don't need to worry about memory. You create an object and the memory for it is found for you. When you no longer need the object javascript will cleanup the memory so it is free for other objects.
This make programing in Javascript easier. However in animations this can become a problem as the code that manages the memory allocation and clean up (AKA delete allocated memory or GC Garbage Collection) takes time. If your animation is taking a long time to compute and render each frame then GC is forced to block your script and cleanup.
This management of memory is a the biggest source of Jank in Javascript animations (games).
It also introduces extra processing to create objects, as free memory has to be located and allocated when you create a new object. It gets worse on low end devices with low amounts of RAM. Creating new objects will more often force GC to free up memory for the new object (stealing precious CPU cycles). This make performance on low end devices not a linear degradation but rather a logarithmic degradation.
Types of Objects.
Objects (player, pickups, bullets, FX) can be classified by how long they live and how many can exist at one time. The lifetime of the object means you can take advantages of how JS manages memory to optimise objects and memory use.
Single instance object.
These are objects that exist only once in the animation. That is have a lifetime from start to end (in a game that may be from level start to level end).
Eg the player, the score display.
Example
The best ways to create these objects is as a singleton or object factory. The bullets example below this uses a singleton
EG Object factory to create player
function Shooter() {
// Use closure to define the properties of the object
var x = 100;
var y = 500;
const size = 50;
const style = {
fillStyle : "blue",
strokeStyle : "black",
lineWidth : 5,
}
// the interface object defines functions and properties that
// need to be accessed from outside this function
const API = {
draw() {
ctx.fillStyle = style.fillStyle;
ctx.strokeStyle = style.strokeStyle;
ctx.lineWidth = style.lineWidth;
ctx.fillRect(x, y, size, size);
ctx.strokeRect(x, y, size, size);
// it is quicker to do the above two lines as
/*
ctx.beginPath(); // this function is done automatically
// for fillRect and strokeRect. It
ctx.rect(x, y, size, size);
ctx.fill();
ctx.stroke();
*/
}
}
return API;
}
You create an use it just like any other object
const player = new Shooter();
// or
const player = Shooter(); / You dont need the new for this type of object
// to draw
player.draw();
Many instance object.
These are objects that have very short lives, maybe a few frames. They can also exist in the hundreds (think of sparks FX in an explosion, or rapid fire bullets)
In your code you have only one bullet, but I can imagine that you could have many, or rather than a bullets, it could be gribble or spark FXs.
Instantiation
Creating objects requires CPU cycles. Modern JS has many optimisations and thus there is not much difference in how you create objects. But there is still a difference and using the best method pays off, especially if you do it 1000's of times a second. (the last JS game I wrote handles upto 80,000 FX objects a second, most live no more than 3-6 frames)
For many short lived objects define a prototype or use the class syntax (this reduces creating time by around 50%). Store items in a pool when not used to stop GC hits and reduce instantiation overheads. Be render smart, and don't needlessly waste time waiting for GPU to do pointless state changes.
Memory
These short lived objects are the biggest source of slowdown and JANK due to memory management overhead creating and deleting them creates.
To combat the memory management overhead you can do that management yourself. The best way is complicated (use a pre allocated (at level start) bubble array and sets a max number for each object during the level).
Object Pool
The simplest solutions that will get you 90% of the best and allows for unlimited (depends on total RAM) is to use object pools.
A pool is an array of unused objects, that you would normally have let GC delete. Active objects are stored in an array. They do their thing and when done they are moves from the object array into the pool.
When you need a new object rather than create it with new Bullet() you first check if the pool has any. If it does you take the old object from the pool reset its properties and put it on the active array. If the pool is empty you create a new object.
This means that you never delete an object (over the lifetime of the level / animation). Because you check the pool each time you create the max memory the bullets will use will actually be less than creating new objects each time (GC does not delete immediately)
Rendering
The 2D context is a great API for rendering, but use it badly and you can kill the frame rate without changing the rendered appearance.
If you have many objects that use the same style. Don't render them as separate path. Defined one path, add the objects then fill and stroke.
Example
Example of a rapid fire buller pool. All bullets have the same style. This interface hides the bullets from the main code. You can only access the bullets API
const bullets = (() => { // a singleton
function Bullet() { }
const radius = 5;
const startLife = 100;
this.radius = 5;
const style = {
fillStyle : "orange",
strokeStyle : "green",
lineWidth : 2,
}
// to hold the interface
Bullets.prototype = {
init(x,y,dx,dy) { // dx,dy are delta
this.life = startLife;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
},
draw() {
ctx.arc(this.x, this.y, radius, 0 , Math.PI * 2);
},
move() {
this.x += this.dx;
this.y += this.dy;
this.life --;
}
};
const pool = []; // holds unused bullets
const bullets = []; // holds active bullets
// The API that manages the bullets
const API = {
fire(x,y,dx,dy) {
var b;
if(pool.length) {
b = bullets.pop();
} else {
b = new Bullet();
}
b.init(x,y,dx,dy);
bullets.push(bullets); // put on active array
},
update() {
var i;
for(i = 0; i < bullets.length; i ++) {
const b = bullets[i];
b.move();
if(b.life <= 0) { // is the bullet is no longer needed move to the pool
pool.push(bullets.splice(i--, 1)[0]);
}
}
},
draw() {
ctx.lineWidth = style.lineWidth;
ctx.fillStyle = style.fillStyle;
ctx.strokeStyle = style.strokeStyle;
ctx.beginPath();
for(const b of bullets) { b.draw() }
ctx.fill();
ctx.stroke();
},
get count() { return bullets.length }, // get the number of active
clear() { // remove all
pool.push(...bullets); // move all active to the pool;
bullets.length = 0; // empty the array;
},
reset() { // cleans up all memory
pool.length = 0;
bullets.length = 0;
}
};
return API;
})();
To use
...in the fire function
// simple example
bullets.fire(gun.x, gun.y, gun.dirX, gun.dirY);
...in the main render loop
bullets.update(); // update all bullets
if(bullets.count) { // if there are bullets to draw
bullets.draw();
}
...if restarting level
bullets.clear(); // remove bullets from previouse play
...if at end of level free the memory
bullets.clear();
The somewhere in between object.
These are objects that lay somewhere in between the above two types,
Eg a power ups, background items, enemy AI agents.
If object does not get created in high numbers, and have lives that may last more than a second to less than a complete level you need to ensure that they can be instantiated using the optimal method. (Personally I use pools (bubble array) for all but objects that live for the life of the level, but that can result in a lot of code)
Define the prototype
There are two way to effectively create these objects. Using the class syntax (personally hate this addition to JS) or define the prototype outside the instantiating function.
Example
function Gun(player, bullets) {
this.owner = player;
this.bullets = bullets; // the bullet pool to use.
this.x = player.x + player.size / 2 + 10;
this.y = player.y + player.size / 2;
this.width = 20;
this.height = 10;
const style = {
fillStyle : "grey",
strokeStyle : "brown",
lineWidth : 1,
};
}
// Moving the API to the prototype improves memory use and makes creation a little quicker
Gun.prototype = {
update() {
this.x = this.owner.x + this.owner.size / 2 + 10;
this.y = this.owner.y + this.owner.size / 2;
},
draw() {
ctx.lineWidth = this.style.lineWidth;
ctx.fillStyle = this.style.fillStyle;
ctx.strokeStyle = this.style.strokeStyle;
ctx.beginPath();
ctx.rect(this.x,this.y,this.width,this.height);
ctx.fill();
ctx.stroke();
},
shoot() {
this.bullets.fire(this.x, this.y, 10, 0);
},
}
Hope this helps. :)
I want to make a simple page that grows circles from its center ad infinitum. I'm almost there, but I can't figure out how to repeatedly grow them (resetting the radius i to 0 at a certain interval and calling the function again). I assume it will require a closure and some recursion, but I can't figure it out.
// Initialize canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.getElementsByTagName('body')[0].appendChild(canvas);
// Grow a circle
var i = 0;
var draw = function() {
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, i, 0, 2 * Math.PI);
ctx.fill();
i += 4;
window.requestAnimationFrame(draw);
}
draw();
Two things I'd do...
First, modify your draw function so that if the circle gets to a certain size, the i variable is reset back to zero. That starts the circle over again.
Second, add a setInterval timer to call your draw function at some time interval. See http://www.w3schools.com/js/js_timing.asp for details.
This setup will cause draw() to be called regularly, and the reset of i to zero makes it repeat.
So this did indeed require a closure. We wrap the initial function in a closure, and call it's wrapper function, which reinitializes I every time when called. draw() grows a single circle, and drawIt()() starts a new circle.
var drawIt = function(color) {
var i = 0;
return function draw() {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, i, 0, 2 * Math.PI);
ctx.fill();
i+=1*growthFactor;
// Growing circles until they are huge
if (i < canvas.width) {
window.requestAnimationFrame(draw);
if (i === spacing) {
circles++
drawIt(nextColor())();
}
}
}
};
drawIt(nextColor())();
})();
I found this code on this site, but as I'm a novice with .js, I can't get my head around what the best practice would be to return a square to its original colour. ie, click on a square it changes color, click again it goes back to what it was.
Do I
just color the square on a second click? or
put an else if statement somewhere?
<script>
function getSquare(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: 1 + (evt.clientX - rect.left) - (evt.clientX - rect.left)%10,
y: 1 + (evt.clientY - rect.top) - (evt.clientY - rect.top)%10
};
}
function drawGrid(context) {
for (var x = 0.5; x < 10001; x += 10) {
context.moveTo(x, 0);
context.lineTo(x, 10000);
}
for (var y = 0.5; y < 10001; y += 10) {
context.moveTo(0, y);
context.lineTo(10000, y);
}
context.strokeStyle = "#ddd";
context.stroke();
}
function fillSquare(context, x, y){
context.fillStyle = "red";
context.fillRect(x,y,9,9);
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
drawGrid(context);
canvas.addEventListener('click', function(evt) {
var mousePos = getSquare(canvas, evt);
fillSquare(context, mousePos.x, mousePos.y);
}, false);
</script>
First of all it's a bit dangerous to draw over a previous state, because sometimes you get 'ghosting'. but ignores this for now
And to get back to your questions, as nearly always it depends on the situation.
If you have a canvas that only changes when a user interacts you can (should) use an event handler to trigger a draw.
When you have other things animated you should split animation and values. (requestAnimationFrame) This does however require more work since everything should be stored as a variable.
You are going to need an if statement in the draw-code becaus you need to determine when it's colored and when not.
//https://jsfiddle.net/dgq9agy9/2/
function fillSquare(context, x, y){
var imgd = context.getImageData(x, y, 1, 1);
var pix = imgd.data;
console.log("rgba="+pix[0]+"-"+pix[1]+"-"+pix[2]+"-"+pix[3]);
if(pix[0]==0){
context.fillStyle = "red";
}else if(pix[0]==255){
context.fillStyle = "black";}
context.fillRect(x,y,9,9);
}
In my example I check if the pixel you clicked is red or black and switch them.
This is an easy solution because you don't need any extra variables, but it's not very clean.
If you want a clean solution, you could create an 2d-array with 1 or 0 values and draw a color depending on the number. But this requires alot/more work. Like translation the X,Y mouse click to the N-th number of square.(something like this: color_arr[x/9][y/9])
Im trying to create a ball which generates a different random number in the middle every time the function's called. This is my code for creating a ball object but i have no clue where to put the Math.random method and how to actually draw the number in the middle. Have you got any idea? Below is my JavaScript external file's code:
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var ball = {
radius:50,
x:200,
y:150,
color:"#00F",
draw:function() {
var arcStartAngle=0;
var arcEndAngle=2*Math.PI;
ctx.fillStyle=this.color
ctx.beginPath();
ctx.arc(this.x,this.y,this.radius,arcStartAngle,arcEndAngle);
ctx.fill();
}
}
ball.draw();
Inside your draw function generate a random number and draw it on the screen after the ball:
draw:function() {
...
// Ball drawing stuff
...
// Draw number in the middle
ctx.fillStyle= "#FFF";
var randNum = Math.floor(Math.random()*10);
ctx.fillText(randNum,this.x,this.y);
}
Here is an example
That is if you want the drawing of a number be apart of the ball. Otherwise you can move the code to some sort of drawRandomNumber() function and call that.
For example:
draw:function(){
var arcStartAngle=0;
var number = Math.round(Math.random() * 10)
...
createRandomNo: function() {
var number = Math.round(Math.random() * 10);
ctx.font = "bold 16px Arial";
ctx.fillStyle = "#000";
ctx.fillText(number, this.x, this.y);
},
and call createRandomNo() function after ball is drawn.
In a canvas I created a 2d context. In that context... with a function... I'm able to create some 'circle objects'. Now, what I want, is to get the ImageData of a single circle object instead of the image data of the whole context.
In the code below, you can see my wish commented out.
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var circle = function (X,Y) {
var that = this;
that.X = X;
that.Y = Y;
that.clicked = function(e) {
//
//
//!!!!!!!!!!!!!!
// Code below works fine, on context level
imgData = ctx.getImageData(e.pageX, e.pageY, 1, 1);
//
// Code below is at the level of the circle, that's what I want, but isn't working
imgData = that.getImageData(e.pageX, e.pageY, 1, 1);
//!!!!!!!!!!!!!!
//
//
alert(imgData.data[3]);
}
that.draw = function () {
ctx.save();
ctx.translate(that.X, that.Y);
ctx.fillStyle = '#33cc33';
ctx.beginPath();
ctx.arc(0, 0, 50, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
var circles = new Array();
circles.push(new circle(50,50));
document.addEventListener('click',function() {
circles.forEach(function(circ,index){
circ.clicked();
});
})
So, how do I get the image data on specific objects?
edit:
I understand that I need to draw the circle first, I do that later in my code, but what if I've got a background rect in the context, when I click next to the circle, it will get the imageData of the background rect, when I want to return the 0 value of the alpha rgba.
To this you need to log all your drawings as a "shadow canvas". The most common way is to create shape objects and store them in for example an array:
Draw the shape on canvas
Log its type, position, dimension, colors and orientation and store as an object and push that object to the array
When you need to get an isolated shape or object as an image:
Get mouse position (if you want to click on the object to select it)
Iterate the array of objects to see which object is "hit"
Create a temporary canvas of the dimension of that shape
Draw in the shape into the temporary canvas
Extract the data as an image (ctx.getImageData(x, y, w, h) or canvas.toDataURL())
When you need to resize your canvas you simply iterate all the objects and redraw them. You can even serialize your data for storage using this method.
An example of an object can be:
function Rectangle(x, y, w, h, fill, stroke) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.fill = fill;
this.stroke = stroke;
}
You can extend this object to render it self to canvas as well as giving you a bitmap of itself isolated from the other shapes. Add this to the above code:
Rectangle.prototype.render = function(ctx) {
if (this.fill) { /// fill only if fill is defined
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
if (this.stroke) { /// stroke only if stroke is defined
ctx.strokeStyle = this.stroke;
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
Rectangle.prototype.toBitmap = function() {
var tcanvas = document.createElement('canvas'), /// create temp canvas
tctx = tcanvas.getContext('2d'); /// temp context
tcanvas.width = this.width; /// set width = shape width
tcanvas.height = this.height;
tctx.translate(-this.x, -this.y); /// make sure shape is drawn at origin
this.render(tcxt); /// render itself to temp context
return tcanvas.toDataURL(); /// return image (or use getImageData)
}
You simply draw your shapes, create the object based on the positions etc:
var rect = new Rectangle(x, y, w, h, fillColor, strokeColor);
myShapeArray.push(rect);
When you need to render the shapes:
for(var i = 0, shape; shape = myShapeArray[i++];)
shape.render(ctx);
And when you need to get its bitmap (you retrieved its index in advance with the mouse click):
var image = myShapeArray[index].toBitmap();
And of course: you can make similar objects for circles, lines etc.
Hope this helps!
Remember that Canvas is a bitmap graphics tool. Anything you draw into a single context becomes part and parcel of the same object. You can't get separate image data for each "object" you used to draw on that canvas... it's painted ... flattened ... into those pixel positions for that bitmap as soon as you hit draw().
The only way you could do something like what you are looking for would be to create separate canvas contexts that you overlay on top of each other. This would be better handled by utilizing a library such as KineticJS (http://www.html5canvastutorials.com/kineticjs/html5-canvas-events-tutorials-introduction-with-kineticjs/). The only other option would be to use an object oriented drawing tool such as SVG, (through Raphael.js, for example: http://raphaeljs.com) which does preserve separate objects in the the graphics space.
For reference about getImageData, see http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/
You can use trigonometry instead of trying to locate your colors with getImageData.
For example, if you have a circle defined like this:
var centerX=150;
var centerY=150;
var radius=20;
var circleColor="red";
Then you can test if any x,y is inside that circle like this:
// returns true if x,y is inside the red circle
isXYinCircle(140,140,centerX,centerY,radius);
function isXYinCircle(x,y,cx,cy,r){
var dx=x-cx;
var dy=y-cy;
return(dx*dx+dy*dy<=r*r);
}
If the x,y is inside that red circle then you know the color at x,y is "red"
If you have multiple overlapping circles you can test each circle in increasing z-index order. The last circle that reports x,y inside will be the color at x,y.
It is because that is not a CanvasGraphicsContext. Try:
that.draw();
imgData = ctx.getImageData(e.pageX, e.pageY, 1, 1);
At first, I create my 2 canvas elements. 1 to display, 1 to calculate the pixeldata.
var c = document.getElementById('canvas');
var c2 = document.getElementById('canvas2');
var ctx = c.getContext('2d');
var ctx2 = c2.getContext('2d');
var width = window.innerWidth,
height = window.innerHeight;
c.width = ctx.width = c2.width = ctx2.width = width;
c.height = ctx.height = c2.height = ctx2.height = height;
Than I make my function to create an image
function Afbeelding(src, X, Y, W, H) {
var that = this;
that.X = X;
that.Y = Y;
that.W = W;
that.H = H;
that.onClick = function () { };
that.image = new Image(that.W, that.H);
that.image.src = src;
that.draw = function (context) {
context = (typeof context != 'undefined') ? context : ctx;
context.save();
context.translate(that.X, that.Y);
context.drawImage(that.image, 0, 0, that.W, that.H);
context.restore();
}
When a document.click event is fired, the next function (inside the Afbeelding function) will be called:
that.clicked = function (e) {
if ((e.pageX > that.X - (that.W / 2) && e.pageX < that.X + (that.W / 2)) && (e.pageY > that.Y - (that.H / 2) && e.pageY < that.Y + (that.H / 2))) {
if (that.isNotTransparent(e)) {
that.onClick();
}
}
}
This function (also inside the Afbeelding function) is used to check the pixel for transparancy.
that.isNotTransparent = function (e) {
var result = false;
ctx2.clearRect(0, 0, width, height);
that.draw(ctx2);
var imgData = ctx2.getImageData(e.pageX, e.pageY, 1, 1);
ctx2.clearRect(0, 0, width, height);
if (imgData.data[3] > 0) {
result = true;
}
return result;
}
}
And all below is to lauch the things up above.
var items = new Array();
var afb = new Afbeelding();
afb.draw();
afb.onClick = function () {
alert('clicked');
}
items.push(afb);
document.addEventListener('mousedown', function (e) {
items.forEach(function (item, index) {
item.clicked(e);
});
});