Fabric.js group coordinates with panned canvas - javascript

I have a canvas that's initially panned so that the 0,0 point is in the middle of the screen. I'm trying to move a programmatically created group, but for some reason the group moves relative to the upper left corner of the visible canvas instead of the 0,0 point of the canvas. After that first movement the panning works correctly and the group moves around that position, until I discard it and create it again, and then it's relative to the upper left corner again, without taking the pan into account. If I create a group selection using the selection tool, it works correctly every time.
I pan the canvas with this:
var offsetX = -canvas.getWidth()/2;
var offsetY = -canvas.getHeight()/2;
canvas.absolutePan(new fabric.Point(offsetX, offsetY));
This is how I create the group:
var group = new fabric.Group();
objects.forEach(function (o) {
group.addWithUpdate(o);
canvas.remove(o);
});
canvas.add(group);
Then I try to move it with this:
group.set("left", 0);
canvas.renderAll();
And then the group is right at the left edge of the canvas element. But if I select the group with my mouse and use this instead, it appears right where it should be - in the center:
canvas.getActiveGroup().set("left", 0);
canvas.renderAll();
So, how do I get this to work properly? I know I could just calculate an offset from the canvas transformations, but I feel like this should be compensated for in the library itself and I'm either somehow using it wrong or it's a bug.

Related

Use of absolutePan() to move background image and elements around on canvas with touch event

I am using next code to force content of canvas to follow finger touch movement on screen (drag and drop).
document.addEventListener('touchmove', function(e){
if(e.targetTouches.length ==1) {
var canavasMovex=e.targetTouches[0].pageX-canvasLeftofset-canvas.width/2;
var canavasMovey=e.targetTouches[0].pageY-canvasTopofset-canvas.height/2;
document.getElementById("a").innerHTML= canavasMovex;
document.getElementById("b").innerHTML= canavasMovey;
var delta = new fabric.Point(-canavasMovex,-canavasMovey);
canvas.absolutePan(delta);
canvas.renderAll();
}
}, false);
Note: "-canavasMovex,-canavasMovey" values have the negative sign (I had to use it because otherwise the canvas was moving opposite to finger moment(I think you can ignore this fact and that it has noting to do with offset, which I am trying to solve asking for help here))
I have displacement of position proportional to the distance of touch point from the centre of canvas. If I start the movement pressing from the centre of screen, then displacement is not so notable. But if I start from top corner (for example) I get centre of canvas there in that touch point. From attached drawing I was trying with taking of consideration of "d" parameter (distance of touching point to the centre of canvas), to find right value for the equation for the canvas.absolutePan() function, but no success. Can you help me with this case,pleas? I was trying to use some solutions from browsing the internet to find the way to move canvas content. But then I had lost some of functions of my app because "new" libraries were not interacting correctly with my existing fabric.min.js library. Image info(external rectangle-web page,internal rectangle-canvas): a-canvas offset,b-distance to centre of canvas,c- touch point,d-distance from touch point to centre of canvas. So on the end I am trying to get to accomplish this task in this way.
Malfunction:
Bigger than distance "d" is on starting of dragging , bigger is then the offset. (actuary if I start dragging from canvas corner, canvas content transfers its centre there)
The solution is to get the finger position, calculate the difference to previous position, add difference to the previous position and then finally do the panning.
var canvasMovexoold = 0;
var canvasMoveyoold = 0;
document.addEventListener('touchstart', function(e){
canvasMovexoold = canvasLeftofset+canvas.width/2-e.targetTouches[0].pageX;
canvasMoveyoold =canvasTopofset+canvas.height/2- e.targetTouches[0].pageY;
}, false);
document.addEventListener('touchmove', function(e){
if(e.targetTouches.length ==1) {
var canvasMovexo += canvasLeftofset+canvas.width/2-e.targetTouches[0].pageX-canvasMovexoold ;
var canvasMoveyo +=canvasTopofset+canvas.height/2- e.targetTouches[0].pageY-canvasMoveyoold ;
var delta = new fabric.Point(canvasMovexo,canvasMoveyo);
canvas.absolutePan(delta);
canvasMovexoold = canvasLeftofset+canvas.width/2-e.targetTouches[0].pageX;
canvasMoveyoold =canvasTopofset+canvas.height/2- e.targetTouches[0].pageY;
}
}, false);

