On iOS, I want to disable scrolling on the <body> element only, not elements within the <body> element. The following code will disable dragging on the <body> element, but also prevents dragging on child divs:
$("body").bind("touchstart", function(e) {
e.preventDefault();
});
Is there anyway for this to just affect the parent div, not the child div's within the parent (<body>)? I thought maybe $("#yourdiv").parent() would work, but no luck.
EDIT: This link, while it addresses the same issue, did not solve my problem: prevent app dragging but permit scroll of inner div elements in ios
$("body").bind("touchstart", function(e) {
if(e.target.nodeName === "BODY"){
e.preventDefault();
}
});
You could check the target of the event - not 100% sure how this goes on iOS, but I'd assume it should be fine.
Another possibility would be to stop the event from propagating in the first place.
$("*:not(body)").bind('touchstart', function(e){
e.stopPropagation();
});
But this is just a thought, and I'm also not entirely sure it would work.
You could also compare this with e.target.
$("body").bind("touchstart", function(e) {
if (this === e.target) {
e.preventDefault();
}
});
I'm not sure how different it would be on iOS (haven't tested it there yet), but here's how I solved the problem on Android.
JSBIN DEMO
The solution involves a few things:
Disabling default Body touchmove event
Preventing touchmove event from bubbling to body on targets you want touchmove to work on
Forcing overscroll on permitted targets to bubble to body and get negated
Related
My website has lots of elements which trigger ajax data retrieval on click, and some drag&drop elements handled by jquery ui. Many elements use their own click events, attached directly to them. I need some functionality which will ignore all mouse clicks/mouseup/mousedown events temporarily under some predefined circumstances. For example, I want to disable drag & drop entirely until some ajax request is in progress, etc. I thought I would bind on click and preventDefault(), I tried to bind on document, like this, but it doesn't seem to work:
$(document).on("mousedown", "*", null, function(ev){ev.preventDefault();});
I think it's because when the event reaches $(document), it was already triggered on all childs, so it's too late to preventDefault().
One solution I could imagine is to set some global variable, like ignore_clicks=true; and then add to every function which handles mouse click a check if this variable is true or not. This seems very difficult and I'm afraid even impossible due to external click handlers like in jquery-ui code.
Another solution I imagine is to temporarily put some fixed style element, 100% width and 100% height, zero opacity, over the current page, but this doesn't seem like an ideal solution, feels more like a hack. Furthermore if there is any ongoing animation on the webpage while it is covered by transparent element, the performance of the animation is degraded.
Is there any simple and elegant solution which would allow me to temporarily block all mouse clicks on the given page? (mouseup & mousedown too).
One solution is to stop the event in the capturing phase by using addEventListener:
document.addEventListener("mousedown", function(e) {
e.preventDefault();
e.stopPropagation();
}, true /* true == capturing phase */);
Do note that this won't work in IE8.
With jquery you can toggle on/off event handlers:
function doClick(e) {
e.preventDefault(); //Prevent default event.
//do some fancy ajax stuf...
};
//Toggle on:
$(document).on('click', '.clickable', doClick);
//Toggle off:
$(document).off('click', '.clickable', doClick);
This will work with any event such as click/mousedown/mouseup etc.
If you want to prevent all other events from being fired, you could try as follows:
function preventPropagation(e) {
e.stopImmediatePropagation();
};
//Toggle on when ajax:
$(document).on('click mousedown mouseup', '*', preventPropagation);
//Toggle off when ajax finishes:
$(document).off('click mousedown mouseup', '*', preventPropagation);
If you want to prevent all mouse-based interactions, you could place an invisible overlay in front of the dom, e.g.
HTML
<div id="click-blocker" style="display: none;"></div>
CSS
#click-blocker {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
JS
// to disable clicks:
$('#click-blocker').show()
// to enable clicks again
$('#click-blocker').hide()
By showing the click-blocker, all clicks will happen to the blocker, since it's in front of everything, but since it has no content or background, the user will perceive it as their clicks are just not doing anything.
This won't work if you only want to disable a specific type of interaction, e.g. drag+drop as you mentioned.
I do not think having a "processing" animation overlay is a hack and it seems pretty elegant to me. Plenty of applications use this because it gives users feedback. They know that the application is working and they'll have to wait.
<div id="menuContainer"></div>
<div id="menuItemTemplate" class="menuItem">
<div class="menuItemTitle"></div>
<div class="menuItemImage"><img src="resources/BlackRightChevron.png"/></div>
</div>
The menuContainer div is dynamically appended with clones of the menuItemTemplate. The current click event:
menuContainer.addEventListener('click',menuContainer_click,false);
does not fire when menuContainer overflows in the y-axis.
So I implemented some code found else where on stackoverflow.
Which makes it scrollable but the click events do not run (probably because of the preventDefault()s). Without them I figure every event would be registered as a click instead of a possible move.
Oh, I'm using jQuery mobile and it's UI as well.
Is there any solution to my problem?
The changes I made as per the suggestion:
var scrollStartPosY=0;
document.getElementById(element).addEventListener("touchstart", function(event) {
scrollStartPosY=this.scrollTop+event.touches[0].pageY;
event.preventDefault();
},false);
document.getElementById(element).addEventListener("touchmove", function(event) {
this.scrollTop=scrollStartPosY-event.touches[0].pageY;
event.preventDefault();
move = true;
},false);
document.getElementById(element).addEventListener("touchend", function(event) {
if(move)
move = false;
else
menuContainer_Click(event);
},false);
I'm sure the preventDefaults are wiping out your click. In any case you're using click/mousedown/touchstart to scroll exclusively.
What I think you should do is register a touchend event to trigger whatever you intend to have the current click event do. You may want to verify whether there has been a scroll in the meantime and if so, ignore the touchend. That would differentiate between the two separate intentions of scrolling and clicking.
Decided that iScroll was just an easier solution. Though having difficulty with only one div not scrolling completely to the "bottom".
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 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:'';
}
I am developing a site for Apple iPad. In this, how can I apply shadow on mouseover and how to remove on mouseout? Like HTML a process, or any other way available with Javascript, I am using jQuery here.. any advice?
You can try to bind click or touchstart-touchend events. Like so:
//ipad and iphone fix
if((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) {
$(".menu li a").bind('touchstart', function(){
console.log("touch started");
});
$(".menu li a").bind('touchend', function(){
console.log("touch ended");
});
}
Since there's no mouse and no pointer that moves around the screen (except from some jailbreaked iPads, but that's another story), these events are never fired by iPad's Safari.
You can bind the effects to other events (like mouse click) but maybe it's not necessary...
Switch to applying hover effects/shadow on mousedown and mousemove, and use some other event (a timed mousedown/mouseup), or a completely different button or touch location, for "clicking".
Note that some effects may never even be seen if they are hidden under the touch.