FabricJS - Better solution to centering object on cursor when selected? - javascript

I would like selectable objects to snap their center to my mouse cursor on click. The only modification allowed to the user in this case is moving the object, no scaling, rotating, etc. Simply updating the position of the object on mousedown or selected will update its position only until the moving event is fired, where the object will snap to its original position and then begin following the mouse.
rect.on('moving', moveHandler);
function moveHandler(evt) {
var mousePnt = $canvas.getPointer(evt.e);
rect.set({
left:mousePnt.x - rect.width*0.5 , top:mousePnt.y - rect.height*0.5});
this.setCoords();
}
This is what I've come up with to center a selectable rectangle on the cursor, but I'm certain it's firing two movement events. Is there a way to override that original positioning. Or should I instead write my own mousedown, mouseup, and moving listeners to mimic the default dragging behavior?

You solution is fine if no rotating or scaling is involved.
You are not executing anything more than necessary if not the .setCoords that during a movement is optional since it will be called on mouseUp when the translation is finished.
If you want to take the mouseDown approach, changing the position once for all, you can use this logic:
var canvas = new fabric.Canvas('c');
canvas.add(new fabric.Rect({width: 50, height: 50}));
canvas.on('mouse:down', function(opt) {
if (this._currentTransform && opt.target) {
var target = opt.target;
target.top = this._currentTransform.ey - target.height/2;
target.left = this._currentTransform.ex - target.width/2;
this._currentTransform.left = target.left;
this._currentTransform.offsetX = this._currentTransform.ex - target.left;
this._currentTransform.offsetY = this._currentTransform.ey - target.top;
this._currentTransform.top = target.top;
this._currentTransform.original.left = target.left;
this._currentTransform.original.top = target.top;
this.renderAll();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.18/fabric.min.js"></script>
<canvas id="c" ></canvas>
You have to modify all the transform information that fabric noted down on the mouseDown event before the your mousedown handler is executed.

Related

How to achieve Google Maps-like scrolling behavior on Here Maps API for JavaScript?

I am using Here's Maps API for JavaScript and trying to achieve a scrolling behavior on mobile similar to what Google Map's API's has.
When I want to scroll by touching the map, scrolling the page should not be always prevented, only if the Map's DRAGGING behavior is disabled.
I have tried calling window.scroll() with the corresponding Y parameters extracted from pointer events, but it is not as smooth as the browser's default scrolling.
How should I use drag or some other event targeting the Map to scroll the page?
This code should work for you:
let mapevents = new mapsjs.mapevents.MapEvents(map),
behavior = new mapsjs.mapevents.Behavior(mapevents);
let startY, endY = 0;
map.addEventListener('dragstart', function(evt) {
if (evt.currentPointer.type == 'touch' && evt.pointers.length < 2) {
startY = evt.currentPointer.viewportY;
behavior.disable(H.mapevents.Behavior.DRAGGING);
}
});
map.addEventListener('drag', function(evt) {
if (evt.currentPointer.type == 'touch' && evt.pointers.length < 2) {
endY = evt.currentPointer.viewportY;
window.scrollBy(0, (startY - endY));
}
});
map.addEventListener('dragend', function(evt) {
behavior.enable(H.mapevents.Behavior.DRAGGING);
});
In dragstart and drag callbacks the scrolling is disabled only when pointer's type is touch and number of pointers is 1. That makes it possible to zoom & pan the map with two fingers on touch device and also with mouse on desktops / laptops.
Note, that method behavior.isEnabled(feature) doesn't disable the feature. For that you need to use behavior.disable(opt_features) See https://developer.here.com/documentation/maps/topics_api/h-mapevents-behavior.html
You can allow scrolling on the map by:
Disable the map's dragging behavior on dragstart and capture the Y coordinate.
Enable the map's dragging behavior on dragend and capture the Y coordinate.
Scroll the page by the difference of the start and end Y coordinates on dragend.
var mapEvents = new H.mapevents.MapEvents(map);
var beh = new H.mapevents.Behavior(mapEvents);
var startY, endY = 0;
map.addEventListener("dragstart", function(evt) {
startY = evt.currentPointer.viewportY;
beh.disable(H.mapevents.Behavior.DRAGGING);
});
map.addEventListener("dragend", function(evt) {
endY = evt.currentPointer.viewportY;
window.scrollBy(0, (startY - endY));
beh.enable(H.mapevents.Behavior.DRAGGING);
});

Zoom-IN/OUT canvas in pdf-reader, cause mouse coordinates to change it's position

I am currently implementing a functionality to place an canvas over pdf in pdf-reader so that we can draw on top of canvas, so the pdf in which canvas is placed can be zoomed-in and out to much extent, and I have to ensure that canvas should also resize as per the pdf, so that drawing on top of it remains intact, for resizing canvas I am using the canvas.style.width , drawing.style.height to resize it when pdf-resizes.
But now my concern is that on resizing mouse-pointer changes its position, for instance in the attached image my mouse is in bottom-right and it draws in top-left, due to shifting in mouse coordinates.
I am using RxJS for drawing, and capturing events, but I am not been able to figure out how to take care of mouse position in summary, I want coordinates to scale as per canvas resize.
private captureEvents(canvasEl: HTMLCanvasElement) {
// this will capture all mousedown events from the canvas element
this.drawingSubscription = fromEvent(canvasEl, 'mousedown')
.pipe(
switchMap((e) => {
// after a mouse down, we'll record all mouse moves
return fromEvent(canvasEl, 'mousemove')
.pipe(
// we'll stop (and unsubscribe) once the user releases the mouse
// this will trigger a 'mouseup' event
takeUntil(fromEvent(canvasEl, 'mouseup').do((event: WheelEvent) => {
console.log('e', e.type, e);
const prevPos = {
x: null,
y: null
};
this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
console.log('coordinatesArray', this.coordinatesArray);
})),
// we'll also stop (and unsubscribe) once the mouse leaves the canvas (mouseleave event)
takeUntil(fromEvent(canvasEl, 'mouseleave')),
// pairwise lets us get the previous value to draw a line from
// the previous point to the current point
pairwise()
)
})
)
.subscribe((res: [MouseEvent, MouseEvent]) => {
const rect = canvasEl.getBoundingClientRect();
// previous and current position with the offset
const prevPos = {
x: res[0].clientX - rect.left,
y: res[0].clientY - rect.top
};
const currentPos = {
x: res[1].clientX - rect.left,
y: res[1].clientY - rect.top
};
this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
// console.log('coordinatesArray', this.coordinatesArray);
this.drawOnCanvas(prevPos, currentPos);
});
}
Moreover, I am not generating the new canvas on every zoom-in/out, I am just modifying the it's height/width as per dimensions of page on top of which it is present.
I eventually solved it after spending a hell lot of time.
I have posted the answer in relevant thread that will be helpful to others going through the similar problem.
https://stackoverflow.com/a/56363476/5763627

How to convert screen coordinates to scene coordinates

I created a a-scene with some objects to drag. The final purpose is exactly what aframe-click-drag-component does. Unfortunately, this component is not compatible with the last version of A-Frame.
I created a custom component.
AFRAME.registerComponent('draggable', {
init: function () {
/* Some code */
}
});
I use the aframe-mouse-cursor-component to be able to get the mouseenter and mouseleave events on the draggable object, and detect when the mouse position allows the user to select the object.
I added an EventListener on document.body to know when the dragging starts:
document.body.addEventListener('mousedown', function (e) {
// start dragging
});
I continuously update a global variable to update the mouse position when a mousemove occurs:
document.addEventListener('DOMContentLoaded', function () {
document.body.addEventListener('mousemove', function (e) {
window.mouseX = e.clientX;
window.mouseY = e.clientY;
});
});
This way, I can easily get the position of the mouse during the dragging. But I do not know how to convert the position of the mouse on the client to a position in the Virtual Reality (restricted to a 2D plan to make it possible).
I solved this issue by using the raycaster coming from the cursor in the middle of the a-camera, but I want to drag the objects with the mouse-cursor, and this component does not have a raycaster.
I also tried to use some maths to convert the mouse coordinates to a coordinates set relative to the camera, without success (essentially because of the screen size which can vary).
What solutions are available? I would like to update the click-drag or the mouse-cursor, but I have no knowledge of THREE.js.
See https://github.com/mayognaise/aframe-mouse-cursor-component or https://github.com/mrdoob/three.js/blob/dev/examples/js/controls/DragControls.js or https://www.npmjs.com/package/aframe-click-drag-component for examples
The main chunk of code is like:
canvas.addEventListener( 'mousemove', function () {
var mouse = new THREE.Vector2();
var rect = canvas.getBoundingClientRect();
mouse.x = ( (event.clientX - rect.left) / rect.width ) * 2 - 1;
mouse.y = - ( (event.clientY - rect.top) / rect.height ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
}, false);

EaselJS: add child with mousedown triggered

The basic functionality I'm going for is...
Tap (mousedown) on the stage to create and add a child at that location.
*EDIT: I'm also trying to solve for multitouch, so multiple balls can be created at the same time.
As you hold down you can drag (pressmove) that child around and it grows (using regX/regY/scaleX/scaleY) until you release (pressup), at which point it falls (using a tick cycle).
I have everything sort of working, but I've hit a snag wherein I can't add a child and have it immediately register mousedown without releasing and pressing again.
Is there a way to manually trigger mousedown after adding, or some other workaround that makes more sense? dispatchEvent doesn't seem to be working.
Here's my stage event listener and touch bits:
stage.enableMouseOver(10);
createjs.Touch.enable(stage, false, false);
stage.preventSelection = false;
stage.addEventListener("stagemousedown", spawnsnowball);
And here are my functions. The spawnsnowball one includes displayObject event listeners verging on desperate, but the only way I've been able to get the pressmove and pressup working is to click on the same snowball again. releasesnowball right now just releases all instances of them (using a 'stagemouseup' listener), but if I can get it triggering off of pressup then I'll rewrite it to target just the event target.
function spawnsnowball(evt){
var ball = new createjs.Bitmap(loader.getResult("snowball"));
ball.crossOrigin="Anonymous";
ball.name="ball";
ball.scaleX = 0.5;
ball.scaleY = ball.scaleX;
ball.regX = ball.image.width/2;
ball.regY = ball.image.height/2;
ball.x = evt.stageX;
ball.y = evt.stageY;
ball.type = balltype;
ball.holding = 1;
ball.velX = 0;
ball.velY = 0;
ball.addEventListener("pressup",releasesnowball);
ball.addEventListener("pressmove",dragsnowball);
ball.onPress = function(mouseEvent) {};
stage.addChild(ball);
ball.dispatchEvent("mousedown");
ball.dispatchEvent("pressdown");
}
function dragsnowball(evt){
evt.target.x = evt.stageX;
evt.target.y = evt.stageY;
}
function releasesnowball(evt){
for(var i=0;i<stage.getNumChildren();i++){
var shape = stage.getChildAt(i);
if(shape.type == balltype){
if(shape.holding){
shape.holding = 0;
var dX = shape.x - shape.oldX;
var dY = shape.y - shape.oldY;
if(Math.abs(dY)>8)
dY = 8*dY/Math.abs(dY);
if(Math.abs(dX)>3)
dX = 3*dX/Math.abs(dX);
}
}
}
}
The pressmove event is special because it basically stores off the target of the last mousedown event, and then remembers it for pressmove and pressup events.
This means you can't really fake the event by forcing mouse events. Dispatching a mouse event from the target will not do the trick.
Instead, consider just handling the initial drag manually. You already know what you want to be the target of the pressmove, so you can listen for the stagemousemove event, and handle it yourself:
// Listen to the stagemousemove and manually call the event.
var initialDrag = stage.on("stagemousemove", function(event) {
event.target = ball; // Re-target the event so your other method works fine.
dragsnowball(event);
});
// When done, remove the move listener.
// The off() method supports a "once" parameter so you don't have to unsubscribe that listener.
stage.on("stagemouseup", function(event) {
stage.off("stagemousemove", initialDrag);
}, null, true); // Fires one time
Here is a quick sample using your code as the base: http://jsfiddle.net/3qhmur82/
I also added some comments in the demo which might be useful.
Hope that helps!

Trying to drag ship on canvas from left to right in Space Invaders clone

I have a "ship" that I have drawn on my canvas and I want to be able to drag it left and right on the bottom of the canvas when it is clicked. I have done some research but I'm having no luck.
I'm assuming I use onMouseDown to do this so I have this when the page loads...
canGame.onMouseDown = canCanvas.onMouseDown(e);
and here is the code that creates my ship
gShip = spriteNew("MediumSpringGreen", 230, 950, 150, 20);
From there I am not really sure what to do. I have a gameUpdate function which moves the invaders down the screen. A gameInit function that will draw the invaders and ship on the screen. Also I have a gameDraw function which draws everything on the screen. I have a few others which are not really important for my issue.
Here is the jsfiddle with the full source code. For some reason though it will run on my browser when I run the HTM file but not in jsfiddle.
http://jsfiddle.net/f66bk/2/
Like I said in comment, the best way of doing this kind of behaviour is by using the events mouseup, mousedown and mousemove.
I defined a var named isMouseDown to know that the ship is dragged that I set to true on mousedown and false on mouseup:
canGame.onMouseDown = function() { isMouseDown = true }
canGame.onMouseUp = function() { isMouseDown = false }
You will also need the event onmousemove to get your mouse position to redraw your ship properly:
canGame.onMouseUp = function(event) { moveShip(event, this) }
In the function moveShip, you can get your mouse position and set the position of your ship properly:
function moveShip(evt, obj) {
if(!isMouseDown)
return;
var target = evt.target || evt.srcElement,
rect = target.getBoundingClientRect(),
offsetX = evt.clientX - rect.left;
gShip.Rect.X = offsetX - gShip.Rect.Width/2;
}
Here is your jsfiddle :)

Categories

Resources