Destroying multiple shapes on end of KonvaJS tween - javascript

So I have a for loop generating, placing and tweening 20 rectangles. However the code only destroys the last generated rectangle. Is there an (ideally simple) way to ensure that the .destroy() applies to every rectangle instead of the last one?
$("#combat").click(function() {
for (var i = 0; i < 20; i++){
var rect = new Konva.Rect({
x: -500,
y: stage.height()*Math.random(),
width: 480,
height: 20,
fill: 'green',
stroke: 'black',
strokeWidth: 3
});
layer.add(rect);
tween = new Konva.Tween({
node: rect,
duration: 1,
x: 500,
onFinish: function() {
rect.destroy()
}
}).play();
}
});

var tween = new Konva.Tween({
node: pentagon,
duration: 1,
x: 500,
onFinish: function() {
// this will be tween instance
// this.node will be rectangle
console.log(this.node);
}
}).play();

You can use Group object for this:
EDIT:
$("#combat").click(function() {
var rectGroup = new Konva.Group();
for (var i = 0; i < 20; i++){
var rect = new Konva.Rect({
x: -500,
y: stage.height()*Math.random(),
width: 480,
height: 20,
fill: 'green',
stroke: 'black',
strokeWidth: 3
});
rectGroup.add(rect);
layer.add(rectGroup);
tween = new Konva.Tween({
node: rect,
duration: 1,
x: 500,
onFinish: function() {
rectGroup.destroy()
}
}).play();
}
});

Related

Resize Konva Label on Zoom

I have a simple setup where use clicks location and creates a label with text.
What i would like to be able to do is resize on zoom, so that label is relative size always. Currently label is huge when i zoom in covering key areas.
code:
createTag() {
return new Konva.Tag({
// omitted for brevity
})
}
createLabel(x: number, y: number) {
return new Konva.Label({
x: x,
y: y,
opacity: 0.75
});
}
createText() {
return new Konva.Text({
// omitted for brevity
});
}
createMarker(point: any) {
var label = createLabel(point.x, point.y);
var tag = createTag();
var text = createText();
label.add(tag);
label.add(text);
this.layer.add(label);
this.stage.add(this.layer);
}
Mouse click event listener
this.stage.on('click', function (e) {
// omitted for brevity
var pointer = this.getRelativePointerPosition();
createMarker(pointer);
// omitted for brevity
}
this.stage.on('wheel', function (e) {
// zoom code
});
How can i resize the labels to be relative size of the canvas?
There are many possible solutions. If you are changing scale of the whole stage, then you can just apply reversed scale to the label, so its absolute scale will be always 1.
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
var layer = new Konva.Layer();
stage.add(layer);
var circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 50,
fill: 'green',
});
layer.add(circle);
// tooltip
var tooltip = new Konva.Label({
x: circle.x(),
y: circle.y(),
opacity: 0.75,
});
layer.add(tooltip);
tooltip.add(
new Konva.Tag({
fill: 'black',
pointerDirection: 'down',
pointerWidth: 10,
pointerHeight: 10,
lineJoin: 'round',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.5,
})
);
tooltip.add(
new Konva.Text({
text: 'Try to zoom with wheel',
fontFamily: 'Calibri',
fontSize: 18,
padding: 5,
fill: 'white',
})
);
var scaleBy = 1.01;
stage.on('wheel', (e) => {
e.evt.preventDefault();
var oldScale = stage.scaleX();
var pointer = stage.getPointerPosition();
var mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
var newScale =
e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy;
stage.scale({ x: newScale, y: newScale });
// apply reversed scale to label
// so its absolte scale is still 1
tooltip.scale({ x: 1 / newScale, y: 1 / newScale })
var newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
stage.position(newPos);
});
<script src="https://unpkg.com/konva#8.3.0/konva.min.js"></script>
<div id="container"></div>

KonvaJS - Rotate rectangle around cursor without using offset

