Fabric JS Strange problem when handling mouse move over line object - javascript

I'm developing an object drawing canvas that uses Fabric.js and i'm encountering some strange issues when trying to deal with lines.
To make things clear, i've created a very simple jsfiddle that show what i'm seeing.
It's an event inspector that checks if the mouse is over a line, over a square or nowhere.
The objects are movable if you want, it's the same with fixed objects.
As you can see, if you try to position your mouse around the square, it only fires an event when you actually put the pointer on the square.
When you try to go near the line things change and we see a bigger area that fires this event.
Am I making some trivial mistake, its a bug or something else that i didn't think of?
Thank you all for your interest
var canvas = new fabric.Canvas('c');
var square = new fabric.Rect({
left: 200,
top: 100,
width: 20,
height: 20,
});
var line = new fabric.Line([0,0,100,100], {
stroke: 'red',
strokeWidth: 1,
});
canvas.add(square);
canvas.add(line);
canvas.on('mouse:move', (e)=>{
switch (e.target){
case line:
{
console.log("Pointer over a line");
canvas.backgroundColor="green";
break;
}
case square:
{
console.log("Pointer over a square");
canvas.backgroundColor="blue";
break;
}
default:
{
canvas.backgroundColor="white";
break;
}
}
canvas.renderAll();
});
https://jsfiddle.net/arozgkct/3/

Mouse events in FabricJS are always triggered within the bounding box of the object, which means that the bounding dimensions of your line are actually determined by the width and height of your line object. If you'd rather the events be confined to the line itself, draw the line on just one axis and then rotate it by setting an angle value of 45 degrees.
var line = new fabric.Line([0,100,100,100], {
stroke: 'red',
strokeWidth: 1,
angle: 45,
left: 50
});
https://jsfiddle.net/melchiar/puwgeb02/

In addition to what melchiar said, i learnt that there is another method to capture the event from the line only and not also from the bounding box around it.
I document it here if anyone else need in the future.
You just have to add these two lines after adding the line to the canvas
line.perPixelTargetFind = true;
line.targetFindTolerance = 4;
Complete code in the updated jsfiddle
https://jsfiddle.net/gmto0h58/

Related

Pan Zoom behaviour using EaselJS

