How to drag a line by its ends / line on a group - konvajs

Posting this self-answer question for my own future reference and in case it helps anyone else passing this way.
I have a straight line with circles at each end to act as anchors for positioning the line. These shapes are in a group. I want the user to be able to click and drag a circle and have the line follow the movement of the circle.
The group is draggable and when I drag either circle the entire group is moved - what I want is to drag the circles independently of the group.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
}),
layer = new Konva.Layer({name: 'layer'}),
group = new Konva.Group({name: 'group', draggable: true}),
line = new Konva.Line({
name: 'the line',
x: 20,
y: 20,
points: [0, 0, 500, 200],
strokeWidth: 5,
stroke: 'cyan'
}),
circle1 = new Konva.Circle({
name: 'circle1',
x: 20,
y: 20,
radius: 10,
fill: 'magenta'
}),
circle2 = circle1.clone({name: 'circle2', x: 520, y: 220, fill: 'magenta'});
group.add(line, circle1, circle2)
stage.add(layer)
layer.add(group);
// What next ?
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#8.3.2/konva.min.js"></script>
<div id="container"></div>

Found the answer: Make the circles draggable, then use the position of the dragged circle to give the position to move the appropriate end of the line to.
When shapes are on a group, their position is relative to the group parent. I wondered if this would be a complication. However since the line and circles are all children of the same group parent, we can get the plain shape.position() call to find the position we need. shape.position() gets the position relative to the parent. For future reference, you would use getAbsolutePosition() to find the position in relation to the stage but you don't need that for this case because all shapes are on the same parent and therefore everything is relative to that parent.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
}),
layer = new Konva.Layer({name: 'layer'}),
group = new Konva.Group({name: 'group', draggable: true}),
line = new Konva.Line({
name: 'the line',
x: 20,
y: 20,
points: [0, 0, 500, 200],
strokeWidth: 5,
stroke: 'cyan'
}),
circle1 = new Konva.Circle({
name: 'circle1',
x: 20,
y: 20,
radius: 10,
fill: 'magenta',
draggable: true
}),
circle2 = circle1.clone({name: 'circle2', x: 520, y: 220, fill: 'magenta'});
group.add(line, circle1, circle2)
stage.add(layer)
layer.add(group);
circle1.on('dragmove', function(event){
const circle1Pos = circle1.getPosition(),
circle2Pos = circle2.getPosition();
line.position(circle1Pos);
line.points([0, 0, circle2Pos.x - circle1Pos.x, circle2Pos.y - circle1Pos.y])
})
circle2.on('dragmove', function(event){
const linePos = line.getPosition(),
circle2Pos = circle2.getPosition();
line.points([0, 0, circle2Pos.x - linePos.x, circle2Pos.y - linePos.y])
})
<script src="https://unpkg.com/konva#8.3.2/konva.min.js"></script>
<p>Click & drag circles to reposition the line. Drag line to move group.</p>
<div id="container"></div>

Related

How to drag a group in Konva JS only from an inner shape and not from other areas of the group?

I want to drag the entire group from the center red square, (see fiddle)
but restrict dragging from the outer rectangle, while maintaining the inner rectangle's position inside the group.
Possibly when mouse is down I want the crosshair cursor to be dead in the center of the inner red rectangle.
Any help would be appreciated, thanks.
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
let layer = new Konva.Layer();
let group = new Konva.Group({
draggable: true,
height:50,
width: 50
});
const containerRect = new Konva.Rect({
width:50,
height: 50,
fill: 'yellow'
});
const rect = new Konva.Rect({
x: 15,
y: 15,
width: 20,
height: 20,
fill: 'red'
});
stage.add(layer);
group.add(containerRect);
group.add(rect);
layer.add(group);
layer.draw();
I suggest making the red rectangle draggable. Once you start a drag, you can stop it and start dragging of the group:
rect.on('dragstart', () => {
// stop rect dragging
rect.stopDrag();
// move group to the center
const size = group.getClientRect();
const pos = stage.getPointerPosition();
group.setAttrs({
x: pos.x - size.width / 2,
y: pos.y - size.height / 2,
});
group.startDrag();
});
https://jsfiddle.net/0cdw3ya7/21/

How to make a text appear from 0 to 100% (fold out effect) in KonvaJs