I am using KonvaJS to drag and drop rectangles into predefined slots. Some of the slots need to be rotated 90 degrees. I have a hit box around the slots that are rotated vertically, so when the user drags the rectangle into the area it will rotate 90 degrees automatically (to match the orientation). When it rotates, it moves out from under the mouse. This can be solved with offset, but then the rectangle doesn't visually line up with the boxes after snapping. This can (probably) be solved with additional code.
I have tried to rotate the rectangle, and then move it under the mouse. Since the user is still dragging it, this doesn't seem to work as I planned.
Is it possible to force the rectangle to rotate under the mouse without using offset?
Here is a fiddle showing the issue - The offset problems can be demonstrated by setting the first variable to true.
https://jsfiddle.net/ChaseRains/1k0aqs2j/78/
var width = window.innerWidth;
var height = window.innerHeight;
var rectangleLayer = new Konva.Layer();
var holdingSlotsLayer = new Konva.Layer();
var controlLayer = new Konva.Layer();
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
draggable: true
});
//vertical holding spot
holdingSlotsLayer.add(new Konva.Rect({
x: 300,
y: 25,
width: 130,
height: 25,
fill: '#fff',
draggable: false,
rotation: 90,
stroke: '#000'
}));
//horizontal holding spot
holdingSlotsLayer.add(new Konva.Rect({
x: 25,
y: 75,
width: 130,
height: 25,
fill: '#fff',
draggable: false,
rotation: 0,
stroke: '#000'
}));
//mask to set boundaries around where we wannt to flip the rectangle
controlLayer.add(new Konva.Rect({
x: 215,
y: 15,
width: 150,
height: 150,
fill: '#fff',
draggable: false,
name: 'A',
opacity: 0.5
}));
stage.add(holdingSlotsLayer, controlLayer);
//function for finding intersections
function haveIntersection(placeHolder, rectangle, zone) {
if (rectangle.rotation == 0 || zone == true) {
return !(
rectangle.x > placeHolder.x + placeHolder.width ||
rectangle.x + rectangle.width < placeHolder.x ||
rectangle.y > placeHolder.y + placeHolder.height ||
rectangle.y + rectangle.height < placeHolder.y
);
} else {
return !(
rectangle.x > placeHolder.x + 25 ||
rectangle.x + rectangle.width < placeHolder.x ||
rectangle.y > placeHolder.y + placeHolder.height + 90 ||
rectangle.y + rectangle.height < placeHolder.y
);
}
}
//function to create rectangle group (so we can place text on the rectangle)
function spawnRectangle(angle) {
var rectangleGroup = new Konva.Group({
x: 95,
y: 95,
width: 130,
height: 25,
rotation: angle,
draggable: true,
});
rectangleGroup.add(new Konva.Rect({
width: 130,
height: 25,
fill: 'lightblue'
}));
rectangleGroup.add(new Konva.Text({
text: '123',
fontSize: 18,
fontFamily: 'Calibri',
fill: '#000',
width: 130,
padding: 5,
align: 'center'
}));
//function tied to an on drag move event
rectangleGroup.on('dragmove', (e) => {
//shrink rectangle hitbox for use in placeholder intersection
var dimensions = {
"height": 3,
"width": 5,
"x": e.target.attrs.x,
"y": e.target.attrs.y,
'rotation': e.target.attrs.rotation
};
//loop over holding slots to see if there is an intersection.
for (var i = 0; holdingSlotsLayer.children.length > i; i++) {
//if true, change the look of the slot we are hovering
if (haveIntersection(holdingSlotsLayer.children[i].attrs, dimensions, false)) {
holdingSlotsLayer.children[i].attrs.fill = '#C41230';
holdingSlotsLayer.children[i].attrs.dash = [10, 3];
holdingSlotsLayer.children[i].attrs.stroke = '#000';
//set attributes back to normal otherwise
} else {
holdingSlotsLayer.children[i].attrs.fill = '#fff';
holdingSlotsLayer.children[i].attrs.dash = null;
holdingSlotsLayer.children[i].attrs.stroke = null;
}
}
//check to see if we are in a zone that requires the rectangle to be flipped 90 degrees
if (haveIntersection(controlLayer.children[0].attrs, dimensions, true)) {
if (rectangleGroup.attrs.rotation != 90) {
rectangleGroup.attrs.rotation = 90;
}
} else {
rectangleGroup.attrs.rotation = 0;
}
stage.batchDraw();
});
rectangleGroup.on('dragend', (e) => {
for (var i = 0; holdingSlotsLayer.children.length > i; i++) {
//If the parking layer has an element that is lit up, then snap to position..
if (holdingSlotsLayer.children[i].attrs.fill == '#C41230') {
rectangleGroup.position({
x: holdingSlotsLayer.children[i].attrs.x,
y: holdingSlotsLayer.children[i].attrs.y
});
holdingSlotsLayer.children[i].attrs.fill = '#fff';
holdingSlotsLayer.children[i].attrs.dash = null;
holdingSlotsLayer.children[i].attrs.stroke = null;
}
}
stage.batchDraw();
});
rectangleLayer.add(rectangleGroup);
stage.add(rectangleLayer);
}
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #D3D3D3;
background-size: cover;
}
#desc {
position: absolute;
top: 5px;
left: 5px;
}
<script src="https://unpkg.com/konva#4.0.18/konva.min.js"></script>
<body>
<div id="container"></div>
<div id="desc">
<button onclick="spawnRectangle(0)">spawn rectangle</button>
</div>
</body>
Here is a simple function to rotate the rectangle under the mouse, without using konva offset(). I used a tween to apply the movement but if you prefer to use it without the tween just apply the rect.rotate() then apply the newPos x & y as the position.
EDIT: The OP pointed out that if you clicked, held the mouse down whilst the rectangle completed its animation, then dragged, then the rectangle would jump away. What gives ? Well, when the mousedown event runs Konva takes note of the shape's initial position in its internal drag function. Then when we start to actually drag the mouse, Konva dutifully redraws the shape in the position it calculates. Now, 'we' know that we moved the shape in out code, but we didn't let Konva in on our trick.
The fix is to call
rect.stopDrag();
rect.startDrag();
immediately after the new position has been set. Because I am using a tween I do this in the onFinish() callback function of one of the tweens - you would want to ensure its the final tween if you apply more than one. I got away with it because my tweens run over the same period. If you aren't using tweens, just call the above immediately you apply your last rotate() or position() call on the shape.
function rotateUnderMouse(){
// Get the stage position of the mouse
var mousePos = stage.getPointerPosition();
// get the stage position of the mouse
var shapePos = rect.position();
// compute the vector for the difference
var rel = {x: mousePos.x - shapePos.x, y: mousePos.y - shapePos.y}
// Now apply the rotation
angle = angle + 90;
// and reposition the shape to keep the same point in the shape under the mouse
var newPos = ({x: mousePos.x + rel.y , y: mousePos.y - rel.x})
// Just for fun, a tween to apply the move: See https://konvajs.org/docs/tweens/Linear_Easing.html
var tween1 = new Konva.Tween({
node: rect,
duration: 0.25,
x: newPos.x,
y: newPos.y,
easing: Konva.Easings.Linear,
onFinish: function() { rect.stopDrag(); rect.startDrag();}
});
// and a tween to apply the rotation
tween2 = new Konva.Tween({
node: rect,
duration: 0.25,
rotation: angle,
easing: Konva.Easings.Linear
});
tween2.play();
tween1.play();
}
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'canvas-container',
width: 650,
height: 300
});
layer = new Konva.Layer();
stage.add(layer);
newPos = {x: 80, y: 40};
rect = new Konva.Rect({
width: 140, height: 50, x: newPos.x, y: newPos.y, draggable: true, stroke: 'cyan', fill: 'cyan'
})
layer.add(rect);
stage.draw()
rect.on('mousedown', function(){
rotateUnderMouse()
})
}
var stage, layer, rect, angle = 0;
setup()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Click the rectangle - it will rotate under the mouse.</p>
<div id="canvas-container"></div>

