65.9K
CodeProject 正在变化。 阅读更多。
Home

使用KineticJS选择多个对象

2013 年 11 月 21 日

MIT

3分钟阅读

viewsIcon

20944

downloadIcon

325

KineticJS 库使使用 HTML5 canvas 标签变得轻松而直接。此示例演示如何

Sample Image - maximum width is 600 pixels

引言

KineticJS 库使使用 HTML5 canvas 标签变得轻松而直接。此示例演示如何使用KineticJS库在屏幕上创建可以通过拖动鼠标创建选择框来选择的对象。

背景

如果您不熟悉 HTML5 canvas 标签,请访问HTML5CanvasTutorials.com。如果您不熟悉 KineticJS 库,请点击这里了解更多信息以及它们如何使 Web 浏览器的图形体验焕然一新。此示例针对 KineticJS 库的 4.7.4 版本,可在这里找到。本文是使用 KineticJS 库选择和操作 HTML5 canvas 对象的两篇文章系列中的第一篇。

Using the Code

在此示例中,我在 HTML5 canvas 上创建了三个可以选中和对齐的方块。与往常一样,KineticJS 项目始于创建一个div容器

<div id="container" style="position:absolute;left:0;top:0"></div>

接下来,创建一个舞台和一个图层——这同样是 KineticJS 的标准做法

var stage = new Kinetic.Stage({
	container: 'container',
	width: 300,
	height: 500
});
var layer = new Kinetic.Layer();

要使此方法起作用的第一个技巧是添加一个透明的背景矩形。这将允许我们捕获背景上发生的点击,同时仍然允许在单击时像往常一样拖动和放下我们的对象。

var rectBackground = new Kinetic.Rect({
	x: 0,
	y: 0,
	height: stage.attrs.height,
	width: stage.attrs.width,
	fill: 'transparent',
	draggable: false,
	name: 'rectBackground'
});
layer.add(rectBackground);

现在我们将方块添加到画布上

DrawBlocks();
function DrawBlocks()
{
	var x, y, height;
	x = 90;
	y = 10;
	size = 40;
	CreateBlock(x, y, size, size, "green");

	x = 150;
	y = 80;
	CreateBlock(x, y, size + 20, size + 60, "red");

	x = 110;
	y = 170;
	CreateBlock(x, y, size, size, "blue");
	layer.draw();
}

function CreateBlock(x, y, height, width, color)
{
	var grpBlk = new Kinetic.Group({
		x: x,
		y: y,
		height: height,
		width: width,
		name: color,
		draggable: true
	});

	var blk = new Kinetic.Rect({
		x: x,
		y: y,
		height: height,
		width: width,
		fill: color,
		name: color + ' block'
	});
	grpBlk.add(blk);
	blk.setAbsolutePosition(x, y);
	grpBlk.setAbsolutePosition(x, y);
	layer.add(grpBlk);
	return grpBlk;
}

此示例允许您通过在画布区域上拖动鼠标来创建选择框。为此,我们需要一些变量

var arSelected = new Array();
var bDragging = false;
var bHaveSelBox = false;
var rectSel = null;
var initX = 0;
var initY = 0;
  • arSelected将保存用户已选择的方块的名称。
  • bDragging在我们计算鼠标仍在移动时的框的大小时防止重入代码。bHaveSelBox防止我们多次绘制框
  • rectSel是一个全局变量,用于保存选择框,这样我们就不必一直查找它(性能更快)
  • initXinitY包含按下鼠标时的初始位置。这使我们能够计算拖动时选择矩形的大小。

现在我们需要引入一些事件处理程序。我们需要捕获的第一件事是我们透明背景上的点击,以便知道开始绘制选择框。

rectBackground.on("mousedown", function (evt)
{
	bDragging = true;
});

接下来,我们需要在拖动鼠标时重绘选择框。

stage.getContent().addEventListener('mousemove', function (e)
{
	if (bDragging)
	{
		SetSelRectPosition(e);
	}
});

var bInHere = false;  //prevents re-entrance in event driven code
function SetSelRectPosition(e)
{
	if (bDragging && !bInHere)
	{
		bInHere = true;
		var canvas = layer.getCanvas();

		var mousepos = stage.getPointerPosition();
		var x = mousepos.x;
		var y = mousepos.y;

		if (!bHaveSelBox)
		{
			initX = x;
			initY = y;
			
			//create the selection rectangle
			rectSel = new Kinetic.Rect({
				x: initX,
				y: initY,
				height: 1,
				width: 1,
				fill: 'transparent',
				stroke: 'black',
				strokeWidth: 1
			});
			layer.add(rectSel);
			layer.draw();
			bHaveSelBox = true;
		}
		else
		{
			var height = 0;
			var width = 0;
			var newX = 0;
			var newY = 0;

			if (x > initX)
				newX = initX;
			else
				newX = x;

			if (y > initY)
				newY = initY;
			else
				newY = y;

			height = Math.abs(Math.abs(y) - Math.abs(initY));
			width = Math.abs(Math.abs(x) - Math.abs(initX));

			rectSel.setHeight(height);
			rectSel.setWidth(width);
			rectSel.setX(newX);
			rectSel.setY(newY);
			layer.draw();
		}
	}
	bInHere = false;
}

