1

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!

HamZa
  • 14,671
  • 11
  • 54
  • 75
Wicked Jester
  • 53
  • 2
  • 4
  • 1
    No, the canvas API does not provide anything so high-level. – Matt Ball Apr 01 '13 at 16:54
  • 2
    The canvas itself does not really work with objects, you can only paint on it. Could you show us how those objects are grouped/clustered in your current code? – Bergi Apr 01 '13 at 16:56

4 Answers4

0

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();
Community
  • 1
  • 1
net.uk.sweet
  • 12,444
  • 2
  • 24
  • 42
0

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>
markE
  • 102,905
  • 11
  • 164
  • 176
0

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.

Jarrod
  • 9,349
  • 5
  • 58
  • 73
0

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