This is my code:
<!DOCTYPE html>
<html>
<head>
<!-- Load the Paper.js library -->
<script type="text/javascript" src="paper.js"></script>
<!-- Define inlined PaperScript associate it with myCanvas -->
<script type="text/paperscript" canvas="myCanvas">
// Create a Paper.js Path to draw a line into it:
var path = new Path();
// Give the stroke a color
path.strokeColor = 'black';
var start = new Point(100, 100);
// Move to start and draw a line from there
path.moveTo(start);
// Note the plus operator on Point objects.
// PaperScript does that for us, and much more!
function onFrame(event) {
// Your animation code goes in here
for (var i = 0; i < 100; i++) {
path.add(new Point(i, i));
}
}
</script>
</head>
<body style="margin: 0;">
<canvas id="myCanvas"></canvas>
</body>
</html>
When the page is loaded, a line is drawn. But I am trying to animate the drawing of the line from point A to B. My attempt above doesn't seem to do anything... it just draws the line on page load with no animation of the actual line going from A to B.
Ref. http://paperjs.org/download/
Since you are running the for-loop on every frame, you're re-creating the same line segments over and over, all at once. Instead, you need to add one segment per frame:
// Create a Paper.js Path to draw a line into it:
var path = new Path();
// Give the stroke a color
path.strokeColor = 'black';
var start = new Point(100, 100);
// Move to start and draw a line from there
// path.moveTo(start);
// Note the plus operator on Point objects.
// PaperScript does that for us, and much more!
function onFrame(event) {
// Your animation code goes in here
if (event.count < 101) {
path.add(start);
start += new Point(1, 1);
}
}
The if statement serves as a limit for the line's length.
Also note that the path.moveTo(start) command does not mean anything if your path does not yet have any segments.
If you don't want to add points every frame, but only change the length of a line, simply change the position of one of the segments. First create add two segments to your path, then change the position of the second segment's point per frame event:
// Create a Paper.js Path to draw a line into it:
var path = new Path();
// Give the stroke a color
path.strokeColor = 'black';
path.add(new Point(100, 100));
path.add(new Point(100, 100));
// Move to start and draw a line from there
// path.moveTo(start);
// Note the plus operator on Point objects.
// PaperScript does that for us, and much more!
function onFrame(event) {
// Your animation code goes in here
if (event.count < 101) {
path.segments[1].point += new Point(1, 1);
}
}
Related
I have been practicing using sprites for a game I am going to make and have watched and read a few tutorials, I thought I was close to getting my sprite to appear so I could finally start my game but while practicing I cant get it to work, I have dont 2 seperate tutorials where I can get the sprite and the background to appear by themselfs but cannot get them to work together, I have been using EaselJS too. some of the sprite animation code has been copied from tutorials too.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>sprite prac<title>
<!-- EaselJS library -->
<script src="lib/easel.js"></script>
<script>
// Initialize on start up so game runs smoothly
function init() {
canvas = document.getElementById("canvas");
stage = new Stage(canvas);
bg = new Image();
bg.src = "img/grassbg.jpg";
bg.onload = setBG;
stage.addChild(background);
imgMonsterARun = new Image();
imgMonsterARun.onload = handleImageLoad;
imgMonsterARun.onerror = handleImageError;
imgMonsterARun.src = "img/MonsterARun.png";
stage.update();
}
function handleImageLoad(e) {
startGame();
}
// Simple function for setting up the background
function setBG(event){
var bgrnd = new Bitmap(bg);
stage.addChild(bgrnd);
stage.update();
}
function startGame() {
// create a new stage and point it at our canvas:
stage = new createjs.Stage(canvas);
// grab canvas width and height for later calculations:
screen_width = canvas.width;
screen_height = canvas.height;
// create spritesheet and assign the associated data.
var spriteSheet = new createjs.SpriteSheet({
// image to use
images: [imgMonsterARun],
// width, height & registration point of each sprite
frames: {width: 64, height: 64, regX: 32, regY: 32},
animations: {
walk: [0, 9, "walk"]
}
});
// create a BitmapAnimation instance to display and play back the sprite sheet:
bmpAnimation = new createjs.BitmapAnimation(spriteSheet);
// start playing the first sequence:
bmpAnimation.gotoAndPlay("walk"); //animate
// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
bmpAnimation.shadow = new createjs.Shadow("#454", 0, 5, 4);
bmpAnimation.name = "monster1";
bmpAnimation.direction = 90;
bmpAnimation.vX = 4;
bmpAnimation.x = 16;
bmpAnimation.y = 32;
// have each monster start at a specific frame
bmpAnimation.currentFrame = 0;
stage.addChild(bmpAnimation);
// we want to do some work before we update the canvas,
// otherwise we could use Ticker.addListener(stage);
createjs.Ticker.addListener(window);
createjs.Ticker.useRAF = true;
createjs.Ticker.setFPS(60);
}
//called if there is an error loading the image (usually due to a 404)
function handleImageError(e) {
console.log("Error Loading Image : " + e.target.src);
}
function tick() {
// Hit testing the screen width, otherwise our sprite would disappear
if (bmpAnimation.x >= screen_width - 16) {
// We've reached the right side of our screen
// We need to walk left now to go back to our initial position
bmpAnimation.direction = -90;
}
if (bmpAnimation.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpAnimation.direction = 90;
}
// Moving the sprite based on the direction & the speed
if (bmpAnimation.direction == 90) {
bmpAnimation.x += bmpAnimation.vX;
}
else {
bmpAnimation.x -= bmpAnimation.vX;
}
// update the stage:
stage.update();
}
</script>
</head>
<body onload="init();">
<canvas id="canvas" width="500" height="500" style="border: thin black solid;" ></canvas>
</body>
</html>
There are a few places where you are using some really old APIs, which may or may not be supported depending on your version of EaselJS. Where did you get the easel.js script you reference?
Assuming you have a version of EaselJS that matches the APIs you are using, there are a few issues:
You add background to the stage. There is no background, so you are probably getting an error when you add it. You already add bgrnd in the setBackground method, which should be fine. If you get an error here, then this could be your main issue.
You don't need to update the stage any time you add something, just when you want the stage to "refresh". In your code, you update after setting the background, and again immediately at the end of your init(). These will fire one after the other.
Are you getting errors in your console? That would be a good place to start debugging. I would also recommend posting code if you can to show an actual demo if you continue to have issues, which will help identify what is happening.
If you have a newer version of EaselJS:
BitmapAnimation is now Sprite, and doesn't support direction. To flip Sprites, use scaleX=-1
Ticker no longer uses addListener. Instead it uses the EventDispatcher. createjs.Ticker.addEventListener("tick", tickFunction);
You can get new versions of the CreateJS libraries at http://code.createjs.com, and you can get updated examples and code on the website and GitHub.
I am looking for a way to wrap a bitmap image around the canvas, for an infinite scrolling effect. I'm looking at EaselJS but clean javascript code will also suffice.
Right now I am displacing an image to the left, and when it reaches a certain mark, it resets itself.
Coming from actionscript, there was an option to "wrap" the pixels of a bitmap around to the other side, thereby never really displacing the image, instead you were wrapping the pixels inside the image. Is this possible in javascript with canvas?
My current code:
this.update = function() {
// super large graphic
_roadContainer.x -= 9;
if(_roadContainer.x < -291) _roadContainer.x = 0;
}
Start with a good landscape image.
Flip the image horizontally using context.scale(-1,1).
Combine the flipped image to the right side of the original image.
Because we have exactly mirrored the images, the far left and right sides of the combined image are exactly the same.
Therefore, as we pan across the combined image and “run out of image”, we can just add another copy of the combined image to the right side and we have infinite + seamless panning.
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/ywDp5/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// thanks Paul Irish for this RAF fallback shim
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var infiniteImage;
var infiniteImageWidth;
var img=document.createElement("img");
img.onload=function(){
// use a tempCanvas to create a horizontal mirror image
// This makes the panning appear seamless when
// transitioning to a new image on the right
var tempCanvas=document.createElement("canvas");
var tempCtx=tempCanvas.getContext("2d");
tempCanvas.width=img.width*2;
tempCanvas.height=img.height;
tempCtx.drawImage(img,0,0);
tempCtx.save();
tempCtx.translate(tempCanvas.width,0);
tempCtx.scale(-1,1);
tempCtx.drawImage(img,0,0);
tempCtx.restore();
infiniteImageWidth=img.width*2;
infiniteImage=document.createElement("img");
infiniteImage.onload=function(){
pan();
}
infiniteImage.src=tempCanvas.toDataURL();
}
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/mountain.jpg";
var fps = 60;
var offsetLeft=0;
function pan() {
// increase the left offset
offsetLeft+=1;
if(offsetLeft>infiniteImageWidth){ offsetLeft=0; }
ctx.drawImage(infiniteImage,-offsetLeft,0);
ctx.drawImage(infiniteImage,infiniteImage.width-offsetLeft,0);
setTimeout(function() {
requestAnimFrame(pan);
}, 1000 / fps);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=500 height=143></canvas><br>
</body>
</html>
You can achieve this quite easily with the html5 canvas.
Look at the drawImage specification here :
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#drawing-images
drawImage comes in 3 flavors, the first being a simple copy of the image, the second allowing to scale the image, and the third is the one you seek, it allows to perform clipping in a single call.
What you have to do :
- have a counter that will move from zero to the width of your image, then loop to zero again.
- each frame, draw the maximum of the image that you can on the canvas.
- If there is still some part of the canvas not drawn, draw again the image starting from zero to fill the canvas.
i made a fiddle, the only part that matters is in the animate function (other things are tools i often use in my fiddles).
(Rq : I assumed in this example that two images would be enough to fill the canvas.)
http://jsfiddle.net/gamealchemist/5VJhp/
var startx = Math.round(startPos);
var clippedWidth = Math.min(landscape.width - startx, canvasWidth);
// fill left part of canvas with (clipped) image.
ctx.drawImage(landscape, startx, 0, clippedWidth, landscape.height,
0, 0, clippedWidth, landscape.height);
if (clippedWidth < canvasWidth) {
// if we do not fill the canvas
var remaining = canvasWidth - clippedWidth;
ctx.drawImage(landscape, 0, 0, remaining, landscape.height,
clippedWidth, 0, remaining, landscape.height);
}
// have the start position move and loop
startPos += dt * rotSpeed;
startPos %= landscape.width;
To answer my own question: I found a way to achieve this effect with EaselJS. The great benefit of this method is that you don't have to check for the position of your bitmap. It will scroll infinitely - without ever resetting the position to 0.
The trick is to fill a shape with a bitmapfill. You can set a bitmapfill to repeat infinitely. Then you use a Matrix2D to decide the offset of the bitmapfill. Since it repeats automatically, this will generate a scrolling effect.
function.createRoad() {
// road has a matrix, shape and image
_m = new createjs.Matrix2D();
// this gets the image from the preloader - but this can be any image
_r = queue.getResult("road");
// this creates a shape that will hold the repeating bitmap
_roadshape = new createjs.Shape();
// put the shape on the canvas
addChild(_roadshape);
}
//
// the draw code gets repeatedly called, for example by requestanimationframe
//
function.drawRoad() {
// var _speed = 4;
_m.translate(-_speed, 0);
_roadshape.graphics.clear().beginBitmapFill(_r, "repeat", _m).rect(0, 0, 900, 400);
}
I have been around this problem for days now.I have written my code in constructor pattern for the first time.I want to extend the height of 10 bezier lines in transition.I have tried kineticjs(i failed),tried Setinterval(creates jerk in animation).So I finally resorted to requestAnimationFrame.But because of this constructor pattern,I am totally confused where to include it and what changes are to be made.
This is what I have done so far---JSFIDDLE
So basically I will be extending my endY and cpY1 and cpy2 in transition.Onmouseover of canvas the height of all bezier lines must increase in transition giving it an animation like feel.
JAVASCRIPT:
//for entire code please have a look at the fiddle.this is just 10% of my code
//for simplification purpose,you can use 3 instances instead of 9!!!
(function() {
hair = function() {
return this;
};
hair.prototype={
draw_hair:function(a,b,c,d,e,f,g,h){
var sx =136+a;//start position of curve.used in moveTo(sx,sy)
var sy =235+b;
var cp1x=136+c;//control point 1
var cp1y=222+d;
var cp2x=136+e;//control point 2
var cp2y=222+f;
var endx=136+g;//end points
var endy=210+h;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.strokeStyle="grey";
context.lineWidth="8";
context.beginPath();
context.moveTo(sx,sy);
context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,endx,endy);
context.lineCap = 'round';
context.stroke();
}
};
})();
Here is the answer you wanted about growing hair.
This also has the info you wanted about how to create a hair "object".
Code and a Fiddle: http://jsfiddle.net/m1erickson/8K825/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
window.requestAnimationFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// the Hair "object"
// Hair is a containing object that hosts a user-specified # of hairs
var Hair = (function () {
// constructor
function Hair(x,y,width,height,haircount) {
this.x=x;
this.y=y;
this.width=width;
this.height=height;
this.right=this.x+this.width;
this.bottom=this.y+this.height;
this.hairCount=haircount;
this.startX=x+20;
this.startY=y+height-3; //235;
this.hairHeight=25;
this.hairGrowth=0;
this.lastEndX=[];
for(var i=0;i<haircount;i++){
this.lastEndX[i]= x+20+(i*15);
}
}
// grows the hair
// works by changing the Y value of the end & control points
Hair.prototype.grow = function(increment){
this.hairGrowth+=increment;
return(this.hairGrowth);
}
// draw all the hairs
Hair.prototype.draw = function(mouseX){
// clear this object's space on the canvas
// and set its styles
ctx.clearRect(this.x,this.y,this.width,this.height);
ctx.beginPath();
ctx.strokeStyle="grey";
ctx.lineWidth=7;
ctx.lineCap = 'round';
ctx.beginPath();
for(var i=0;i<this.hairCount;i++){
// straight hair
var sx=cp1x=cp2x= this.startX+(i*15);
var sy= this.startY;
var cp1y = cp2y = (this.startY-(this.hairHeight+this.hairGrowth)/2);
var endy = this.startY-this.hairHeight-this.hairGrowth;
var endx = this.lastEndX[i];
// create bend, if any
if(Math.abs(mouseX-sx)<=10){
endx = sx+(mouseX-sx)*1.1;
this.lastEndX[i]=endx;
};
// draw this curve
ctx.moveTo(sx,sy);
ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,endx,endy);
}
// stroke
ctx.stroke();
// temp outline
ctx.lineWidth=1;
ctx.beginPath();
ctx.rect(this.x,this.y,this.width,this.height);
ctx.stroke();
}
//
return Hair;
})();
var direction=1;
var fps = 3;
function animate() {
setTimeout(function() {
// change hair length
var hairLength=hair.grow(direction);
if(hairLength<1 || hairLength>10){ direction=(-direction); }
// draw
hair.draw();
// request next frame
requestAnimationFrame(animate);
}, 1000 / fps);
}
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
$("#movelog").html("Move: "+ mouseX + " / " + mouseY);
// Put your mousemove stuff here
if(mouseX>=hair.x && mouseX<=hair.right && mouseY>=hair.y && mouseY<=hair.bottom){
hair.draw(mouseX);
}
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#grow").click(function(e){ animate(); });
// create a new patch of hair
var hair=new Hair(25,50,150,50,8);
hair.draw(225);
}); // end $(function(){});
</script>
</head>
<body>
<p id="movelog">Move</p>
<canvas id="canvas" width=300 height=200></canvas><br>
<button id="grow">Grow Hair</button>
</body>
</html>
[ Added: explanation of “Class” and instantiating an object from a class ]
var Hair=(function(){ …; return Hair; })() creates a Hair “class”.
var hair = new Hair(…) creates an actual, usable Hair “object”.
Think of the Hair-class as a template (or blueprint or cookie-cutter). All the code inside Hair-class only defines the properties and methods of the class. You don’t actually call any of the code in the Hair class. You just use it as a template to create actual hair objects.
You can use the Hair class to create as many actual hair objects as you need—it’s reusable. The act of creating a hair object is known as “instantiating the Hair-class”.
BTW, javascript does not actually have classes, so this is just a pseudo-class. But that’s a whole other explanation!
You ask: What’s the use of direction=0.25?
The direction variable is used to incrementally increase the height of hair when hair is “growing” during animation. The .25 tells the control/end points of the hair to go up by .25 pixels per animation frame.
You ask: What’s the significance of callback function and that settimeout?
setTimeout is wrapping requestAnimationFrame so that the animation occurs at a fixed frame-per-second.
RequestAnimationFrame (RAF) does a great job of optimizing performance, but you can’t control the frames-per-second with RAF alone. If you wrap RAF inside setTimeout, you can control the frames-per-second. For example, setTimeout(anyFunction,1000/fps) will trigger anyFunction about 3 times a second when fps==3. See this nice article on RAF+setTimeout: http://creativejs.com/resources/requestanimationframe/
As you discovered, RAF without setTimeout will still work, but RAF will try to grow your hair as quickly as possible, rather than with a fps interval.
I've got a problem filling 25 canvas elements automatically in a for loop. They are numbered like so: can01 to can25.
I've tried all I knew to draw different images on the canvas and I have spent a lot of time in searching a few articles which are about this problem but I haven't found any.
This is my working code to fill all canvas elements with the same image:
var imageGrass = new Image();
imageGrass.src = 'recources/imagesBG/grass.jpg';
imageGrass.onload = function() {
for (var i = 1; i < 26; i++)
{
if( i < 10 )
{
var task = "can0" + i + "_ctx.drawImage(imageGrass, 0, 0);";
eval(task);
}
else
{
var task = "can" + i + "_ctx.drawImage(imageGrass, 0, 0);";
eval(task);
}
}
}
But I really don't know how to make the imageGrass.src dynamic. For example, the canvas element no. 5 (can05) in this case shall look like stone texture.
I´m really looking forward to read your ideas. I just don't get it.
Here’s how to impliment Dave’s good idea of using arrays to organize your canvases:
Create an array that will hold references to all your 25 canvases (do the same for 25 contexts)
var canvases=[];
var contexts=[];
Next, fill the array with all your canvases and contexts:
for(var i=0;i<25;i++){
var canvas=document.getElementById("can"+(i<10?"0":""));
var context=canvas.getContext("2d");
canvases[i]=canvas;
contexts[i]=context;
}
If you haven't seen it before: i<10?"0":"" is an inline if/else used here to add a leading zero to your lower-numbered canvases.
Then you can fetch your “can05” canvas like this:
var canvas=canvases[4];
Why 4 and not 5? Arrays are zero based, so canvases[0] holds can01. Therefore array element 4 contains your 5th canvas “can05”.
So you can fetch the drawing context for your “can05” like this:
var context=contexts[4];
As Dave says, “evals are evil” so here’s how to fetch the context for “can05” and draw the stone image on it.
var context=contexts[4];
context.drawImage(stoneImage,0,0);
This stone drawing can be shortened to:
contexts[4].drawImage(stoneImage,0,0);
You can even put this shortened code into a function for easy reuse and modification:
function reImage( canvasIndex, newImage ){
contexts[ canvasIndex ].drawImage( newImage,0,0 );
}
Then you can change the image on any of your canvases by calling the function:
reimage( 4,stoneImage );
That’s it!
The evil-evals have been vanquished (warning: never invite them to your computer again!)
Here is example code and a Fiddle: http://jsfiddle.net/m1erickson/ZuU2e/
This code creates 25 canvases dynamically rather than hard-coding 25 html canvas elements.
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:0px; margin:0px;border:0px; }
canvas{vertical-align: top; }
</style>
<script>
$(function(){
var canvases=[];
var contexts=[];
var grass=new Image();
grass.onload=function(){
// the grass is loaded
// now make 25 canvases and fill them with grass
// ALSO !!!
// keep track of them in an array
// so we can use them later!
make25CanvasesFilledWithGrass()
// just a test
// fill canvas#3 with gold
draw(3,"gold");
// fill canvas#14 with red
draw(14,"red");
}
//grass.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/grass.jpg";
//grass.src="grass.jpg";
function make25CanvasesFilledWithGrass(){
// get the div that we will fill with 25 canvases
var container=document.getElementById("canvasContainer");
for(var i=0;i<25;i++){
// create a new html canvas element
var canvas=document.createElement("canvas");
// assign the new canvas an id, width and height
canvas.id="can"+(i<10?"0":"")+i;
canvas.width=grass.width;
canvas.height=grass.height;
// get the context for this new canvas
var ctx=canvas.getContext("2d");
// draw the grass image in the new canvas
ctx.drawImage(grass,0,0);
// add this new canvas to the web page
container.appendChild(canvas);
// add this new canvas to the canvases array
canvases[i]=canvas;
// add the context for this new canvas to the contexts array
contexts[i]=ctx;
}
}
// test -- just fill the specified canvas with the specified color
function draw(canvasIndex,newColor){
var canvas=canvases[canvasIndex];
var ctx=contexts[canvasIndex];
ctx.beginPath();
ctx.fillStyle=newColor;
ctx.rect(0,0,canvas.width,canvas.height);
ctx.fill();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="canvasContainer"></div>
</body>
</html>
I'm experimenting with canvas and javascript and was wondering if there was a way to group objects together so they can be treated as a single object within one canvas. So, as a very simple example, say I have a filled circle surrounded by a larger unfilled circle. Now, assume I have 10 of these. I want to move each set separately so is there a way to group each set together as a single object? I don't want to have to make a call to move each object separately.
My example above is a little simple as I actually have 10 or 11 objects grouped together in each cluster. Moving each object in the cluster separately is a pain so I'd like to be able to be able to group them together and make one call to move them.
Any suggestions would be appreciated!
You can use canvas’ “translate” function to draw your circle-group without recalculating!
Location is made simpler for groups when you use a “tranlslate”.
A translate is just the “behind-the-scenes” math that drags your entire group to a new location on the canvas.
You don’t have to do separate calculations for each of your circles. Instead you just do a translate and then draw your circles as if they were in their starting positions.
This is how to impliment translate:
// do a translate to mouseX,mouseY
// all draws will be now be done RELATIVE to mouseX,mouseY
// so if we ctx.translate(100,100)
// then a ctx.rect(0,0,10,10) will actually be drawn at 100,100
ctx.translate(mouseX,mouseY);
// draw the ball group
// notice we didn't have to calculate ANY new positions!!
drawBallGroup();
// translate back after we're done
ctx.translate(-mouseX,-mouseY);
Here’s code and a Fiddle: http://jsfiddle.net/m1erickson/4rJgw/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
drawBallGroup();
function drawBallGroup(){
drawBall(8,10,5,"red");
drawBall(20,15,10,"green");
drawBall(25,25,8,"blue");
drawBall(5,22,10,"orange");
drawBall(18,30,10,"black");
}
function drawBall(x,y,radius,color){
ctx.beginPath();
ctx.fillStyle=color;
ctx.arc(x,y,radius, 0, 2 * Math.PI, false);
ctx.fill();
}
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// do a translate to mouseX,mouseY
// all draws will be now be done RELATIVE to mouseX,mouseY
// so if we ctx.translate(100,100)
// then ctx.rect(0,0,10,10) will actually be drawn at 100,100
ctx.translate(mouseX,mouseY);
// draw the ball group
// notice we didn't have to calculate ANY new positions!!
drawBallGroup();
// translate back after we're done
ctx.translate(-mouseX,-mouseY);
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
}); // end $(function(){});
</script>
</head>
<body>
<p>Click to move the ball group to a new location</p>
<p>WITHOUT recalculating each new ball position!</p><br/>
<canvas id="canvas" width=400 height=500></canvas>
</body>
</html>
As pointed out in the comments, there's nothing in the Canvas API which supports grouping of objects. However, there are various libraries for Canvas which you could look into which support this type of functionality. This stackoverflow question might be a good place to start investigating.
easel.js, for example, supports the nesting and grouping of objects. If you're familiar with the ActionScript displayList you'll find it really intuitive as the API is very similar.
Your example of grouping an outer and inner circle and manipulating it as a single object would look something like this with easel:
// Create a stage by getting a reference to the canvas
var stage = new createjs.Stage("canvas");
// Create a container for the circle.
var circle = new createjs.Container();
// Create the outer circle
var outerCircle = new createjs.Shape();
outerCircle.graphics.beginFill("red").drawCircle(0, 0, 40);
// Create the inner circle
var innerCircle = new createjs.Shape();
innerCircle.graphics.beginFill('green').drawCircle(0, 0, 30);
// Add inner and outer circles instance to circle container
circle.addChild(outerCircle);
circle.addChild(innerCircle);
// Now we can move the circle container and its children as a single object
circle.x = circle.y = 40;
// Add circle container instance to stage display list.
stage.addChild(circle);
// Update stage will render next frame
stage.update();
I did something similar recently and I solved this by drawing the group of objects to an off screen (in memory) canvas. Moving this canvas essentially moves the "group".
Depending on what your actual goal is, this may be inflexible - but it's another option to consider.
Well, you could put all your objects in one array and then make a loop, like this:
var objects = [
{x: 200, y: 130, radius: 36, color: "red"}
,
{x: 150, y: 80, radius: 31, color: "blue"}
,
{x: 20, y: 210, radius: 22, color: "green"}
];
for (var i = 0; i < objects.length; i++) {
context.beginPath();
context.arc(objects[i].x, objects[i].y, objects[i].radius, 0, Math.PI * 2, false);
context.fillStyle = objects[i].color;
context.fill();
context.closePath();
}
:D