I noticed that in Webkit the <button> element does not fire the onclick event when the mouse is moved from/to child elements of the button during the click. With other words: when the mousedown and mouseup events do not happen on the same element - even if both are children of the button.
The same happens when clicking/releasing on/out of the pixels of the button text.
To clarify I made a testcase: http://jsfiddle.net/gx9B3/
It works fine in FireFox. Fails in Chrome 15 and QtWebkit 4.7.1
Is there a way around this? I need a solution specifically for Webkit because my project is targeted to this browser only.
Solution
I could solve this problem based on the method suggested by Jan Kuča (the solution I accepted). Some additional tweaks were necessary, especially introducing a timer to avoid double clicks. Have a look at my fully working solution at JSFiddle:
http://jsfiddle.net/mwFQq/
You could set up a mousedown listener on document.body (to fix the problem on the whole page). You would check if the mousedown event originated from an HTMLButtonElement (or from any of its child elements) and if it did, you set up a mouseup listener (on the button so it does not have to bubble too much) that will check the target property of the mouseup event. If it is contained in the button and is different from the target of the mousedown event, you fire a click event like this:
var e = document.createEvent('Events');
e.initEvent('click', true, true);
button.dispatchEvent(e);
(Do this only for WebKit-based browsers so that you don't get multiple click events in other browsers. Or you could call the preventDefault method of the mousedown event as it should also prevent firing the click event.)
You could try adding this CSS style:
button * {
pointer-events: none;
}
Child elements will then ignore mouse events and the click will come from the button element itself. Here's an example http://jsfiddle.net/Tetaxa/gx9B3/2/
You can also solve it by pure CSS trick. Just place pseudo element over <button> to cover text:
button {
position:relative;
}
button:after {
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
content:'';
}
Related
Is it normal that in Firefox or a previous version of QTWebkit a click event is not fired in these cases:
mousedown on element > mouseup on child element >>> NO CLICK triggered
mousedown on child element > mouseup on parent element >>> NO CLICK triggered
http://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
https://bugzilla.mozilla.org/show_bug.cgi?id=326851
Test URL: http://jsfiddle.net/3d6dzr02/
document.getElementById("test").addEventListener("click",function(){
alert("ok");
})
div {
padding:20px;
background:red;
}
<div id="test">
<div style="margin:20px; background:yellow;">
</div>
</div>
Works well on last chrome version
I don't know if chrome is OK or if it is Firefox that do correctly the spec.
So should it trigger event or not?
Indeed, Firefox behaves differently from IE and Chrome, which fire a 'click' on the common ancestor(?) when mousedown and mouseup happen on different elements. The votes from the browser vendors are:
Mozilla bug 1229143 ("Not receiving click event on the common ancestor of two elements") - no decision made
Chrome issue 484655 merged into 543776 ("Stop sending "click" on mouse drag across elements") - doesn't intend to change their behavior.
IE bug 809003 moved to Edge issue #103724 ("Unexpected Click event triggered when the elements below cursor at mousedown and mouseup events are different") - marked as fixed without any comments (I didn't test if it indeed is)
I think even the latest version of the spec (now called The UI Events) is unclear about this:
The click event type MUST be dispatched on the topmost event target indicated by the pointer, when the user presses down and releases the primary pointer button [...]
You could argue that this implies that the "topmost event target indicated by the pointer" is the same for the mousedown and the mouseup, but it's not clear.
Later in an example there's this text:
trigger a click event [..], since the user has stayed within the scope of the same element.
...which supports the hypothesis that the intention of the spec authors aligned with what was implemented in Firefox.
Problem: Firefox loses the first click event when a textarea has the following CSS:
textarea:focus {
resize: vertical;
}
See demo: http://jsbin.com/wuxomaneba/edit?html,css,output
The solution to this is simple - remove the :focus selector.
However I'd like to know why this happens and are there any other css rules or situations where this can occur.
While this does look like a bug (thanks for filing it!), generally a click event is dispatched to the deepest element that observed both the mousedown and mouseup events. So you can similarly "break" the click event by moving the textarea on :focus (e.g. position:absolute; top: xxx) - this is because the order of events is mousedown->focus (updates the web page)->mouseup->click.
The resize property controls "anonymous content" that's not visible to the web page, but used by the browser to implement the additional UI for the user's benefit. My guess is that changes to this content interfere with click event detection, but without looking at this in a debug build it's impossible to say for sure.
function logEvent(ev) {console.log(ev.type, window.getComputedStyle(document.querySelector("textarea")).resize)}
document.querySelector("textarea").addEventListener("mousedown", logEvent, false);
document.querySelector("textarea").addEventListener("focus", logEvent, false);
document.querySelector("textarea").addEventListener("mouseup", logEvent, false);
document.querySelector("textarea").addEventListener("click", logEvent, true);
textarea:focus{
position:absolute; top: 500px;
/*resize:vertical;*/
}
<textarea>click me</textarea>
This is because when you click on the resize button, you don't focus to the textarea but to the resize button on Firefox. I do not know if this is a bug or not, but it looks like it was intended. Removing :focus removes the requirement of focusing on the textarea, therefore it applies when you hold the resize button, which only goes vertically.
The simplest way is to use jQuery focus. For me it is more simple yet effective and applicable to all kind of browsers.
If you want it focus upon loading the page.
$( document ).ready(function() {
$('#txtId').focus();
});
In a mousedown event-handler of a div another new div is created and appended to the body.
This new div has position:fixed (can also be position:absolute) and has 100% width and 100% height. Therefore it immediately covers the source div which triggered the mouse down event.
Now with the latest Google Chrome (v30), latest Firefox (v24), Opera v12.16 and even with a older Safari v5.1.1 (on Windows) after the mousedown event no click event gets fired on an event listener attached to the body.
Only Internet Explorer (both 9 and 10) does fire the click event on the body afterwards! Why? And how can this be prevented? Is this actually a bug in IE?
The HTML:
<div class="clickme">Click me</div>
The CSS:
.clickme {
background-color: #BBB;
}
.overlay {
position: fixed; /* or absolute */
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: #000;
}
The JavaScript:
$(document).on('click', function(event) {
console.log('body click');
});
$('.clickme').on('mousedown', function(event) {
console.log('div mousedown');
var mask = $('<div></div>');
mask.attr('class', 'overlay');
mask.appendTo('body');
});
Here is a the example with some additional comments: http://jsfiddle.net/Fh4sK/5/
After clicking the "Click me" div, only
div mousedown
should be written to the console, but in Internet Explorer it actually is
div mousedown
body click
I appreciate any help!
EDIT 1:
I found some resources describing the conditions when to trigger a click event:
http://www.quirksmode.org/dom/events/click.html:
"click - Fires when a mousedown and mouseup event occur on the same element."
http://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order
"...in general should fire click and dblclick events when the event target of the associated mousedown and mouseup events is the same element with no mouseout or mouseleave events intervening, and should fire click and dblclick events on the nearest common ancestor when the event targets of the associated mousedown and mouseup events are different."
I'm not 100% sure what the "correct" behaviour now actually should be (maybe IE is the only browser which handles it right?). From the last sentence, it seems that it is correct to fire the click event on the body, because the body is the "nearest common ancestor" of both div elements. There are some other statements on the referenced w3.org page above, which describe the behaviour if an element gets removed, but again I'm not sure if this applies here, as no element gets removed, but covered by an other element.
EDIT 2:
#Evan opened a bug report asking Microsoft to drop the described behaviour: https://connect.microsoft.com/IE/feedback/details/809003/unexpected-click-event-triggered-when-the-elements-below-cursor-at-mousedown-and-mouseup-events-are-different
EDIT 3:
In addition to Internet Explorer, Google Chrome recently started to have the same behaviour: https://code.google.com/p/chromium/issues/detail?id=484655
I bumped into this issue too. I decided I'd make a jQuery plugin to solve this issue and put it on GitHub.
It's all here, feedback is welcome : https://github.com/louisameline/XClick
#mkurz : thanks for finding that W3 directive, you saved me a lot of time.
#vitalets : I solved this issue because I use select2 like you (you led me to this topic). I'll fork the select2 repo and leave a message for the people interested in this.
I'll see if I can ask the Microsoft people to take a look at it and hopefully change that annoying click behavior.
I also struggled with such behavior. I've modified your fiddle to find out how all mouse events are triggered with dynamically created overlay:
http://jsfiddle.net/Fh4sK/9/
So, when mousedown handler of some element shows overlay on the same place where mousedown occured:
Chrome, FF:
mousedown triggered on element
mouseup triggered on overlay
click does not trigger
IE:
mousedown triggered on element
mouseup triggered on overlay
click triggered on BODY(!)
The same behavior if you hide mask in mousedown.
This issue can lead to weird things in IE with modals, masks, overlays etc..
Intresting thing: if you show overlay in mouseup instead of mousedown - everything works
The solution I found is to use mouseup instead of mousedown.
It can't be explained normally because both these events are triggered before click.
In that case all mouse events are triggered on element:
http://jsfiddle.net/Fh4sK/11/
mousedown triggered on element
mouseup triggered on element
click triggered on element
Hope this helps.
You could filter the document click by the target to exclude clicks on that div:
$(document).on('click', function(event) {
if(!$(event.target).hasClass('clickme')) {
console.log('body click');
}
});
If you want to stop bubbling of the click event, try this : (I don't have IE to test)
$(document).on('click', function(event) {
console.log('body click');
});
$('.clickme').on('mousedown', function(event) {
console.log('div mousedown');
});
$('.clickme').on('click', function(event) {
event.stopPropagation();
});
Click, mousedown and mouseup are differents events, independant.
here is a similar question
I am trying to drag an image with Javascript (no libraries). I am able to listen to mousedown and mousemove events. For some reason, I am not able to capture the mouseup event after mousemove. (I can capture mouseup if it is a click but not if it is a drag).
I have tried to listen to the event on document, window, and the image.
Here's the url to my test page:
https://dl-web.dropbox.com/get/Public/move.html?w=74a0d498
Any help on this would be greatly appreciated!
Found the issue, if it is going to be of help to anyone:
I added event.preventDefault(); in the mousedown event and now I am getting mouseup notifications.
Strangely, I've found that when I set my text as unselectable using the below CSS, that inhibits the mouseup event from firing as well -- perhaps this will help someone else.
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
I have seen the mouseup event not fire on my target div because its parent div began consuming drag events. I clicked my target div and this caused my mousedown and mousemove handlers to run. I was expecting to see a mouseup but I did not.
In my case, I had a parent div living beneath my target div in the same location where I launched my mouse button click, and instead of bubbling up the mouseup event on my document once I let go of the left mouse button, I instead got a dragend event fire on its parent div.
The solution for me was simply to set user-select: none; CSS property on the parent div which houses my target div and to make sure I set user-select: text on my target div. This property seems to disable dragging for my parent div and because it has stopped consuming drag events, my mouseup event now properly bubbles its way up.
I presume the reason why this might happen is because the browser starts thinking your mouse move is actually part of a drag event on another element, at which point it seems to stop reporting the standard mouse events and switches to drag events instead.
I was running into this exact same issue! Adding event.preventDefault(); worked for me but I was forced to add it to both the mousedown and mousemove functions.
Neither of the above answers reliably worked to ensure a mouseup event.
Here's what I discovered works consistently:
document.querySelector('html').addEventListener('mouseup', function (e) {
console.log("html mouseup");
var evt = document.createEvent("MouseEvents");
evt.initEvent("mouseup", true, true);
document.getElementById('drag-me').dispatchEvent(evt);
});
If mouseup fires on target element, it does not fire on html, and if it did not fire on target, it will fire on html.
For above two methods:
add e.preventDefault() in mouseup event
add user-select: none CSS rule
Tried on Chrome 87 but both useless. Finally I add a additional mouseout event listener and it fired when drag out of element.
The event which you want to fire in mouseup , You can fire in mousedown and inside the function write event.stopPropagation().
It will stop the drag event.
It is going to be of help to anyone:
I recommended you use window instead of document when adding Event mouseup
The problem is in selection. Some elements are draggable, when user - having unknowingly uncollapsed selection (likely across many non-text elements), starts moving his mouse with mouse button pushed down he drags these elements alongside the movement of his mouse.
Try setting draggable="false" on html elements where mousedown event starts or if it doesn't work try clearing or collapsing selection on mousedown.
Clear:
getSelection().setPosition(null);
A bit cleaner, collapse:
const selection = getSelection();
selection.setPosition(selection.anchorNode, selection.anchorOffset)
Setting draggable="false" worked for me when working with mousedown event starting with an <img> element and clearing selection worked when I had selections spanning over multiple elements on a control panel an this made some input[type="range"] elements unusable (click event worked but not thumbnail dragging).
I'm having trouble finding a solution to this via google but I would have assumed it would be quite a common problem. I have a div which I have applied an onmouseout event handler to (the handler is used to roll a menu up using jquerys "slideup" function, as I would like the menu to be hidden when the mouse leaves). Problem is that the child elements of that div also cause the handler to fire (I accept that this is by design due to the nature of the bubbling event model). Now what I would like to know is what is the best way to ignore these events that are triggered by the divs children and only roll the menu up when the mouse leaves the div the event is applied to.
Thanks
What you are looking for is mouseenter and mouseleave.
A good example can be found at this link (they have compared both mouseenter and mouseover)
http://docs.jquery.com/Events/mouseover
A blog entry
http://blogs.oracle.com/greimer/entry/mouse_over_out_versus_mouse
You might want to attempt cancelling the event bubbling or propagation. Quirksmode.org has a handy section explaining how to turn off bubbling or propagation in both the models.
Since JQuery presents the W3C standards to developers, you will not need to set the cancelbubble property. Calling stopPropagation() method will be enough.
Use the "mouseenter" and "mouseleave" on the parent div instead - the child elements will not effect the "mouseleave"
document.getElementById('Main_Menu_Container_Div').addEventListener('mouseenter',function(){
// do something like your 'slideup' or what ever you want.
});
"Event bubbling" as it is known, is the problem - "event on element2 takes precedence. This is called event bubbling". http://www.quirksmode.org/js/events_order.html
Use Example:
<style>
.Child_Div_Button{
float:left;
cursor:pointer;
height:100px;
width:100px;
background-color:#CCC;
border:#000 thin solid;
margin:2px;
padding:2px;
}
.Child_Div_Button:hover{
background-color:#FFF;
}
</style>
</head>
<body>
<div id="Parent_Div_Container" style="height:300px; width:300px; border:#333 thin solid; background-color:#D2FFFF;">
Parent Div
<div id="button_container">
<div class="Child_Div_Button">
Button 1 Child Div
</div>
<div class="Child_Div_Button">
Button 2 Child Div
</div>
</div>
</div>
<script type="text/javascript">
document.getElementById('button_container').style.display = 'none';// initiate
document.getElementById('Parent_Div_Container').addEventListener('mouseenter',function(){
document.getElementById('button_container').style.display = 'block';
});
document.getElementById('Parent_Div_Container').addEventListener('mouseleave',function(){
document.getElementById('button_container').style.display = 'none';
});
</script>
Seems to work in latest Firefox WITHOUT the need for JQUERY - But Chrome, IE and Safari, all seem to need the jquery library for this to work. Is that to say that mozila now fully supports the use of mouseenter and mouseleave?!?! If so .. YAY! for the browser that actually does things to help developers : )
Simon you can check who has trigged the event using jquery Event.target property.
No, it's not by design, you've accidentally applied your 'onmouseout' to too many divs. You only want to apply it to one.