In KonvaJS how to make a text appear from 0 to 100% (fold out effect)
i want to create same effect as in below video, but with text
https://www.youtube.com/watch?v=HO6mco2MGH0
i tried giving width 0 to the node and then increased the width to 100% in the tween. But it is not giving me the desired effect. Text is appear character by character.
TIA
For that case, you can use group clipping.
const group = new Konva.Group({
clipX: 100,
clipY: 0,
clipWidth: 100,
clipHeight: 100
});
layer.add(group);
const circle = new Konva.Circle({
x: 0,
y: 50,
radius: 50,
fill: 'green'
});
group.add(circle);
circle.to({
x: 150,
duration: 1
});
Demo: http://jsbin.com/miketotuvo/1/edit?html,js,output

bezier line in konvajs is not the normal bezier

I want change normal line to bezier curve with this:
var grayBezierLine = new Konva.Line({
points: [50, 50, 100, 100, 150, 50, 200, 100, 250, 50, 300, 100, 350, 50],
stroke: 'gray',
strokeWidth: 15,
lineCap: 'round',
lineJoin: 'round',
bezier: true,
});
but the display is weird, after check the sourcecode, found this:
if (bezier) {
// no tension but bezier
n = 2;
while (n < length) {
context.bezierCurveTo(
points[n++],
points[n++],
points[n++],
points[n++],
points[n++],
points[n++]
);
}
}
At first I thought except the first(50, 50) point and last point(350, 50), all intermediate points are control points, but it's not, every three points forms a curve.
this is a reproduce demo.
My question is this a deliberate design or a bug?

Konvajs layer positions not being set as expected

I have an app where I am panning and zooming multiple layers.
However, when I set the position of the layers they are being updated to the wrong values.
For instance, after calling:
layer.position({
x: 12,
y: 233,
});
I would expect
layer.getClientRect();
to return
{
x: 12,
y: 233,
}
Instead it returns
{
x: -22,
y: 220,
}
Is there any known reason this is happening?
I realised I wasn't taking into account all the offsets. The x position of a circle is set from the centre while getClientRect() returns the width and height including negatives. So getClientRect() on a circle with a radius of 50 and x of 0 will actually return {x: -25, y: -25, width: 50, height: 50}.
Seems to work for me as advertised in this cut down test. Did you move the stage or something ?
In the snippet below I draw a layer with 4 'corner' rects, display its value from grtClientRect() in 'Layer pos #1' which gives (0, 0), then move the stage to 12, 233 and display its value from grtClientRect() in 'Layer pos #2' which shows (12, 233). I move the layer back up to (12, 23) just for fun thereafter.
var stage = new Konva.Stage({
container: 'container',
width: 1600,
height: 400
});
// add a layer
var layer = new Konva.Layer();
stage.add(layer);
// add 4 corner rects - code I happened to have to hand
var rectCorner = [];
for (i=0;i<4;i=i+1){
rectCorner[i] = new Konva.Rect({
x: 0,
y: 0,
width: 50,
height: 50,
fill: 'red',
opacity: 0.5,
strokeWidth: 0})
layer.add(rectCorner[i]);
}
// put the rects in the corners to give a known layer space
rectCorner[1].position({x:500, y: 0});
rectCorner[2].position({x:500, y: 150});
rectCorner[3].position({x:0, y: 150});
layer.draw();
// get the client rect now
var p = layer.getClientRect();
$('#info1').html('Layer pos #1=' + p.x + ', ' + p.y);
// move the layer...
layer.position({
x: 12,
y: 233,
});
// get the client rect now
var p = layer.getClientRect();
$('#info2').html('Layer pos #2=' + p.x + ', ' + p.y);
// move the layer back to the top...
layer.position({
x: 12,
y: 23,
});
stage.draw();
p
{
padding: 4px;
}
#container
{
background-color: silver;
overflow: hidden;
}
<div id='info1'></div>
<div id='info2'></div>
<div id="container"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.7.6/konva.min.js"></script>
<script type="text/javascript" src="test.js"></script>

Konvajs Clipping function but with opacity

