Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Aligning Multiple Objects with KineticJS

21 Nov 2013MIT2 min read 13.2K   107  
This article builds on a previous one where we created a selection box by dragging the mouse using the KineticJS library. In this article, we will add functions to allow these objects to be aligned along their left or right edges, tops or bottoms, and middles or centers.
Sample Image - maximum width is 600 pixels

Introduction

This article builds on a previous one where we created a selection box by dragging the mouse using the KineticJS library. In this article, we will add functions to allow these objects to be aligned along their left or right edges, tops or bottoms, and middles or centers.

Background

If you have not read the previous article, please do so. This article assumes that you have all of the pieces from the previous article in place. If you are new to the HTML5 canvas tag, please visit HTML5CanvasTutorials.com. If you are new to the KineticJS libraries, click here to learn more about them and how they can make graphics for web browsers a whole new experience. This sample works against version 4.7.4 of the KineticJS library, which can be found here. This article is the second in a series of two that use the KineticJS library to select and manipulate HTML5 canvas objects.

Using the Code

Aligning the tops, bottoms, lefts, rights, middles and centers is a set of functions that is expected by users in many graphical editing tools. The first thing we will do in this sample is to add the functions that know how to manipulate the objects. Time to get a little bit of math on.

The first thing we need to know for our alignment functions is which object is the topmost or leftmost. Depending on whether we are aligning on a horizontal or vertical axis, the topmost or leftmost will serve as the object that we will line all of the other ones up to. These two functions find these items for us:

JavaScript
function FindLeftmost()
{
	var lowestX = 100000;
	var grpLeft = null;

	for (var i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		if (parseInt(grp.attrs.x) < lowestX)
		{
			grpLeft = grp;
			lowestX = parseInt(grp.attrs.x);
		}
	}
	return grpLeft;
}

function FindTopmost()
{
	var lowestY = 10000;
	var grpTop = null;

	for (var i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		if (parseInt(grp.attrs.y) < lowestY)
		{
			grpTop = grp;
			lowestY = parseInt(grp.attrs.y);
		}
	}
	return grpTop;
}

Next, we have the actual functions that do the alignment. They all work in the same general way: get the absolute position of our reference object (the topmost or leftmost) and then figure out the right math to align the rest of the selected objects with it, in the manner that the user has requested.

JavaScript
///////////////////////////////////////////////
//// Alignment functions 
///////////////////////////////////////////////
function AlignLefts()
{
	var grpTop = FindTopmost();

	if (grpTop == null)
	{
		return;
	}
	var topPos = grpTop.getAbsolutePosition();
	for (i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		var newX = topPos.x;
		var newY = parseInt(grp.attrs.y);
		grp.setAbsolutePosition(newX, newY);
	}
	RemoveHighlights();
	layer.draw();
}

function AlignRights()
{
	var grpTop = FindTopmost();

	if (grpTop == null)
	{
		return;
	}
	var topPos = grpTop.getAbsolutePosition();
	var end = topPos.x + parseInt(grpTop.getWidth());

	for (i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		var newX = end - grp.getWidth();
		var newY = grp.getY();
		grp.setAbsolutePosition(newX, newY);
	}
	RemoveHighlights();
	layer.draw();
}

function AlignTops()
{
	var grpLeft = FindLeftmost();

	if (grpLeft == null)
	{
		return;
	}
	var leftPos = grpLeft.getAbsolutePosition();

	for (i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		var newX = grp.getX();
		var newY = leftPos.y;
		grp.setAbsolutePosition(newX, newY);
	}
	RemoveHighlights();
	layer.draw();
}

function AlignBottoms()
{
	var grpLeft = FindLeftmost();

	if (grpLeft == null)
	{
		return;
	}
	var leftPos = grpLeft.getAbsolutePosition();
	var bottom = leftPos.y + grpLeft.getHeight();

	for (i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		var newX = grp.getX();
		var newY = bottom - grp.getHeight();
		grp.setAbsolutePosition(newX, newY);
	}
	RemoveHighlights();
	layer.draw();
}

function AlignCenters()
{
	var grpTop = FindTopmost();

	if (grpTop == null)
	{
		return;
	}
	var topPos = grpTop.getAbsolutePosition();
	var middle = topPos.x + (grpTop.getWidth() / 2);

	for (i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		var newX = middle - (grp.getWidth() / 2);
		var newY = grp.getY();
		grp.setAbsolutePosition(newX, newY);
	}
	RemoveHighlights();
	layer.draw();
}

function AlignMiddles()
{
	var grpLeft = FindLeftmost();

	if (grpLeft == null)
	{
		return;
	}
	var leftPos = grpLeft.getAbsolutePosition();
	var middle = leftPos.x + (grpLeft.getHeight() / 2);

	for (i = 0; i < arSelected.length; i++)
	{
		var grp = layer.get("." + arSelected[i])[0];
		var newX = grp.getX();
		var newY = middle - (grp.getHeight() / 2);
		grp.setAbsolutePosition(newX, newY);
	}
	RemoveHighlights();
	layer.draw();
}

Finally, we just need a way to call those functions. For this, we will use the CreateButton function from the previous article.

JavaScript
x = 10;
y = 290;
var grpAlignLeftButton = CreateButton(x, y, "Align Lefts");
grpAlignLeftButton.on("click", function (evt) { AlignLefts(); });

y = 330;
var grpAlignRightButton = CreateButton(x, y, "Align Rights");
grpAlignRightButton.on("click", function (evt) { AlignRights(); });

y = 370;
var grpAlignTopsButton = CreateButton(x, y, "Align Tops");
grpAlignTopsButton.on("click", function (evt) { AlignTops(); });

x = 160
y = 290;
var grpAlignBottomsButton = CreateButton(x, y, "Align Bottoms");
grpAlignBottomsButton.on("click", function (evt) { AlignBottoms(); });

y = 330;
var grpAlignMiddlesButton = CreateButton(x, y, "Align Middles");
grpAlignMiddlesButton.on("click", function (evt) { AlignMiddles(); });

y = 370;
var grpAlignCentersButton = CreateButton(x, y, "Align Centers");
grpAlignCentersButton.on("click", function (evt) { AlignCenters(); });

x = 85;
y = 410;
var grpResetButton = CreateButton(x, y, "Reset Blocks");
grpResetButton.on("click", function (evt) { DrawBlocks(); });

stage.add(layer);
layer.draw();

Points of Interest

It should be noted that the lines where setAbsolutePosition are being called are absolutely necessary. If you just set the x and y, the position will not be the same relative to the stage as the mouse clicks, which will cause you great pain when you are calculating which items fall within the selection box.

You can see a working demo of this project here.

History

  • 21st November, 2013: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License