get the properties of a shape in a different layer Konva JS

I have the following code where a shape is dynamically created. On clicking the shape arrow is generated which can be resized using a controller on it's arrow head.
Now when I drag the shape i need to move the starting point of the arrow along with it. But since the arrow is in a different layer i'm unable to reference it. How can this be done?
Sample code:
var stage = new Konva.Stage({
container: 'canvas', // id of container <div>
width: 500,
height:300
});
var layer = new Konva.Layer();
jQuery("#add-shape").click(function(){
addShape();
});
var addShape = function(){
var parentContainer = new Konva.Rect({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
width: 200,
height: 60,
cornerRadius: 10,
fill: '#ccc'
});
var smallCircle = new Konva.Circle({
x: stage.getWidth() / 2 + 200,
y: stage.getHeight() / 2 + 30,
radius: 15,
fill: "#F2784B",
stroke: "white",
strokeWidth: 2
});
smallCircle.on('click', function() {
console.log(this.getX(),this.getY());
var mousePos = stage.getPointerPosition();
addArrow(mousePos.x,mousePos.y,layer);
});
layer.on('dragstart dragmove', function() {
//change the starting point of arrow here
});
layer.add(parentContainer);
layer.add(smallCircle);
layer.draggable('true');
stage.add(layer);
}
var addArrow = function(arrowX,arrowY){
var connectorLayer = new Konva.Layer();
var connector = new Konva.Arrow({
points: [arrowX,arrowY,arrowX+20,arrowY],
pointerLength: 4,
pointerWidth: 4,
fill: 'black',
stroke: 'black',
strokeWidth: 2
});
var arrowHead = new Konva.Circle({
x: arrowX+25,
y: arrowY,
radius: 5,
fill: "#F2784B",
stroke: "white",
strokeWidth: 1
});
arrowHead.on('dragstart dragmove', function() {
connector.setPoints([
arrowX,
arrowY,
arrowHead.getX(),
arrowHead.getY()
]);
});
arrowHead.draggable('true');
connectorLayer.add(arrowHead);
connectorLayer.add(connector);
stage.add(connectorLayer);
}
<script src="https://cdn.rawgit.com/konvajs/konva/1.0.2/konva.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="add-shape">
Add shape
</button>
<div id="canvas">
</div>
When you need to get a shape from another layer, you only need to call layer.getIntersection(mousePositionObject) like the following:
var stage = new Konva.Stage({
container: 'canvas', // id of container <div>
width: 500,
height:300
});
var layer = new Konva.Layer();
jQuery("#add-shape").click(function(){
addShape();
});
var connectorLayer = new Konva.Layer();
var addShape = function(){
var parentContainer = new Konva.Rect({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
width: 200,
height: 60,
cornerRadius: 10,
fill: '#ccc'
});
var smallCircle = new Konva.Circle({
x: stage.getWidth() / 2 + 200,
y: stage.getHeight() / 2 + 30,
radius: 15,
fill: "#F2784B",
stroke: "white",
strokeWidth: 2
});
smallCircle.on('click', function() {
console.log(this.getX(),this.getY());
var mousePos = stage.getPointerPosition();
addArrow(mousePos.x,mousePos.y);
});
layer.on('dragstart dragmove', function() {
//change the starting point of arrow here
var mousePosition = stage.getPointerPosition();
var arrow = connectorLayer.getIntersection();
arrow.x(mousePosition.x);
arrow.y(mousePosition.y);
});
layer.add(parentContainer);
layer.add(smallCircle);
layer.draggable('true');
stage.add(layer);
};
var addArrow = function(arrowX,arrowY){
var connector = new Konva.Arrow({
points: [arrowX,arrowY,arrowX+20,arrowY],
pointerLength: 4,
pointerWidth: 4,
fill: 'black',
stroke: 'black',
strokeWidth: 2
});
var arrowHead = new Konva.Circle({
x: arrowX+25,
y: arrowY,
radius: 5,
fill: "#F2784B",
stroke: "white",
strokeWidth: 1
});
arrowHead.on('dragstart dragmove', function() {
connector.setPoints([
arrowX,
arrowY,
arrowHead.getX(),
arrowHead.getY()
]);
});
arrowHead.draggable('true');
connectorLayer.add(arrowHead);
connectorLayer.add(connector);
stage.add(connectorLayer);
};
<script src="https://cdn.rawgit.com/konvajs/konva/1.0.2/konva.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="add-shape">
Add shape
</button>
<div id="canvas">
</div>

