I'm currently using this code to bind events in a Google Chrome extension:
var bindEvent = function(elem ,evt,cb) {
//see if the addEventListener function exists on the element
if ( elem.addEventListener ) {
elem.addEventListener(evt,cb,false);
//if addEventListener is not present, see if this is an IE browser
} else if ( elem.attachEvent ) {
//prefix the event type with "on"
elem.attachEvent('on' + evt, function(){
/* use call to simulate addEventListener
* This will make sure the callback gets the element for "this"
* and will ensure the function's first argument is the event object
*/
cb.call(event.srcElement,event);
});
}
};
/* ... */
bindEvent(document,'click', function(event)
{ var target = event.target || event.srcElement;
/*Code to do stuff about a clicked element*/
this.removeEventListener('click',arguments.callee,false);
});
And it works well with the click event.
Now, my question is: what event could I use to change something about an element being hovered and not simply clicked on? The final goal would be to change the background color of the element being hovered by the cursor.
I tried mouseover, mouseenter, focus and focusin to no avail. To be exact, I tried to do a console.log() when the event triggers, but it never really happened, except one time when I clicked on a dialog box and it detected my focus on this element.
I currently am using Chrome (v24.0), but a cross-browser solution would be a nice feature because I plan to reuse the script on Firefox later. It's not a top priority though.
The relevant events for hovering are mouseover and mouseout - they are triggered when the mouse enters or leaves an element respectively. However, since that event is also triggered for the child elements of the element you attached your listener on and these events bubble up, you also have to check event.target:
elem.addEventListener("mouseover", function(event) {
if (event.target == elem) {
// Mouse pointer entered elem
}
}, false);
elem.addEventListener("mouseout", function(event) {
if (event.target == elem) {
// Mouse pointer left elem
}
}, false);
Related
Repro: https://jsfiddle.net/ssabc/cL6qxn1r/13/
I have a background element and a foreground element (you can think of it as a dialog popup on top of a canvas). When the user right-clicks on the foreground I would like to prevent the context menu from appearing in the background.
I tried binding a handler for the foreground's context menu and returning false from it to no avail:
document.getElementById('above').oncontextmenu = function(e) {
e.preventDefault();
return false;
}
As you can see in the JSFiddle, the oncontextmenu-event triggers on both elements. Here's a screenshot showing event firing in the background no matter which element is right-clicked
Is there any way to prevent the background event from firing?
You just need to add
e.stopPropagation();
to your child element right click event handler. With the change, it would look like this:
document.getElementById('above').oncontextmenu = function(e) {
e.preventDefault();
e.stopPropagation(); // <=== add this
getResultP().innerHTML += '<li>Dialog oncontextmenu called</li>';
return false;
}
This prevents the event from bubbling up the DOM tree. Read more about it here.
In a click event (attached to document) I want to figure out the target when the user started pressing the mousebutton.
Example:
User presses mousebutton on a custom popup
User moves the mouse outside the popup and let go
In that case the code below will return a target outside the popup, but I want to figure out if it started within the popup.
$(document).click(function(e)
{
// will return the target during *releasing the mouse button*
// but how to get the target where the *mouse press started*
console.log(e.target);
}
Of course I could track this manually by listening to mousedown and saving this within a variable - but I rather have something native, because:
less code
I might be missing edge cases
Both Jquery or vanilla JavaScript answers are good to me (but preference vanilla)
You could use mousedown together with a mouseup function, and have them both saving their targets to a global variable, then compare them in an if statement.
let downTarget, upTarget;
$(document).mousedown(function(e) {
downTarget = e.target;
});
$(document).mouseup(function(e) {
upTarget = e.target;
});
if(upTarget === downTarget){
*do stuff*
};
just use the event mousedown instead click like
$(document).mousedown(function(e)
{
console.log(e.target);
}
also has another event that verifies if the mouse is over that is the mouseover
I am trying to fix a bug with the mouseenter and mouseleave handler.
listener.addEventListener('mouseenter', function(){
element.style.visibility = "visible";
}, true);
listener.addEventListener('mouseleave', function(){
element.style.visibility = "hidden";
}, true);
The events work as expected except for when i am moving the mouse over the element it flashes the mouse leave event.
Any fixes for this?
Plain javascript solutions only, please (no 3rd party libraries).
The pointer in Javascript is only "hovering" the topmost (visible) element.
This means that if you've say a background div and when "entering" it you display another div on top of it the cursor will exit the background to enter the new div.
May be you can just set opacity to 0 instead of hiding the div and leave it always "visible" (also placing the event handler in the appearing div, not in the background one).
There is property named relatedTarget in the mouse event, which would help you fix it. you should check if it is not in the area like:
listener.addEventListener('mouseleave', function(event){
if(!event.relatedTarget ||
(event.relatedTarget != listener && event.relatedTarget.parentNode != listener))
element.style.visibility = "visible";
else
alert("I am still in listener area but mouseleave got triggered :)))");
}, true);
The point here is I have checked only for the first parent level, but you better create a function to check it for all the DOM tree in the listener node. I mean it could be one of the nested childNodes in the listener node.
I know it seems kind of weird, but it is the way it is, sometimes when it enters to a child nodes area mouseleave gets triggered.
Here's a JSFiddle of the behavior I'm seeing, relating to middle-click and the click event in Chrome and FF.
'click' kinda sorta works
Approach 1: Bind a click handler directly to an a element and a middle-click will trigger the handler in Chrome but not in FF.
$('div a').on('click', function(ev) {
// middle click triggers this handler
});
Approach 2: Bind a delegated click handler to a div which contains one or more a. Middle click will not trigger this handler in Chrome or FF.
$('div').on('click', 'a', function(ev) {
// middle click doesn't trigger this handler
});
This approach is extremely valuable if the div starts out empty and the a elements are filled in later by an AJAX call, or as a result of some user input.
'mouseup' works
Using mouseup instead of click causes both approach 1 and 2 to work in both browsers.
// Approach 1 w/ mouseup
$('div a').on('mouseup', function(ev) {
// middle click **does** trigger this handler in Chrome and FF
});
// Approach 2 w/ mouseup
$('div').on('mouseup', 'a', function(ev) {
// middle click **does** trigger this handler in Chrome and FF
});
Here's the JSFiddle with mouseup.
This is interesting and might be useful in some cases, because mouseup is almost click. But mouseup isn't click, and I'm after the behavior of click. I do not want to create a hacky mousedown; setTimeout; mouseup simulation of click.
I'm pretty sure the answer is "nope", but is there a cross-browser way to cause middle-click to trigger click handlers? If not, what are the reasons why?
The click event is generally fired for the left mouse button, however, depending on the browser, the click event may or may not occur for the right and/or middle button.
In Internet Explorer and Firefox the click event is not fired for the right or middle buttons.
Therefore, we cannot reliably use the click event for event handlers on the middle or right button.
Instead, to distinguish between the mouse buttons we have to use the mousedown and mouseup events as most browsers do fire mousedown and mouseup events for any mouse button.
in Firefox and Chrome event.which should contain a number indicating what mouse button was pressed (1 is left, 2 is middle, 3 is right).
In Internet Explorer on the other hand, event.button indicates what mouse button was clicked (1 is left, 4 is middle, 2 is right);
event.button should also work in Firefox and other browsers, but the numbers can be slightly different (0 is left, 1 is middle, 2 is right).
So to put that together we usually do something like this :
document.onmousedown = function(e) {
var evt = e==null ? event : e;
if (evt.which) { // if e.which, use 2 for middle button
if (evt.which === 2) {
// middle button clicked
}
} else if (evt.button) { // and if e.button, use 4
if (evt.button === 4) {
// middle button clicked
}
}
}
As jQuery normalizes event.which, you should only have to use that in jQuery event handlers, and as such be doing:
$('div a').on('mousedown', function(e) {
if (e.which === 2) {
// middle button clicked
}
});
In other words you can't use the onclick event, so to simulate it you can use both mousedown and mouseup.
You can add a timer to limit the time allowed between the mousedown and mouseup event, or even throw in a mousemove handler to limit the movement between a mousedown and mouseup event, and make the event handler not fire if the mouse pointer moved more than ten pixels etc. the possibilites are almost endless, so that shouldn't really be an issue.
$('#test').on({
mousedown: function(e) {
if (e.which === 2) {
$(this).data('down', true);
}
},
mouseup: function(e) {
if (e.which === 2 && $(this).data('down')) {
alert('middle button clicked');
$(this).data('down', false);
}
}
});
Short answer: Nope.
The question is, what do you want to capture the middle clicks for? A middle click isn't meant to interact with the current page but rather to open a link in a new tab.
Chrome is also currently working on droping this behavior: https://code.google.com/p/chromium/issues/detail?id=255
And there is currently a general discussion on the w3c mailing list about this topic: http://lists.w3.org/Archives/Public/www-dom/2013JulSep/0203.html
Yet for now, you can catch middleclicks in Firefox on a document-level:
$(document).on('click', function(e){
console.log(e);
});
I've build a factory for creating Middle mouse click handlers using vanilla JS and working in latest Firefox and Chrome:
const MiddleClickHandlerFactory = (node, handlerFn) => {
node.addEventListener('mousedown', e => {
if (e.button !== 1) return;
e.preventDefault(); // stop default scrolling crap! Instead install ScrollAnywhere!
const originalTarget = e.target;
document.addEventListener('mouseup', e => { // register on DOCUMENT to be sure it will fire even if we release it somewhere else
if (e.target.isSameNode(originalTarget)) handlerFn(e);
}, {capture: true, once: true, passive: true});
}, true)
};
I'm trying to create a file drag/drop handler (drag a file into the browser window, to be used for upload).
For some reason when I bind the drag/drop listener to $("body") instead of to a $("div") in the body the events fire several times in a row, sometimes even non-stop (seemingly looping). What could be causing this?
Here's a trimmed down version of the code: http://jsfiddle.net/WxMwK/9/
var over = false;
$("body")
.on("dragover", function(e){
e.preventDefault();
if (! over) {
over = true;
$("ul").append($("<li/>").text("dragover"));
}
})
.on("dragleave", function(e){
e.preventDefault();
if (over) {
over = false;
$("ul").append($("<li/>").text("dragleave"));
}
})
.on("drop", function(e){
e.preventDefault();
if (over) {
over = false;
$("ul").append($("<li/>").text("drop"));
}
});
To test: drag a file into the orange area, you'll see the event firing multiple times in a row.
The anon is (mostly) correct. To put it simply: when the mouse moves over the edge of an element inside your drop target, you get a dropenter for the element under the cursor and a dropleave for the element that was under the cursor previously. This happens for absolutely any descendant.
You can't check the element associated with dragleave, because if you move the mouse from your drop target onto a child element, you'll get a dropenter for the child and then a dropleave for the target! It's kind of ridiculous and I don't see how this is a useful design at all.
Here's a crappy jQuery-based solution I came up with some time ago.
var $drop_target = $(document.body);
var within_enter = false;
$drop_target.bind('dragenter', function(evt) {
// Default behavior is to deny a drop, so this will allow it
evt.preventDefault();
within_enter = true;
setTimeout(function() { within_enter = false; }, 0);
// This is the part that makes the drop area light up
$(this).addClass('js-dropzone');
});
$drop_target.bind('dragover', function(evt) {
// Same as above
evt.preventDefault();
});
$drop_target.bind('dragleave', function(evt) {
if (! within_enter) {
// And this makes it un-light-up :)
$(this).removeClass('js-dropzone');
}
within_enter = false;
});
// Handle the actual drop effect
$drop_target.bind('drop', function(evt) {
// Be sure to reset your state down here
$(this).removeClass('js-dropzone');
within_enter = false;
evt.preventDefault();
do_whatever(evt.originalEvent.dataTransfer.files);
});
The trick relies on two facts:
When you move the mouse from a grandchild into a child, both dragenter and dragleave will be queued up for the target element—in that order.
The dragenter and dragleave are queued together.
So here's what happens.
In the dragenter event, I set some shared variable to indicate that the drag movement hasn't finished resolving yet.
I use setTimeout with a delay of zero to immediately change that variable back.
But! Because the two events are queued at the exact same time, the browser won't run any scheduled functions until both events have finished resolving. So the next thing that happens is dragleave's event handler.
If dragleave sees that it was paired with a dragenter on the same target element, that means the mouse must have moved from some descendant to some other descendant. Otherwise, the mouse is actually leaving the target element.
Then the setTimeout finally resolves zero seconds later, setting back the variable before another event can come along.
I can't think of a simpler approach.
You are adding a listener on the BODY HTMLElement for the dragover, dragleave and drop.
When you continue to drag over the DIV, there is a dragleave that is fired because the mouse is no more dragging over the BODY, but over the DIV.
Secondly, as you are not stopping the bubble event on the DIV (no listener is set), the dragover fired on the DIV is bubling to the BODY.
If I resume:
The mouse enter the body (in dragover)
--> fire drag over (body)
The mouse enter the DIV in the body
--> fire drag leave (of BODY)
--> fire drag over (of DIV) --> event bubling --> fire drag over (of BODY)
There is a similar problem with mouseover and mouseout, which is fixed by using mouseenter and mouseleave.
May be you can try the same code using dragenter event type. If its not working, you can check if the event.target is the BODY. This test could help to skip undesired drag event.
Good luck
var over = false;
$("body")
.on("dragover", function(e){
e.preventDefault();
if (! over) {
over = true;
$("ul").append($("<li/>").text("dragover"));
}
})
.on("dragleave", function(e){
e.preventDefault();
if (over) {
over = false;
$("ul").append($("<li/>").text("dragleave"));
}
})
.on("drop", function(e){
e.preventDefault();
if (over) {
over = false;
}
});
Or you could just use stop(); to stop animation buildup