Fabric.js right mouse click - javascript

Is there a way to receive right click mouse events on a Fabric.js canvas?
The following code works only with left click:
canvas.observe('mouse:down', function(){console.log('mouse down'));

NOTE: Most answers above are outdated; this answer applies to the latest Fabric version 2.7.0
Simply enable firing right/middle click events for your Fabric canvas
The config for firing right click and middle click events in the canvas can be found here for fireRightClick and here for fireMiddleClick and are set to false by default. This means right and middle click events are by default disabled.
The parameter stopContextMenu for stopping context menu to show up on the canvas when right clicking can be found here
You can enable these simply by setting the values when creating your canvas:
var canvas = new fabric.Canvas('canvas', {
height: height,
width: width,
fireRightClick: true, // <-- enable firing of right click events
fireMiddleClick: true, // <-- enable firing of middle click events
stopContextMenu: true, // <-- prevent context menu from showing
});
Now your mousedown event will fire for all clicks and you can distinguish them by using the button identifier on the event:
For canvas:
canvas.on('mouse:down', (event) => {
if(event.button === 1) {
console.log("left click");
}
if(event.button === 2) {
console.log("middle click");
}
if(event.button === 3) {
console.log("right click");
}
}
For objects:
object.on('mousedown', (event) => {
if(event.button === 1) {
console.log("left click");
}
if(event.button === 2) {
console.log("middle click");
}
if(event.button === 3) {
console.log("right click");
}
}
When clicking on objects you can reach the "real" mouse dom event through event.e:
if(event.button === 3){
console.log(event.e);
}

I've implemented right click by extending the fabric.Canvas class. Take a look here the _onMouseDown method.
Basically the right mouse down event for an object was disabled in fabricjs by default.

If you want to handle right clicks (on canvas or its objects), then set context menu listener on upper-canvas element. Using canvas method findTarget you can check if any target was clicked and if so, you can check type of the target.
let scope = this;
jQuery(".upper-canvas").on('contextmenu', function (options: any) {
let target: any = scope.canvas.findTarget(options, false);
if (target) {
let type: string = target.type;
if (type === "group") {
console.log('right click on group');
} else {
scope.canvas.setActiveObject(target);
console.log('right click on target, type: ' + type);
}
} else {
scope.canvas.discardActiveObject();
scope.canvas.discardActiveGroup();
scope.canvas.renderAll();
console.log('right click on canvas');
}
options.preventDefault();
});

The way I did this was to listen for a right click event across the entire canvas and match up the x,y coordinates of the click event to the object which is currently sitting at the given location. This solution feels a little like a hack but hey, it works!
$('#my_canvas').bind('contextmenu', function (env) {
var x = env.offsetX;
var y = env.offsetY;
$.each (canvas._objects, function(i, e) {
// e.left and e.top are the middle of the object use some "math" to find the outer edges
var d = e.width / 2;
var h = e.height / 2;
if (x >= (e.left - d) && x <= (e.left+d)) {
if(y >= (e.top - h) && y <= (e.top+h)) {
console.log("clicked canvas obj #"+i);
//TODO show custom menu at x, y
return false; //in case the icons are stacked only take action on one.
}
}
});
return false; //stops the event propigation
});

Here's what I did, which makes use of some built-in fabric object detection code:
$('.upper-canvas').bind('contextmenu', function (e) {
var objectFound = false;
var clickPoint = new fabric.Point(e.offsetX, e.offsetY);
e.preventDefault();
canvas.forEachObject(function (obj) {
if (!objectFound && obj.containsPoint(clickPoint)) {
objectFound = true;
//TODO: whatever you want with the object
}
});
});

Related

Detecting Left and Right Mouse Events for a Canvas Game

I want to implement a canvas minesweeper game using plain javascript. I use 2D array for my grid. For the game, I need to detect right and left mouse clicks, each of which will do different things. My research directed me towards mousedown, mouseup, contextmenu, however, my code does not seem to work, as for the right click it does the functions for both right and left click,because the mouseup event gets triggered for the right click as well. Can anyone help me understand how to distinguish between the two? I ran into examples of event.which, where left click is event.which === 0, and the right click is event.which === 2, but that works only for buttons, as far as I understood.
Here is the code.
canvas.addEventListener('mouseup', function(evt) {
let x1 = Math.floor(evt.offsetX/(canvas.height/rows));
let y1 = Math.floor(evt.offsetY/(canvas.width/cols));
draw (y1, x1); //this is my drawing functions (draws the numbers, bombs)
}, false);
canvas.addEventListener('contextmenu', function(evt) {
let j = Math.floor(evt.offsetX/(canvas.height/rows));
let i = Math.floor(evt.offsetY/(canvas.width/cols));
ctx.drawImage(flagpic, j*widthCell+5, i*widthCell+2, widthCell-9,
widthCell-5); //draws the flag where right mouse clicked
}, false);
Use click event for left click:
canvas.addEventListener('click', function(evt) { // No right click
And use contextmenu for right click: (Right click from keyboard context menu, also allowing you mouse right click)
canvas.addEventListener('contextmenu', function(evt) { // Right click
You need to call evt.preventDefault() as well for preventing the default action.
For your context, if you wanted to use mousedown or mouseup events, then you can use event.button to detect the clicked button was left:
canvas.addEventListener('mousedown', function(evt) {
if(evt.button == 0) {
// left click
}
Here's the button click values:
left button=0,
middle button=1 (if present),
right button=2
You can look on the example shown in the following link for greater details:
MouseEvent.button
<script>
var whichButton = function (e) {
// Handle different event models
var e = e || window.event;
var btnCode;
if ('object' === typeof e) {
btnCode = e.button;
switch (btnCode) {
case 0:
console.log('Left button clicked.');
break;
case 1:
console.log('Middle button clicked.');
break;
case 2:
console.log('Right button clicked.');
break;
default:
console.log('Unexpected code: ' + btnCode);
}
}
}
</script>
<button onmouseup="whichButton(event);" oncontextmenu="event.preventDefault();">
Click with mouse...
</button>
Try this might work for you
document.getElementById("mydiv").onmousedown = function(event) {
myfns(event)
};
var myfns = function(e) {
var e = e || window.event;
var btnCode;
if ('object' === typeof e) {
btnCode = e.button;
switch (btnCode) {
case 0:
console.log('Left');
break;
case 1:
console.log('Middle');
break;
case 2:
console.log('Right');
break;
}
}
}
<div id="mydiv">Click with mouse...</div>
Reference
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button

js mousedown with touch

is there a way to detect whether a 'mousedown' is a touch right click (hold the finger about 1 sec in place) or just a normal right click?
I think chrome can do this with "ev.originalEvent.sourceCapabilities.firesTouchEvents". But only chrome.
$('#container').mousedown(function(ev) {
if (ev.button === 2 && ev.comesFromTouch) return false
//...
}
edit:
current situation: after about one second after I pressed down my left mouse button, the browser automaticly triggers a 'mousedown' event with button = 2 (tested in the 'device toolbar' mode in chrome). I want to cancel this.
SOLUTION
If a right mousedown appears between a touchstart and touchend it is a right click on a touch screen.
it works something like this.
function onPcRight() { console.log(1);}
function onTouchRight() { console.log(2);}
$('#container').mousedown(function(ev) {
if (ev.button === BUTTON_RIGHT) {
if ($(this).prop('touchdown')) onTouchRight();
else onPcRight();
}
})
.on('touchstart', function() {
$(this).prop('touchdown', true);
})
.on('touchend', function() {
$(this).prop('touchdown', false);
});
I think you can use setTimeout/clearTimeout to count 1s. The pseudo code:
var global_timer = null;
$('#container').mousedown(function(ev) {
if (ev.button === 2) {
global_timer = setTimeout(fireTouchRightClick, 1000);
}
});
$('#container').mousemove(function(ev) {
cancelTouchRightClick();
});
$('#container').mouseleave(function(ev) {
cancelTouchRightClick();
});
$('#container').mouseup(function(ev) {
if (cancelTouchRightClick() && ev.button === 2) {
fireNormalRightClick();
}
});
function cancelTouchRightClick () {
if (global_timer) {
clearTimeout(global_timer);
global_timer = null;
return true;
}
return false;
}
function fireTouchRightClick () {
global_timer = null;
// TODO touch right click
}
I also found a repo to do mouse holding on Github: https://github.com/dna2github/dna2petal/tree/master/visualization
https://github.com/dna2github/dna2petal/blob/master/samples/visualization.html
Maybe you need to pass button type to the mousehold event callback

Removing event listeners before leaving a page

I've developed a javascript drag and drop that mostly uses the standard 'allowdrop', 'drag' and 'drop' events.
I wanted to customise the 'ghosted' dragged object, so I've added a display:none div that get populated with the innerHTML of the draggable element and made visible (display:block;) when the user starts dragging.
The draggable div is absolutely positioned and matches the mouse movements. For this I needed to add 3 event listeners to document.body. They are as follows:
document.body.addEventListener('dragover', function (ev) {
console.log("dragover event triggered");
ev = ev || window.event;
ev.preventDefault();
dragX = ev.pageX;
dragY = ev.pageY;
document.getElementById("dragged-container").style.left = (dragX - dragOffsetX) + "px";
document.getElementById("dragged-container").style.top = (dragY - dragOffsetY - 10) + "px";
if (mostRecentHoveredDropTargetId!="") {
if (dragX<mostRecentHoveredDropTargetRect.left || dragX>mostRecentHoveredDropTargetRect.right || dragY<mostRecentHoveredDropTargetRect.top || dragY>mostRecentHoveredDropTargetRect.bottom) {
document.getElementById(mostRecentHoveredDropTargetId).classList.remove("drop-target-hover");
mostRecentHoveredDropTargetId = "";
}
}
});
document.body.addEventListener('drop', function (ev) {
console.log("drop event triggered");
ev.preventDefault();
var data = ev.dataTransfer.getData("text"); // data set to the id of the draggable element
if (document.getElementById(data)!=null) {
document.getElementById(data).classList.remove("dragged");
document.getElementById("dragged-container").innerHTML = "";
document.getElementById("dragged-container").style.display = "none";
var draggablesClasses = document.getElementById(data).className;
if ((draggablesClasses.indexOf('draggable')==-1 || draggablesClasses=="") && document.getElementById(data).getAttribute('draggable')=="true") {
if (draggablesClasses=="") {
document.getElementById(data).className += "draggable";
} else {
document.getElementById(data).className += " draggable";
}
}
}
});
// resets dragged-container and origin .draggable, when mouse released outside browser window
document.body.addEventListener('mouseleave', function (ev) {
if (jqueryReady==true) {
$(".dragged").addClass("draggable");
$(".dragged").removeClass("dragged");
}
document.getElementById("dragged-container").innerHTML = "";
document.getElementById("dragged-container").style.display = "none";
});
This is all working fine. The drag and drop performs exactly as I expect.
The problem is when I go to another page, obviously those body event listeners are still running.
I've seen a number of answers here and have tried everything I've seen. For starters this:
window.onunload = function() {
console.log("about to clear event listeners prior to leaving page");
document.body.removeEventListener('dragover', null);
document.body.removeEventListener('drop', null);
document.body.removeEventListener('mouseleave', null);
return;
}
...but the console.log output doesn't even appear (let alone the 'null's being wrong, I'm pretty sure). I've also tried this, in the jQuery ready function:
$(window).bind('beforeunload', function(){
console.log("about to clear event listeners prior to leaving page");
document.body.removeEventListener('dragover', null);
document.body.removeEventListener('drop', null);
document.body.removeEventListener('mouseleave', null);
});
..but, once again, the console isn't even receiving that output.
I have also tried both the above with 'onbeforeunload' AND 'onunload'.
What am I doing wrong? - specifically to do with removing these window.body event listeners, I mean (Anything else I can sort out later).
Thanks.
removeEventListener requires the handler
Don't use anonymous functions is the solution.
Like this:
var dragHandler = function (ev) {
console.log("dragover event triggered");
};
document.body.addEventListener('dragover', dragHandler);
and after:
window.onunload = function() {
console.log("about to clear event listeners prior to leaving page");
document.body.removeEventListener('dragover', dragHandler);
return;
}

How to execute code when multiple events are performed ie. mousemove whilst mouse is clicked

Basically I need some code to execute when the mouse is clicked and being dragged around. With my current code the code executes when the mouse is down and when the mouse is moved but then when the mouse click is released the code continues to execute so I have included an if statement. I'm sure there is a much more efficient way of doing this so any help would be really appreciated :)
P.S another problem I am having is say the user clicks on the element, then lets go the mouseup ("//more code") gets executed once but if the user then clicks again and lets go it will be executed twice and if they select and deselect again 3 times etc.
As you can probably tell I am a bit of a jQuery noob! :P
Current code:
$('element').mousedown(function(event){
mouseDown = true;
$(document).mousemove(function(event2){
if(mouseDown){
//code goes here
}
}).mouseup(function(){
mouseDown = false;
//more code
});
});
"Another problem I am having is say the user clicks on the element,
then lets go the mouseup ("//more code") gets executed once but if the
user then clicks again and lets go it will be executed twice and if
they select and deselect again 3 times etc."
That's because you're binding an event every time they press the mouse down; the first time it happens, you have one event handler. The next time, two event handlers. The third time, three event handlers. And so on. You'll want to call unbind() beforehand to remove the existing event handlers, then rebind them.
I have recently used the following code to create a draggable jquery extension. You can pass a target for the drag action.
(function ($) {
var element;
var target;
var settings = {
onDrop: function (x, y) { }
};
var methods = {
init: function (options) {
if (options) {
$.extend(settings, options);
}
return this.each(function () {
// Code here for each element found by the selector
element = $(this);
if (options.target) {
target = $(options.target);
}
else {
target = element;
}
// Move the element by the amount of change in the mouse position
element.parent().mousedown(function (event) {
element.data('mouseMove', true);
element.data('mouseX', event.clientX);
element.data('mouseY', event.clientY);
});
element.parents(':last').mouseup(function () {
element.data('mouseMove', false);
});
element.mouseout(methods.move);
element.mousemove(methods.move);
});
},
move: function (event) {
if (element.data('mouseMove')) {
var changeX = event.clientX - element.data('mouseX');
var changeY = event.clientY - element.data('mouseY');
var newX = parseInt(target.css('margin-left')) + changeX;
var newY = parseInt(target.css('margin-top')) + changeY;
target.css({ 'margin-left': newX, 'margin-top': newY });
settings.onDrop(newX, newY);
element.data('mouseX', event.clientX);
element.data('mouseY', event.clientY);
}
}
};
$.fn.draggable = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.draggable');
return null;
}
};
})(jQuery);
then call it like this:
$('#overlay').draggable({ target: "#imagebehide", onDrop: function (x, y) {
$('#leftpos').val(x);
$('#toppos').val(y);
} });

How do I handle these events and why is my code not working?

I need to do the following. As soon as the user clicks on a div, i want to save the mouse coordinations while the user is moving the cursor over the div and is holding the left mouse button. When the user leaves the div or releases the left button, i want to stop recording the coordinates. I've got the following code:
$(document).ready(function() {
var coordhdl = new coordinateHandler();
$("#test").mousedown(function(e) {
$("#test").mousemove(function(ee) {
$("#test").mouseup(function(e) {
stopIt = true;
});
if(stopIt == false)
{
coordhdl.addCords(ee.pageX - this.offsetLeft, ee.pageY - this.offsetTop);
}
});
});
});
The problems with this code are:
It records coordinate even when the user only clicked the div without pressing the left button.
It doesn't stop recording the coordinates once it has been clicked.
I am new to Javascript/jQuery, so I don't know very much about it.
Something like this should work. It sets a flag to true/false when the mouse is pressed/released respectively. When the mouse moves, if the flag is set, the coordinates are added:
$(document).ready(function() {
var isDown = false,
coordhdl = new coordinateHandler();
$("#test").mousedown(function() {
isDown = true;
}).mouseup(function() {
isDown = false;
}).mousemove(function(e) {
if(isDown) {
coordhdl.addCords(ee.pageX - this.offsetLeft, ee.pageY - this.offsetTop);
}
});
});
Here's a demo of something similar in action (it simply writes the coordinates to a p element instead of using your coordinateHandler object).
Don't attach the event handlers inside the event handlers. On every mouse move you attach a new mouseup event handler. They don't get overridden, they get appended.
Use a "global" flag instead:
$(document).ready(function() {
var coordhdl = new coordinateHandler(),
recording = false;
$("#test").mousedown(function(e) {
recording = true;
}).mousemove(function(e) {
if(recording) {
coordhdl.addCords(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
}
}).mouseup(function(e) {
recording = false;
});
});
Every time there is a mousedown event, you add a mousemove handler, and every time the mouse moves, you add another mouseup handler. I can't see where the stopIt variable is declared so the scope of this variable may also be an issue. You don't need to nest the handlers, so try it this way.
$(document).ready(function() {
var coordhdl = new coordinateHandler();
var isRecording = false;
$("#test").mousedown(function(e) { isRecording = true })
.mouseup(function(e) { isRecording = false })
.mousemove(function(ee) {
if(isRecording)
{
coordhdl.addCords(ee.pageX - this.offsetLeft, ee.pageY - this.offsetTop);
}
});
});

Categories