当用户松开鼠标时,我们需要确定选择了哪些项目并将它们的名称放入arSelected数组中,以便以后可以访问它们。我们还需要突出显示我们的方块,以便我们可以直观地看到哪些项目被选中。

stage.getContent().addEventListener('mouseup', function (e)
{
	if (bDragging)
	{
		bDragging = false;

		GetOverlapped();

		if (rectSel != null)
			rectSel.remove();

		rectSel = null;
		bHaveSelBox = false;
		layer.draw();
	}
});

function GetOverlapped()
{
	//bail if there is no rectangle
	if (rectSel == null)
		return;

	var iHeight = 0;
	var iWidth = -1000;

	arSelected.length = 0;

	initX = 10;
	initY = 10;

	var arGroups = layer.getChildren();

	for (i = 0; i < arGroups.length; i++)
	{
		var grp = arGroups[i];

		if (grp.attrs.name != rectSel.attrs.name && 
		grp.attrs.name != rectBackground.attrs.name && grp.attrs.name != 'btn' &&
				grp.attrs.name != 'highlightBlock')
		{
			var pos = rectSel.getAbsolutePosition();

			//get the extents of the selection box
			var selRecXStart = parseInt(pos.x);
			var selRecXEnd = parseInt(pos.x) + parseInt(rectSel.attrs.width);
			var selRecYStart = parseInt(pos.y);
			var selRecYEnd = parseInt(pos.y) + parseInt(rectSel.attrs.height);

			//get the extents of the group to compare to
			var grpXStart = parseInt(grp.attrs.x);
			var grpXEnd = parseInt(grp.attrs.x) + parseInt(grp.attrs.width);
			var grpYStart = parseInt(grp.attrs.y);
			var grpYEnd = parseInt(grp.attrs.y) + parseInt(grp.attrs.height);

			//Are we inside the selction area?
			if ((selRecXStart <= grpXStart && selRecXEnd >= grpXEnd) && 
			(selRecYStart <= grpYStart && selRecYEnd >= grpYEnd))
			{
				if (arSelected.indexOf(grp.getName()) < 0)
				{
					arSelected.push(grp.getName());

					var tmpX = parseInt(grp.attrs.x);
					var tmpY = parseInt(grp.attrs.y);

					var rectHighlight = new Kinetic.Rect({
						x: tmpX,
						y: tmpY,
						height: grp.attrs.height,
						width: grp.attrs.width,
						fill: 'transparent',
						name: 'highlightBlock',
						stroke: '#41d6f3',
						strokeWidth: 3
					});

					layer.add(rectHighlight);
				}
			}
		}
	}
}

最后,用户会期望在选择背景或单个项目时,项目将取消选中。为了实现这一点,我们添加以下内容

stage.getContent().addEventListener('mousedown', function (e)
{   
    if(arSelected.length > 0)
    {
        var name = "";

        if (e.shape != undefined)
            name = e.shape.attrs.name;

        if(e.targetNode != undefined)
            name = e.targetNode.attrs.name;
        
		//we don't want to unselect if we are pushing the button
        if (name != 'btn')
        {
            RemoveHighlights();
        }
    }

});

function RemoveHighlights()
{
	var arHighlights = layer.get('.highlightBlock');
	while (arHighlights.length > 0)
	{
		arHighlights[0].remove();
		arHighlights = layer.get('.highlightBlock');
	}
	arSelected.length = 0;
}

此时,您应该能够运行它,并且应该能够选择项目。

如果您想获得所选对象的列表,请将此按钮添加到屏幕上

x = 85;
y = 250;
var grpGetSelectedButton = CreateButton(x, y, "Get Selected");
grpGetSelectedButton.on("click", function (evt) { ShowSelected(); });

function CreateButton(x, y, text)
{
	var grpButton = new Kinetic.Group({
		x: x,
		y: y,
		height: 30,
		width: 135,
		name: 'btn',
		draggable: true
	});

	var blkButton = new Kinetic.Rect({
		x: x,
		y: y,
		height: 30,
		width: 135,
		fill: 'Violet',
		name: 'btn'
	});

	var txtButton = new Kinetic.Text({
		x: x + 2,
		y: y + 2,
		fontFamily: 'Calibri',
		fontSize: 22,
		text: text,
		fill: 'black',
		name: 'btn'
	});

	grpButton.add(blkButton);
	grpButton.add(txtButton);
	grpButton.setAbsolutePosition(x, y);
	blkButton.setAbsolutePosition(x, y);
	txtButton.setAbsolutePosition(x + 2, y + 2);

	layer.add(grpButton);

	return grpButton;
}

function ShowSelected()
{
	var str = "";
	for (var i = 0; i < arSelected.length; i++)
	{
		str += arSelected[i] + ", ";
	}
	if (str != "")
		str = str.substring(0, str.length - 2);

	alert(str);
}

关注点

需要注意的是,调用setAbsolutePosition的行是绝对必要的。如果您只设置xy,则该位置相对于舞台的鼠标点击位置不会相同,这会在您计算哪些项目落在选择框内时给您带来很大的痛苦。

本系列的第二篇文章可在这里找到。

您可以在这里看到此项目的运行演示。

历史

  • 2013 年 11 月 21 日:初始版本
© . All rights reserved.