Moving multiple objects simultaneously in createJS/easelJS

I've been using easelJS within the createJS framework for a project and have enjoyed it a lot until recently hitting a roadblock. I have multiple objects that I'd like to move simultaneously when one of the group is dragged. Here is my current situation:
What I'd like to do is when the red circle is moved, the red crosshairs would also move so that they appear to be "locked" to the circle. The same with the green circle.
I have been able to accomplish something very close to this by adding the circles and crosshairs to a container, as mentioned in the answers to this question:
Easeljs Scrollable Container
But the issue I encounter is that the container is actually a rectangle, such that I can click anywhere between the circle and crosshairs to move the various objects contained within the container. Instead I would like for the objects to be moved only when I click on a circle.
Does anyone have any idea how to accomplish this? Am I correct in thinking this can be accomplished somehow with easelJS containers?
Containers should be fine. You can turn off mouseEnabled on the cross-hair in order to make it ignore the mouse.
You could also just store the offset for each cross-hair/circle, and just set the cross-hair position when the circle moves.
Here is a quick demo:
http://jsfiddle.net/lannymcnie/kah9of6e/
// Set the offset when the circle is pressed
circle.on("mousedown", function(e) {
circle.offset = new createjs.Point(crosshair.x-circle.x, crosshair.y-circle.y);
});
// Add drag and drop to each shape
circle.on("pressmove", handleDrag);
crosshair.on("pressmove", handleDrag);
function handleDrag(e) {
// Move the target to the mouse
e.target.x = e.stageX; e.target.y = e.stageY;
// If the target is the circle, also move the cross-hair
if (e.target == circle) {
// Move the cross-hair
crosshair.x = circle.x + circle.offset.x;
x.y = circle.y + circle.offset.y;
}
}

svg.js strange interaction of scale and move/center

I'm not very familiar working with svgs in js but here is something that is definitely strange.
I'm having an arrow and then a path that is supposed to fill that arrow to a certain extend. looks like this:
Now my aim is to be able to scale the white part but it should still stay inside that arrow.
Now the weird thing is that I cannot figure out how move the white part back into the right place. I've tried different attempts.
here is my current code (it works for scaleFactor 1 but not for any other):
var draw = SVG('arrow').size(500, 500);
var arrowPath=draw.polyline('10,243.495 202.918,15.482 397.199,245.107').fill('none').stroke({ width: 20 });
var arrow=draw.group();
arrow.add(arrowPath.clone());
var scaleFactor=0.5;
var fillArrow=draw.path('M357.669,198.387c-41.747,35.357-95.759,56.678-154.751,56.678c-58.991,0-113.003-21.32-154.75-56.676l154.75-182.907 L357.669,198.387z');
fillArrow.fill('#ffffee');
var moveX=(arrowPath.width()/2-fillArrow.width()/2)/scaleFactor+9.5;
console.log(moveX);
fillArrow.center(arrowPath.cx(), arrowPath.cy()).scale(scaleFactor);
//another attempt was
fillArrow.move(moveX,0);
When you are scaling, rotating and translating in SVG you are doing coordinate transforms. That is, you are actually not changing the object you are drawing but the coordinate system that you are drawing the object in. Think of it as pixel has a certain size on your screen, and if your do svgObject.scale(0.5) the pixel becomes half the size.
So if you draw a square by path('M10,10 L20,10 L20,20 L10,20 z') and then apply scale(0.5) it will look like you have drawn a path that looks like path('M5,5 L10,5 L10,10 L5,10 Z')
This might sound strange at first but, but alot of geometrical calculations becomes much simpler when you can do this.
You want to scale around the tip of the arrow (make sure that does not move). Then you should place that point in the origo (0,0) and draw the object around that point. Do that in a group. Because then you can translate the group coordinate system to the correct position.
var draw = SVG('arrow').size(600, 600);
// create a group
var arrow = draw.group();
// Draw the arrow path in the group
// I have placed the "tip" of the arrow in (0,0)
var arrowPath = arrow.polyline('-250,250 0,0 250,250').fill('none').stroke({
width: 20
});
// Draw the fill arrow in the group. Again, the point
// I which to scale around is placed at (0,0)
var fillArrow = arrow.path('M0,0L150,150,-150,150z').fill('#ffffee');
// Move the group to the position we like to display it in the SVG
arrow.move(260, 20);
var scaleFactor = 0.5
fillArrow.scale(scaleFactor);
And here is a working example where you can test and change the scale factor.
http://jsfiddle.net/ZmGQk/1/
And here is a good explanation on how the translate, rotate and scale works.
http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Transforming_the_Coordinate_System

