I'm dragging a rectangle 'left_handle' using Kinetic JS. During the drag I want to also position the left edge of another rectangle 'box' on top of it. I'm not getting any errors, so I'm assuming what I'm doing is working, but I think I need to do something to update the canvas or...?
The left_handle rectangle moves as expected, but nothing happens to the box.
var STAGE_WIDTH = 500;
var stage = new Kinetic.Stage({
container: "container",
width: STAGE_WIDTH,
height: 200
});
var layer = new Kinetic.Layer();
var rectX = 80;
var rectY = stage.getHeight() / 2 - 25;
var box = new Kinetic.Rect({
x: rectX,
y: rectY,
width: 100,
height: 50,
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4
});
var handle_left = new Kinetic.Rect({
x: rectX,
y: rectY,
height: 50,
width: 10,
fill:'#FFC400',
stroke:'black',
strokeWidth:3,
draggable:true,
dragConstraint:'horizontal',
dragBounds: {
left: 10, right: STAGE_WIDTH - 20
}
});
// - - - - - - - this is the bit that doesn't work - - - - -
handle_left.on('dragmove', function(evt) {
box.setX(handle_left.x);
layer.draw();
});
// - - - - - - - - - - - -
layer.add(box);
layer.add(handle_left);
stage.add(layer);
you were close, try this:
box.setX(handle_left.getX());
For reference: http://jsfiddle.net/jHCRm/
Related
Using the sample code from Konvajs.org as a base (https://konvajs.org/docs/sandbox/Multi-touch_Scale_Stage.html), I have added a large SVG to a layer (4096 x 3444) to experiment with zoom / pan of a vector-based map, base64 encoded SVG in this instance. Initial impressions are good however during testing I experience an odd bug where during a pinch the view of the map would snap to a different location on the map not the area that I centred on.
Here is the code (map base64 code removed due to length):
// by default Konva prevent some events when node is dragging
// it improve the performance and work well for 95% of cases
// we need to enable all events on Konva, even when we are dragging a node
// so it triggers touchmove correctly
Konva.hitOnDragEnabled = true;
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
draggable: true,
});
var layer = new Konva.Layer();
var triangle = new Konva.RegularPolygon({
x: 190,
y: stage.height() / 2,
sides: 3,
radius: 80,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
});
var circle = new Konva.Circle({
x: 380,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
let bg = new Konva.Image({
width: 4096,
height: 3444
});
layer.add(bg);
var image = new Image();
image.onload = function() {
bg.image(image);
layer.draw();
};
image.src = 'data:image/svg+xml;base64,...';
function getDistance(p1, p2) {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
function getCenter(p1, p2) {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
};
}
var lastCenter = null;
var lastDist = 0;
stage.on('touchmove', function (e) {
e.evt.preventDefault();
var touch1 = e.evt.touches[0];
var touch2 = e.evt.touches[1];
if (touch1 && touch2) {
// if the stage was under Konva's drag&drop
// we need to stop it, and implement our own pan logic with two pointers
if (stage.isDragging()) {
stage.stopDrag();
}
var p1 = {
x: touch1.clientX,
y: touch1.clientY,
};
var p2 = {
x: touch2.clientX,
y: touch2.clientY,
};
if (!lastCenter) {
lastCenter = getCenter(p1, p2);
return;
}
var newCenter = getCenter(p1, p2);
var dist = getDistance(p1, p2);
if (!lastDist) {
lastDist = dist;
}
// local coordinates of center point
var pointTo = {
x: (newCenter.x - stage.x()) / stage.scaleX(),
y: (newCenter.y - stage.y()) / stage.scaleX(),
};
var scale = stage.scaleX() * (dist / lastDist);
stage.scaleX(scale);
stage.scaleY(scale);
// calculate new position of the stage
var dx = newCenter.x - lastCenter.x;
var dy = newCenter.y - lastCenter.y;
var newPos = {
x: newCenter.x - pointTo.x * scale + dx,
y: newCenter.y - pointTo.y * scale + dy,
};
stage.position(newPos);
lastDist = dist;
lastCenter = newCenter;
}
});
stage.on('touchend', function () {
lastDist = 0;
lastCenter = null;
});
layer.add(triangle);
layer.add(circle);
stage.add(layer);
I am unsure if this is due to the large size of the image and / or canvas or an inherent flaw in the example code from Konvas.js. This has been tested, with the same results, on 2 models of iPad Pro, iPhone X & 11, Android Pixel 3, 5 and 6 Pro.
Here is the code on codepen as an example: https://codepen.io/mr-jose/pen/WNXgbdG
Any help would be appreciated, thanks!
I faced the same issue and discovered that it was caused by the dragging functionality of the stage. Everytime if (stage.isDragging()) evaluated to true, the jump happened.
For me what worked was setting draggable to false while pinch zooming and back to true on touch end.
stage.on('touchmove', function (e) {
...
if (touch1 && touch2) {
stage.draggable(false);
....
}
});
stage.on('touchend', function (e) {
...
stage.draggable(true);
});
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>
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>
I'm struggling to implement a little things on canvas with KineticJS.
I want to create a circle + a line which form a group (plane).
The next step is to allow the group to rotate around itself with a button that appears when you click on the group.
My issue is that when I click on the rotate button, it does not rotate near the button but elsewhere. Have a look :
My rotation atm : http://hpics.li/b46b73a
I want the rotate button to be near the end of the line. Not far away..
I tried to implement it on jsfiddle but I'm kinda new and I didn't manage to put it correctly , if you could help me on that, I would be thankful !
http://jsfiddle.net/49nn0ydh/1/
function radians (degrees) {return degrees * (Math.PI/180)}
function degrees (radians) {return radians * (180/Math.PI)}
function angle (cx, cy, px, py) {var x = cx - px; var y = cy - py; return Math.atan2 (-y, -x)}
function distance (p1x, p1y, p2x, p2y) {return Math.sqrt (Math.pow ((p2x - p1x), 2) + Math.pow ((p2y - p1y), 2))}
jQuery (function(){
var stage = new Kinetic.Stage ({container: 'kineticDiv', width: 1200, height:600})
var layer = new Kinetic.Layer(); stage.add (layer)
// group avion1
var groupPlane1 = new Kinetic.Group ({
x: 150, y: 150,
draggable:true
}); layer.add (groupPlane1)
// avion 1
var plane1 = new Kinetic.Circle({
radius: 10,
stroke: "darkgreen",
strokeWidth: 3,
}); groupPlane1.add(plane1);
var trackPlane1 = new Kinetic.Line({
points: [10, 0, 110, 0],
stroke: "darkgreen",
strokeWidth: 2
}); groupPlane1.add(trackPlane1);
groupPlane1.on('click', function() {
controlGroup.show();
});
groupPlane1.setOffset (plane1.getWidth() * plane1.getScale().x / 2, plane1.getHeight() * plane1.getScale().y / 2)
var controlGroup = new Kinetic.Group ({
x: groupPlane1.getPosition().x + 120,
y: groupPlane1.getPosition().y ,
opacity: 1, draggable: true,
}); layer.add (controlGroup)
var signRect2 = new Kinetic.Rect({
x:-8,y: -6,
width: 20,
height: 20,
fill: 'white',
opacity:0
});
controlGroup.add(signRect2);
var sign = new Kinetic.Path({
x: -10, y: -10,
data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z',
scale: {x:0.5, y:0.5}, fill: 'black'
}); controlGroup.add (sign)
controlGroup.setDragBoundFunc (function (pos) {
var groupPos = groupPlane1.getPosition();
var rotation = degrees (angle (groupPos.x, groupPos.y, pos.x, pos.y));
var dis = distance (groupPos.x, groupPos.y, pos.x, pos.y);
groupPlane1.setRotationDeg (rotation);
layer.draw()
return pos
})
controlGroup.on ('dragend', function() {
controlGroup.hide();
layer.draw()
})
controlGroup.hide();
layer.draw()
})
You can adjust the rotation point by setting the offsetX and offsetY of the group.
I'm trying to create a line within a bounded area that can also be dragged around once it has been drawn on the layer. Although there are solutions for creating lines dynamically - http://jsfiddle.net/42RHD/65/, I also need them to be draggable around the canvas.
Setting
draggable: true
does not work. I'm guessing it's because we are overriding the mouse down and mouse up events? Here's my attempt with a different approach, which also doesn't work -
<body>
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
<script defer="defer">
var x1, y1, x2, y2
var stage = new Kinetic.Stage({
container: 'container',
width: 700,
height: 700
});
var layer = new Kinetic.Layer();
var line = new Kinetic.Line({
y: 0,
points: [x1, y1, x2, y2],
stroke: 'black',
strokeWidth: 5,
lineCap: 'round',
lineJoin: 'round',
draggable: true
});
var bounds = new Kinetic.Rect({
x: 50,
y: 50,
width: 600,
height: 600,
fill: 'white',
stroke: 'black',
strokeWidth: 4
});
layer.add(bounds);
layer.add(line);
stage.add(layer);
bounds.on('mousedown', function() {
var start = stage.getPointerPosition();
x1 = start.x - 190;
y1 = start.y - 40;
});
bounds.on('mouseup', function() {
var end = stage.getPointerPosition();
x2 = end.x - 190;
y2 = end.y - 40;
line.draw();
});
</script>
Made the switch to FabricJS. The following fiddle shows what I wanted to do:
http://jsfiddle.net/RDqTd/27/
updateComplexity();
takes care of it.