I've been using the Konvajs clipping function to limit what can be seen inside a bounding box.
Has anyone managed to write a similar function but when the konva object leaves the bounding box it changes in opacity instead of simply not being seen?
Thanks for the input.
Here's a working version. The yellow region is the canvas extent. The clear image in the center is the clipping region. The semi-transparent region is the full extent of the image. Click & drag the clipped region and you can see the 'clipped off' component of the image displayed.
// this is the position for the clipping rectangle
var clipRect = {left: 30, top: 30, width : 420, height : 340, right: 450, bottom: 390};
// generic
// add a stage & add a layer
var s = new Konva.Stage({container: 'container', width: 800, height: 600});
var l = new Konva.Layer({draggable: true});
// background layer
var bgr = new Konva.Rect({x:0, y: 0, width: 500, height: 500, fill: 'gold', opacity: 0.1, listening: false})
l.add(bgr)
s.add(l);
// end of generic
// Add an image to show the full extent of the clipped image
var boundsRect = new Konva.Image({opacity: 0.2, stroke: 'black', strokeWidth: 1, draggable: false, dash: [2, 2], listening: false});
l.add(boundsRect);
// to clip we have to add a group with a clip.
var vp = new Konva.Group({
clip: { x: clipRect.left, y: clipRect.top, width : clipRect.width, height : clipRect.height}
});
// add a border to the clip region via a rect just surrounding it.
var r1 = new Konva.Rect({x: clipRect.left - 1, y: clipRect.top - 1, width : clipRect.width + 2, height : clipRect.height + 2, stroke: '#00008B', strokeWidth: 1, draggable: false});
l.add(r1);
/* This is the image that is the subject of the clipping effort */
var i=new Konva.Image({x: 0, y: 0, width: 0, height: 0, draggable: true,
// we want to ensure that the image cannot be dragged
// beyond the glip rect bounds.
dragBoundFunc: function(pos) {
var posRect = getPosRect(pos,this);
var iPos = this.getClientRect();
var newX = pos.x;
var newY = pos.y;
newX = (posRect.left >= clipRect.left) ? clipRect.left : posRect.left;
newX = (posRect.right <= clipRect.right) ? clipRect.right - posRect.width : newX;
newY = (posRect.top >= clipRect.top) ? clipRect.top : posRect.top;
newY = (posRect.bottom <= clipRect.bottom) ? clipRect.bottom - posRect.height : newY;
return {
x: newX,
y: newY
}}
});
i.on('dragmove', function() {
setBoundRect(this);
});
vp.add(i);
var imageObj = new Image();
imageObj.onload=function () {
i.image(imageObj);
boundsRect.image(imageObj);
i.width(imageObj.width);
i.height(imageObj.height);
setBoundRect(i);
s.draw();
}
imageObj.src = "http://btckstorage.blob.core.windows.net/site13706/gallery/2015/Porthleven%20lifeboat%20day%202015%20f%20-%20Chris%20Yacoubian%20Ltd.jpg";
l.add(vp) // add image to clipping viewport
s.draw()
// used to get the client rect of a shape.
function getPosRect(pos, ele){
var cliPos = ele.getClientRect();
var posRect = {left: pos.x, top: pos.y, right: pos.x + cliPos.width, bottom: pos.y + cliPos.height, width: cliPos.width, height: cliPos.height};
return posRect;
}
// set the bounds rect to the size of the given element
function setBoundRect(ele){
var x = ele.position();
var posRect = getPosRect(ele.position(), ele);
boundsRect.position({x: posRect.left, y: posRect.top});
boundsRect.size({width: posRect.width, height: posRect.height});
}
// change the image
$('img').on("click", function(e){
imageObj.src = $(this).prop("src");
})
.pic
{
max-width:150px;
max-height: 80px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<div id='container' style="display: inline-block; width: 750px; height: 400px; background-color: transparent; overflow: hidden;"></div>
<div style="display: inline-block; width: 500px; height: 400px; " >
<p>
<img class='pic' src="http://btckstorage.blob.core.windows.net/site13706/gallery/2015/53%20-%20Chris%20Yacoubian%20Ltd.jpg"/>
</p>
<p>
<img class='pic'src="http://btckstorage.blob.core.windows.net/site13706/gallery/2015/Crew%20day%20Mousehole%202015%20e%20-%20Chris%20Yacoubian%20Ltd.jpg" />
</p>
<p>
<img class='pic' src="http://btckstorage.blob.core.windows.net/site13706/gallery/2015/34%20-%20Chris%20Yacoubian%20Ltd.jpg"/>
</p>

Resources