KineticJS - How to transition 2 shapes at once?

Here is the code: http://jsfiddle.net/MTDpC/
function drawArrow(firstShape, lastShape){
var group = new Kinetic.Group();
group.previousShape = firstShape;
group.nextShape = lastShape;
var beginX = group.previousShape.getX() + group.previousShape.getChildren()[0].getWidth() / 2;
var beginY = group.previousShape.getY() + group.previousShape.getChildren()[0].getHeight();
var endX = group.nextShape.getX() + group.nextShape.getChildren()[0].getWidth() / 2;
var endY = group.nextShape.getY() - 8;
var linha = new Kinetic.Line({
points: [ beginX, beginY, endX, endY],
name: 'linha',
stroke: '#555',
strokeWidth: 4,
lineCap: 'butt'
});
var seta = new Kinetic.RegularPolygon({
x: endX,
y: endY,
sides: 3,
radius: 4,
fill: '#555',
stroke: '#555',
strokeWidth: 4,
name: 'seta'
});
seta.rotateDeg(180);
group.add(seta);
group.add(linha);
firstShape.arrow = group;
lastShape.arrow = group;
group.reposition = function(){
var beginX = this.previousShape.getX() + this.previousShape.getChildren()[0].getWidth() / 2;
var beginY = this.previousShape.getY() + this.previousShape.getChildren()[0].getHeight();
var endX = this.nextShape.getX() + this.nextShape.getChildren()[0].getWidth() / 2;
var endY = this.nextShape.getY() - 8;
this.get('.linha')[0].transitionTo({
points:[ beginX, beginY, endX, endY],
duration: 0.0000001,
});
this.get('.seta')[0].transitionTo({
x: endX,
y: endY,
duration: 0.0000001,
});
};
return group;
}
function getProcess (processText, x, y, width) {
var group = new Kinetic.Group();
var complexText = new Kinetic.Text({
text: processText + '\nX: ' + x + '\nY: ' + y,
fontSize: 12,
fontFamily: 'Calibri',
fill: '#555',
padding: 20,
align: 'center',
name: 'Texto-Processo'
});
var rect = new Kinetic.Rect({
stroke: '#555',
strokeWidth: 5,
fill: '#ddd',
width: complexText.getWidth(),
height: complexText.getHeight(),
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: [10, 10],
shadowOpacity: 0.2,
cornerRadius: 10,
name: 'Quadro-Processo'
});
group.add(rect);
group.add(complexText);
group.setDraggable(true);
group.on('mouseover', function() {
document.body.style.cursor = 'pointer';
});
group.on('mouseout', function() {
document.body.style.cursor = 'default';
});
group.on('dragmove', function(){
group.getChildren()[1].setText(this.customText + "\nX: " + this.getX() + "\nY: " + this.getY());
group.arrow.reposition();
group.arrow.draw();
});
group.setX(x);
group.setY(y);
group.customText = processText;
return group;
}
var stage = new Kinetic.Stage({
container: 'container',
width: 800,
height: 600,
id: 'myCanvas'
});
var layer = new Kinetic.Layer();
var processo = getProcess('Processo de Teste de canvas 1', (stage.getWidth() / 2) - 100, 10 , 100);
processo.setId('canvas1');
layer.add(processo);
var processo2 = getProcess('Processo de Teste de canvas 2', (stage.getWidth() / 2) -100, processo.getY() + 300, processo.getX());
layer.add(processo2);
layer.add(drawArrow(processo, processo2));
// add the layer to the stage
stage.add(layer);
When one of the boxes are dragged, the arrow connecting them moves too.
As the arrow was made using 2 shapes, I want to know if is there some way to use .transtionTo at the same time, because you can see some delay between the line and the triangle that forms the arrrow when you move the bottom box.
The problem is that the transitionTo function does not support transitioning points. Also, each time you move the object, you are creating a new transition object... which is extremely slow. You do not need transitions for this, transitions are only useful for animating things automatically when the user is not meant to control them.
Try this instead:
this.get('.linha')[0].setPoints([beginX, beginY, endX, endY]);
this.get('.seta')[0].setPosition(endX,endY);
this.getLayer().draw();
/* this.get('.linha')[0].transitionTo({ //cant transition points
points: [beginX, beginY, endX, endY],
duration: 0.0000001,
});
this.get('.seta')[0].transitionTo({ // lags because everytime you move, a new transition is created, no this would be extremely slow
x: endX,
y: endY,
duration: 0.0000001,
});
*/
http://jsfiddle.net/MTDpC/1/
works fast eh?