I’m having some trouble incorporating pan/zoom behaviour with the ability to also drag-move some shapes around on the canvas, using EaselJS.
I want to be able to move the shape ONLY if I mousedown on it, but if I mousedown on the stage (i.e. not on a shape), then I want to be able to pan the stage.
This behaviour needs to be consistent regardless of the ‘zoom’ level (which is altered by the mousewheel).
I have read this: How to stop the event bubble in easljs? Which suggests that the stage mousedown events will fire regardless of whether I have clicked on a shape or empty space, so it would be better to create a ‘background’ shape to capture my mousedown events that are not on a ‘proper’ shape.
This fiddle is how I have set it up: https://jsfiddle.net/hmcleay/mzheuLbg/
var stage = new createjs.Stage("myCanvas");
console.log('stage.scaleX: ', stage.scaleX);
console.log('stage.scaleY: ', stage.scaleY);
function addCircle(r,x,y){
var g=new createjs.Graphics().beginFill("#ff0000").drawCircle(0,0,r);
var s=new createjs.Shape(g)
s.x=x;
s.y=y;
s.on('pressmove', function(ev) {
var localpos = stage.globalToLocal(ev.stageX, ev.stageY)
s.x = localpos.x;
s.y = localpos.y;
stage.update();
});
stage.addChild(s);
stage.update();
}
// create a rectangle 'background' Shape object to cover the stage (to allow for capturing mouse drags on anything except other shapes).
bg = new createjs.Shape();
bg.graphics.beginFill("LightGray").drawRect(10, 10, stage.canvas.width - 20, stage.canvas.height - 20); //deliberately smaller for debugging purposes (easier to see if it moves).
bg.x = 0;
bg.y = 0;
stage.addChild(bg);
stage.update();
//create a rectangle frame to represent the position of the stage.
stageborder = new createjs.Shape();
stageborder.graphics.beginStroke("Black").drawRect(0, 0, stage.canvas.width, stage.canvas.height);
stageborder.x = 0;
stageborder.y = 0;
stage.addChild(stageborder);
stage.update();
// MOUSEWHEEL ZOOM LISTENER - anywhere on canvas.
var factor
canvas.addEventListener("wheel", function(e){
if(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)))>0){
factor = 1.1;
} else {
factor = 1/1.1;
}
var local = stage.globalToLocal(stage.mouseX, stage.mouseY);
stage.regX=local.x;
stage.regY=local.y;
stage.x=stage.mouseX;
stage.y=stage.mouseY;
stage.scaleX = stage.scaleX * factor;
stage.scaleY = stage.scaleY * factor;
//re-size the 'background' shape to be the same as the canvas size.
bg.graphics.command.w = bg.graphics.command.w / factor;
bg.graphics.command.h = bg.graphics.command.h / factor;
// re-position the 'background' shape to it's original position of (0,0) in the global space.
var localzero = stage.globalToLocal(0, 0);
bg.x = localzero.x;
bg.y = localzero.y;
stage.update();
});
// listener to add circles to the canvas.
canvas.addEventListener('dblclick', function(){
var localpos = stage.globalToLocal(stage.mouseX, stage.mouseY);
addCircle(10, localpos.x, localpos.y);
});
bg.addEventListener("mousedown", function(ev1){
// purpose of this listener is to be able to capture drag events on the 'background' to pan the whole stage.
// it needs to be a separate 'shape' object (rather than the stage itself), so that it doesn't fire when other shape objects are drag-moved around on the stage.
// get the initial positions of the stage, background, and mousedown.
var mousedownPos0 = {'x': ev1.stageX, 'y': ev1.stageY};
var stagePos0 = {'x': stage.x, 'y': stage.y};
var bgPos0 = {'x': bg.x, 'y': bg.y};
bg.addEventListener('pressmove', function(ev2){
//logic is to pan the stage, which will automatically pan all of it's children (shapes).
// except we want the 'background' shape to stay where it is, so we need to offset it in the opposite direction to the stage movement so that it stays where it is.
stageDelta = {'x': ev2.stageX - mousedownPos0.x, 'y': ev2.stageY - mousedownPos0.y};
//adjust the stage position
stage.x = stagePos0.x + stageDelta.x;
stage.y = stagePos0.y + stageDelta.y;
// return the 'background' shape to global(0,0), so that it doesn't move with the stage.
var localzero = stage.globalToLocal(0,0);
bg.x = localzero.x;
bg.y = localzero.y;
stage.update();
});
});
The grey box is my background shape. I have deliberately made it slightly smaller than the canvas, so that I can see where it is (useful for debugging).
Double click anywhere on the canvas to add some red circles.
If you drag a circle, it only moves that circle.
If you drag on the grey ‘background’ area in between circles, it moves the whole stage (and therefore all the child shapes belonging to the stage).
Because the grey background is also a child of the stage, it wants to move with it. So I have included some code to always return that grey box back to where it started.
The black border represents the position of the ‘stage’, I just added it to help visualise where the stage is.
The mousewheel zoom control is based on the answer to this question: EaselJS - broken panning on zoomed image
Similar to drag-panning, when zooming I have to adjust the size and position of the grey ‘background’ box so that it renders in the same position on the canvas.
However, it doesn’t stay exactly where I want it to… it seems to creep up towards the top left corner of the canvas when I zoom out.
I’ve spent quite some time trying to diagnose this behaviour and can’t find out why it’s happening. I suspect it may have something to do with rounding.. but I’m really not sure.
Can anyone explain why my grey box isn't staying stationary when I zoom in and out?
An alternative method would be to scrap the ‘background’ shape used for capturing mousedown events that aren’t on a ‘proper’ shape.
Instead, it might be possible to use the ‘stage’ mousedown event, but prevent it from moving the stage if the mouse is over a ‘shape’.
Would this be a better way of handling this behaviour? Any suggestions how to prevent it from moving the stage?
Thanks in advance,
Hugh.
Ok,
So as usually happens, after finally asking for help, I managed to work out the problem.
The issue was caused by making the background shape (grey rectangle) 10px smaller than the canvas, so that I could see its position more clearly (to assist with debugging). How ironic that this offset was causing the issue.
The 10px offset was not being converted into the 'local' space when the zoom was applied.
By making the grey rectangle's graphic position at (0,0) with width and height equal to that of the canvas, the problem went away!
Hope this is of use to someone at some point in time.
Cheers,
Hugh.

How can I manage to move object between various layers?

I need to move object between layers, actually I have a main working area (say shape of square) and inside this working area I have a similar shape (bleed shape) same of the outer one but smaller in size, something like this :
https://jsfiddle.net/p0hszz22/24/ [see circle, square, rectangle shapes]
//# add main square
function addSquare() {
clearThisCanvas();
var square = new fabric.Rect({});
canvas.add(square);
addSquare1(); //call bleedline square
}
//# add bleedline square
function addSquare1() {
var square = new fabric.Rect({});
canvas.add(square);
centreOnCanvas(square);
}
So I got two objects on canvas, now my requirement is when I add any image and move that image(or any object) inside the working area the dotted line(bleed shape) should always be on above of my image, so that it shows the user hint that images or anything outside the bleed region will be ignored (present case : does not happened at all )
What I have tried :
I tried to change the order of these two shapes,and when I did the bleed shape hides itself when main shape(outer one) is selected.
I thought of making these shapes into a group but again no success.
How can I achieve this? all I need is to show the user as soon anything crosses the bleed line, it will not be considered.
Any link to study or keywords that I can again look for or any suggestion or any example will be helpful.
Thanks
PS: sorry for code in fiddle actually it was just to setup things quickly.
Try using an overlay image for the bleeding shape, maybe that fit your needs.
That image will always be on top of the other objects inside the canvas
canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png',
canvas.renderAll.bind(canvas), {
opacity: 0.5,
width : 400, // your width
height : 400, // your height
originX: 'center',
originY: 'center'
});