Understanding rotation and calculating the top left point in KineticJS

I am working on a page where I can view images. I want to create a rotation tool. I've done that, but, it's not working consistently. When I set up the centre point to rotate by, the image jumps slightly, and it gets worse each time. I was experimenting, and, I have code to add a wedge to the top left corner of my top level group ( so, at 0,0 ). If I rotate the image by 45 degrees and drag it so that half of it is off the left edge of my canvas, then I call getAbsolutePosition on the wedge and on the group, I get these values:
layer.getAbsolutePosition()
Object {x: 104.66479545850302, y: 279.2748571151325}
wedge.getAbsolutePosition()
Object {x: 180.2684127179338, y: -73.48773356791764}
I think this means my y position is actually the bottom of the image, which is off screen.
What I want to do, is calculate the absolute position of the middle of my image, when the mouse moves over it, regardless of it's rotation. I have some code that works out points with rotation, which seems like it works at first, almost, but it just gets more and more broken the more I use the tool. I feel like there's something about how Kinetic is tracking these things and what it's reporting, that I am missing. Any hints would be most appreciated. Tutorials I can read are even better ( yes, I've read everything linked from the KineticJS site and searched the web ).
In a nutshell, the question is, if I have an image inside a group, and it's rotated, how do I work out the centre point of the image, taking the rotation in to account, and how do I set the offset so it will rotate from that point, and stay in the same place ?
Thanks
As you've discovered about KinetiJS:
rotation is easy
dragging is easy
dragging+rotation is difficult
After you drag your image you must reset its rotation point (offsetX/offsetY).
KineticJS makes dragging+rotation more difficult than it has to be.
Resetting the offset points of your image will cause KineticJS to automatically move your image (Noooo!!).
That's what's causing your jumping.
The solution to the "jumping" problem:
When you reset the image's rotation point (offsetX/OffsetY) you must also reset the image's X/Y position.
This code resets both XY and Offsets for an image after dragging:
A Demo: http://jsfiddle.net/m1erickson/m9Nw7/
// calc new position and offset
var pos=rect.getPosition();
var size=rect.getSize();
var offset=rect.getOffset();
var newX=pos.x-offset.x+size.width/2;
var newY=pos.y-offset.y+size.height/2;
// reset both position and offset
rect.setPosition([newX,newY]);
rect.setOffset(size.width/2,size.height/2);

Object controll with mouse position relative to circle

Need some inspiration. I've got a picture (blue) and want it to move proportional to the mouse position inside an invisible area (orange). So, if the mouse moves in top-left direction, the image should follow the movement.
I don't want to simply copy the mouse position, rather create an Joystick like behaviour, so if the mouse moves, the image should move stepwise in the desired direction.
But how? I've no idea how to set the right x+y coordinates for the image or how to establish a formula to calculate them.
Incremental (vectored) positions. Consider:
Let's call the dead center of your invisible circle the vector reference point (0,0) or VRP.
You move your mouse away form the VRP. Let's use your image as a reference and say that your mouse is at (-3x -2y) relative to the VRP. You keep it there; It creates a -3 X vector and a -2 Y vector.
For as long as you keep your mouse there, those vectors will be applied to the square's current coordinate at each cycle, like this:
Assume Picture starter position is absolute 100,100.
Cycle 1: [x]:100 -3 = 97;[Y]:100 -2 = 97. New picture position = 97x98y.
Cycle 2: [x]:97 -3 = 94;[Y]:98 -2 = 96. New picture position = 94x96y.
And so forth. If you want to stop the movement, just bring the cursor back to the VRP.
You may sophisticate the mechanism creating acceleration intermediate vectors, or a dead zone around the vector reference point.

Categories