KineticJS / Javascript dynamic creation and immediate dragging

What I want do is dynamically create a shape on mousedown, and then immediately have it (the shape) follow the mouse cursor until mouseup sets it in place.
Here is what I have so far and it's not working for me.
addNegativeButton.on('mousedown', function(){
var userPos = stage.getUserPosition();
shapesLayer.add(new Kinetic.Image({
image: imageObj,
x: userPos.x,
y: userPos.y,
height: 25,
width: 25,
rotation: 1,
draggable: true,
offset: 12
}));
var last = shapesLayer.getChildren()[shapesLayer.getChildren().length -1];
stage.on("mouseup", function(){
last.setAbsolutePosition(stage.getUserPosition());
stage.off("mouseup");
});
To reiterate:
What I have is an 'addNegativeButton' which when clicked creates a shape.
But I want it to follow the mouse cursor until the click is released.
http://jsfiddle.net/CPrEe/37/
Any suggestions?
Turns out its rather simple ;)
After you add the element/shape, all you have to do is use the simulate function to simulate mouse down and it drags....
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 20,
y: 20,
width: 100,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
//What I really want here is to start dragging the circle until the
//user releases the mouse, which would then place the circle.
rect.on('mousedown', function(evt) {
var userPos = stage.getUserPosition();
var latestElement = new Kinetic.Circle({
x: userPos.x,
y: userPos.y,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
})
layer.add(latestElement);
// Here's the bit you want. After adding the circle (with draggable:true) simulate the mousedown event on it which will initiate the drag
latestElement.simulate('mousedown');
layer.draw();
});
layer.add(rect);
stage.add(layer);​
http://jsfiddle.net/CPrEe/38/
http://kineticjs.com/docs/symbols/Kinetic.Node.php#simulate

Categories

Resources