KinetikJS Keep bounds while dragging an object

Heyhey,
while learning JS and using KineticJS I am stuck with a little problem.
As Iam not able to get my own code into a fiddle right now lets take this example:
http://jsfiddle.net/m1erickson/n5xMs/
If we make the white rect also draggable:
var white = new Kinetic.Rect({
x: 20,
y: 20,
width: 300,
height: 300,
fill: 'white',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
the dragBoundFunc won´t work correctly anymore if the white rect is moved around. While debugging this with coda2, I found that when the white rect is dragged around, that the x and y attrs do not change.
Why is that so? How do I manage to make the dragBoundFunc work for moving parent-shapes?
thanks in advance for your help.
First of all:
I found that when the white rect is dragged around, that the x and y attrs do not change.
No, they don't, because they're defined statically. If you wanted changing values, you'd want to get the updated values within the dragBoundFunc. It might be as simple as calculating them the same way they are now, but inside that function.
How do I manage to make the dragBoundFunc work for moving parent-shapes?
In your example the white rectangle isn't a parent of anything, it's just a shape drawn on the same stage as the orange rectangle. What you want to do is make the white shape background and the orange rectangle both children of the same node, and drag that. The most direct way to do this here would be to make the layer itself draggable, e.g.:
var layer = new Kinetic.Layer({draggable: true});
But how you do this is arbitrary. The white and orange boxes might also be children of another Kinetic.Container, for example.

Best method for drawing a moving line JavaScript

I wish to have a line going across my canvas element in a similar fashion to the one I have managed to produce below after I had read up a little about the tween function:
http://jsfiddle.net/MjLdT/17/
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var line = new Kinetic.Line({
x: 100,
y: 100,
points: [0, 0, 0, 0],
stroke: '#000000'
});
layer.add(line);
stage.add(layer);
var tween = new Kinetic.Tween({
node: line,
duration: 3,
x: 800,
y: 100,
points: [-700, 0, 800, 0]
});
setTimeout(function () {
tween.play();
}, 2000);
Eventually I wish to have users be able to manipulate the line by clicking the left mouse button and the line goes up and to the right diagonally, right clicking and the line goes down diagonally (horizontal speed towards the right of the screen remains the same). Letting go of the mouse button will result in it returning to it's normal horizontal motion at whatever 'height' it is on the screen at that time.
This is one of my first attempts at using JavaScript and was wondering if the method I have chosen is appropriate for what I wish to achieve.
Also, any tips on how to get started on the mouse effects would be much appreciated.
The method is appropriate and it works, you should look into Kinetic.Animation which may give you slightly cleaner code. With an animation you can put more logic into the code that is being re-run. I believe tweens may be limited for what you are trying to do, it is possible but Animations are probably what you are looking for if you are aiming for a good design.
I would read through this documentation.
http://kineticjs.com/docs/Kinetic.Animation.html

KineticJS calling moveToTop() automatically after clicking over shape

I am with a little problem here but I don't know if it is a bug or a new feature since in the KineticJS change logs there is the following change:
"drag and drop operations now automatically dynamically create a temporary top layer, and place nodes there for groups and shapes to greatly improve drag and drop performance..."
I was used to use the version 4.0.0 in my test projects and I must use explicity moveToTop() when I want to move some shape to the top (I think that this is the correct way). Now (version 4.3.2) if I just click over some pic, it is automatically moved to the top against my wish. This temporary layer should be something internal, nothing visual. So is this really a bug or there is someway to turn this thing off.
Even the labs changed I think, before I think that the objects keeps the space order if you don't call the moveUp, moveToTop, etc, functions. For example in the following link there are no moveToTop() function calls, even this way if you click on the shapes, they are moved to the top.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-move-shape-to-another-container-with-kineticjs/
Thanks for the attention
While I dont think you can completely disable the layer, you can turn off moving the shapes into it. Say you have some shape like:
var shape1 = new Kinetic.Rect({
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'blue',
draggable: true
});
you need to add one parameter to this to disable moving your shape to the temporary layer on drag: 'dragOnTop' which needs to be false, so make your shapes something like:
var shape1 = new Kinetic.Rect({
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'blue',
draggable: true,
dragOnTop: false //<-- add this line so that this shape doesn't use the drag layer.
});

Categories

Resources