I have a full-screen transparent canvas covering my web page. On the canvas, I render an interactive object. There's a problem: elements (e.g. links) below the canvas do not respond to mouse clicks.
The obvious solution, which I would normally use, is to apply pointer-events: none to the canvas. This will allow clicks to pass through. However, this doesn't work in this situation, because I want the interactive object to be clickable.
So here's what I want to do:
The canvas should retain mouse-click events. IF the event is NOT over the interactive object, it should pass the event to the elements on the other side of the page.
How can I do this?
Found a really nice solution that I thought I should share in case anybody else has the same question.
I used pointer-events: none on the canvas. I set canvas.onclick and canvas.onmousemove like I normally would; however, pointer events are disabled so nothing happens. I bypassed the disabled pointer events like this:
document.addEventListener('click', function() {
canvas.onclick();
});
window.onmousemove = function() {
canvas.onmousemove();
}
// etc.
So far, mouse events will be received by both the web page AND the canvas.
Now, in my interactive program, I included a simple little function called "mouseOver" which returns true if the mouse is hovering over the interactive object. I modified window.onmousemove like this:
window.onmousemove = function() {
canvas.onmousemove();
if (mouseOver()) {
canvas.style["pointer-events"] = "auto";
} else {
canvas.style["pointer-events"] = "none";
}};
This prevents mouse events from going through to the web page, allowing interaction with the object without webpage interference.
Every event that is captured goes through two stages,
1) capturing (where it is propagated to the children)
2) bubbling (where it is sent back up to the parent)
by default, capturing is disabled. you can you use addEventListener("click", function(){blah blah blah}, true) which would pass the event to its children.
in the child element, you can handle the event as you wish.
Here's a sample code that i created by editing http://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_element_addeventlistener
<!DOCTYPE html>
<html>
<body>
<p>This example uses the addEventListener() method to attach a click event to a button.</p>
<div id="myBtn">
<button id="myBtn2">Try it</button>
</div>
<p><strong>Note:</strong> The addEventListener() method is not supported in Internet Explorer 8 and earlier versions.</p>
<p id="demo"></p>
<script>
document.getElementById("myBtn").addEventListener("click", function(){
if(true){
document.getElementById("demo").innerHTML = "Hello World";
event.stopPropagation();
}
}, true);
document.getElementById("myBtn2").addEventListener("click", function(){
document.getElementById("demo").innerHTML += "Hello World2";
});
</script>
</body>
</html>
In this case, parent div captures the event and if it acts on it, it stops propagation. otherwise it just sends it to its child who is in-turn listening to it. Hope this helps
Source - http://javascript.info/tutorial/bubbling-and-capturing
I believe that the answer to your question lies in bubbling and capturing. Bubbling is when your click event passes through to the element's parents, and capturing is the opposite. I advise you check out this link to see how these processes work --> http://javascript.info/tutorial/bubbling-and-capturing
I am not 100% sure on how to implement this through jQuery though.
Related
When my overlay comes up, everything works well, but I added some code to close out the overlay, but this code gets triggered even when I'm just clicking my arrows. The following is the code that's being triggered, which is fine when I'm not clicking the arrows to change the image. But when I click the arrows, the background which is the overlay is also being trigger, so the image is changing but the overlay is also hiding.
$('#overlay').click(function() {
$(this).fadeOut('slow');
});
How can I be able to use the arrows without it also clicking on the background overlay? If you open up the project, you will see what I'm saying.
To open the project:
https://htmlpreview.github.io/?https://github.com/rodriguesandrewb/photo_gallery_v1/blob/master/index.html
To open the repository:
https://github.com/rodriguesandrewb/photo_gallery_v1
You want to use event.stopPropagation(): https://api.jquery.com/event.stoppropagation/
This prevents the event from bubbling (being triggered by other elements)
Your outter most element is #overlay. It means that no matter where you click you'll be always clicking on your #overlay element. That is way your callback is being always triggered and closing your image.
To fix your problem and make your image close only when clicking on it you could use:
$('#changeImage').click(function() {
$(this).closest('#overlay').fadeOut('slow');
});
Ok, there's a ton of code to sort out, so I'm guessing your overlay is
<div id="overlay" style="display: block;"></div>
and your event.target is deep down inside this:
<div class="mainCenter">
<div class="container">
<div id="topFixed">
<input type="text" id="search" placeholder="Search">
</div>
<ul id="gallery">
.......
I'm not 100% sure where your event.target is, (the element you want to click and not everything else). But it's safe to assume that after you click your intended button, the event continues to bubble up the event chain. The event chain is basically your event.target's ancestors which includes#overlay` which is at the very top of the event chain.
To prevent event bubbling (btw bubbling is the default behavior but in instances such as your's it's not desired.) try placing stopPropagation() after or inside at the end of your event handler.
I wish I could be more specific as to where and how to apply this code as it pertains to your source, but you didn't provide the specific areas that concern your eventListeners, eventHandlers, etc...
The #overlay is used in this example but I suggest you use the event.target parent instead. The purpose of this code is to accept an event like 'click' on an element (i.e. button) or multiple elements (i.e. buttons) through their mutually shared parent. That's one place to click for potentially several different buttons. At first you'd think that's non-sense and you'd say, "Sure that button is clicked because the parent was clicked, but now everything the parent is chained to will trigger everything else."
That would be correct except we have stopPropagation(); at the very end of your eventHandler. That will stop propagation of the event bubbling back up the event chain, so there's no more rogue triggers lighting up everywhere. Rogue Triggers® sounds like a great band name. :P
For details and a much better explanation: http://www.kirupa.com/html5/handling_events_for_many_elements.htm
var overlay = document.querySelector("#overlay");
theParent.addEventListener("click", doSomething, false);
function doSomething(e) {
if (e.target !== e.currentTarget) {
var clickedItem = e.target.id;
alert("Hello " + clickedItem);
}
e.stopPropagation();
}
I have an app that uses my own approach to SVG buttons, which required some hackery to get to work but I've liked how it works. However when I add jQuery Mobile to the project, my buttons are no longer responding to clicks or touch.
My buttons are not <button> elements, but <object> tags that link an external SVG file. I have code to hook these up like so:
function buttonifySVG(id, clickHandler) {
var obj = document.getElementById(id);
var svgDoc = obj.getSVGDocument();
function addClickHandler() {
svgDoc.removeEventListener('touchstart', clickHandler);
svgDoc.removeEventListener('mousedown', clickHandler);
svgDoc.addEventListener('touchstart', clickHandler);
svgDoc.addEventListener('mousedown', clickHandler);
}
addClickHandler();
obj.addEventListener('load', addClickHandler, false);
}
Here's a sample "button":
<object id="stepForward"type="image/svg+xml" data="stepForward.svg"></object>
And just to be completely clear:
...
buttonifySVG('stepForward', function() { doTheThing(); })
I can confirm with logging that the buttons are still being hooked up by this code but that the passed in clickHandler is never called. Beyond that, poking around in jquery-mobile.js, looks like there's at least one place where clicks are being intercepted and stopped, but I can't tell when, and more to the point, I'd rather not start hacking jquery code to get things to work.
Can anyone tell me what's likely the problem? I may be able to hack around it if I know what's going on here.
Also, does jQuery Mobile do anything special with <object id="myButton" type="image/svg+xml" data="foo.svg"> elements? This approach has so many nice features I'd really like to get it to play well with jQuery Mobile -- the solution I'm seeking is not to replace my smart buttons with jQuery SVG icon buttons (though I plan to use those for other parts of the UI).
Thanks for any help!
I'm not sure what exactly in JQM is causing the problem described, but I was able to get my code to work with a little modification:
function buttonifySVG(id, clickHandler) {
var obj = document.getElementById(id);
function addClickHandler() {
var svgDoc = obj.getSVGDocument();
var rect = svgDoc.getElementsByTagName('rect')[0];
rect.removeEventListener('touchstart', clickHandler);
rect.removeEventListener('mousedown', clickHandler);
rect.addEventListener('touchstart', clickHandler);
rect.addEventListener('mousedown', clickHandler);
}
obj.addEventListener('load', addClickHandler, false);
}
This relies on the fact that I authored the SVG images myself to have a single rect element as the top-most object for simple mouse/touch events. Not sure if there is a more generic approach that would work, depends on how the SVG is made. Whatever JQM is doing seems to be blocking events where the target is the SVG document itself, but not blocking events within that document. I have noticed a new bug with my code on mobile devices where I'm getting 2 touch events for each button touch, which may or may not be due to the above code...
How can I capture a click or mousedown event on a div surrounding an iframe. I've tried attaching the function to click event on the div but since the iframe never bubbles the event up to the surrounding div the function is never called. Is there a way I can capture the event on the div and then propagate it to the iframe for default action?
If the click is in the iframe area, the iframe context handles the click event, it does not bubble up to the iframe parent. So the div will never register the click event at all if it happened in the iframe area.
Furthermore, if the iframe contains a page that does not belong to the same domain as the iframe parent, any interaction is prohibited (re. same origin policy).
When the same origin policy is met, there are a few things you can do, you could call a method in the iframe parent context:
top.parentFunction();
So in the iframe you add an event listener that delegates to the iframe parent (accessible with the top reference.
Propagating events is a lot more complicated, so I'm simply going to refer to Diego Perini's NWEvents library. I believe his event system to be one of the better ones out there and he's particular on iframe interaction.
I certainly would not start writing your own code to achieve this, this can easily be a year long project if you want to do it properly and even then will be inferior to Diego's work.
There's no "good" way to do it, but if you really need to detect a click on an Iframe, you can kind-of do it in the latest browsers.
<iframe src="http://mtw-ed.com/" id="iframe" style=""></iframe>
<script type="text/javascript">
var inIframe = false;
function checkClick() {
if (document.activeElement
&& document.activeElement === document.getElementById("iframe")) {
if (inIframe == false) {
alert("iframe click");
inIframe = true;
}
} else
inIframe = false;
}
setInterval(checkClick, 200);
</script>
This script will check every 200ms whether the user is in the Iframe. Of course, they may not have clicked on the Iframe to get there, but I'm afraid this is the best you can do without #BGerrissen's solution.
It will detect the first 'click' only, unless you click out again. It only works in really modern browsers.
You can use a library like porthole to pass messages between parent and iframe, even across domains. Using this wouldn't exactly propagate the event (you won't be able to get the event object), but you can create your own event in the form of a simple message, and then handle it in the parent as a click.
Here's their example
However, I've used Brendon's answer as it's simpler works for my current need.
If you land here because you need to track a click on a PayPal button (like me), and you have access to the JavaScript SDK, you can listen to the click by adding the onClick callback in the initialization.
Example:
paypal.Buttons({
onClick() {
// here you can track the click
}
}).render('#paypal-container');
Link to the docs: https://developer.paypal.com/sdk/js/reference/#link-oninitonclick.
I have designed a website with a menu that is initially invisible. When the user clicks on a button, the menu becomes visible. There are two ways for the user to hide the now visible menu:
Click the button that caused the menu to become visible
Click anywhere on the web page that isn't the menu
The way I have coded the second option is to tie an onclick event to the window element, and have it compare where the user clicked to the menu's position to determine if the menu should be hidden. This works great in Firefox and Safari, but it fails in Mobile Safari.
I noticed that the window onclick event only fires when I click on another element with an onclick event already assigned. If I click on an element with no event(s) assigned, the window's onclick event never fires. If I click on the button which displays the menu, it fires along with the event tied to the button.
Is it possible to assign events to the window element in Mobile Safari?
I'v been encountering this same problem. Here is what worked for me. (Note: I am working within a Modernizr and jQuery context)
First, I add a custom Modernizr class using Modernizr's addTest Plugin API to test for iOS, which will add the class appleios or no-appleios accordingly.
Because in my research the body seems to fire events on it's own agenda, I am taking a little precaution by wrapping all the document's content with an element in an iOS context. Then I add an event handler to this element.
$(".appleios body").wrapInner('<div id="appleios-helper" />');
$("#appleios-helper").bind("mouseup", function(){return;});
What was suggested earlier in this thread is using void(0). I did some quick testing, and found that void(0) as the event just wasn't causing touches on the body to be recognized. When I plugged in my own "empty" function in the form of function(){return;} things started working.
This all hinges on the fact that no events are fired in Mobile Safari unless the element explicitly has events to fire (Safari Web Content Guide.) By inserting this empty event on the wrapper, things will bubble up to the body.
If you're doing strait JavaScript with none of these libraries, the same effect could be achieved in the HTML markup
<html>
...
<body>
<div id="appleios-helper" onmouseup="function(){return;}">
...
</div>
</body>
</html>
This worked for me to hide tooltips when touching anywhere on the document's body. Your mileage may vary.
Simply adding the dummy onclick handler to the html body works for me:
<body onclick="void(0)">
Note that I am using usual live event handlers as shown below:
function liveHandler( event ) {
var target = event.target; ...}
window.addEventListener(evtype, liveHandler, true);
// evtype such as 'mousedown' or 'click'
// we use the capturing mode here (third parameter true)
This is an old question, but I struggled with the same thing today.
I found that using touchstart event works.
I solved it like this:
var isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
// Do touch related stuff
$(document).on('touchstart', function (event) {
// Do stuff
});
} else {
// Do non-touch related stuff
$(document).on('click', function () {
// Do stuff
});
}
You could just add onclick="void(0);" to some <div> that covers the whole page so that no matter what, you are always clicking on an element that has an onclick event. Not a great solution, though.
I'd prefer not having the onclick event be tied to the window. Why don't you create a container <div> that has that event on it. Then handle it just like you currently are.
You can also:
$('body').css('cursor', 'pointer');
No idea what those "engineers" at Apple are doing. LOL.
This has problems though. You wouldn't want to do this on every touch device. Only touch devices that don't also have a pointing device (Laptops with Touch Screens, for example).
Source: http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
The conclusion of the article is this:
So I don’t understand why all this is the case, but it most certainly is the case. If you’re having bubbling problems, just add an empty-function event handler anywhere between the body and the element, and you’re set to go. But it shouldn’t be necessary.
I have an image that I want to have trigger certain behaviors when the mouse is over, I have a mouseover and mouseout method, but if you happen to have your mouse over the image when the page loads, the mouseover method never fires until you leave the image and come back over it.
Is there a way to detect if the mouse is over an element on the fly without the mouse having to be off of the element and then come over the element to trigger the JS mouseover event? Like is there a document.getElementById("blah").mouseIsOver() type function in Javascript?
I believe this is possible without any action from the user. When your page loads, bind the mouseover event to your image and hide your image (i.e. using CSS display:none). Use setTimeout() to show it again in a few milliseconds (10 should be enough). The even should be fired.
If you don't want to cause the 'flick' effect on your image, you may try using some temporary element instead, attaching event to it, and delegating the event onto your image.
I have no idea if this is cross-browser solution, but it worked from my Firefox 3.0 console ;)
You could use the mousemove event. That would trigger anytime the user moves a mouse; so the only instance of the trigger not firing would be if the user does not move the mouse at all, which should be rare.
The only problem with this is that the event would fire anytime the mouse would move over your image, so you would get a LOT of those events while over the component. What you would probably need to do is implement some sort of flag within your method when the event fires. You turn on the flag when the event first fires, and you turn it off when you leave the component.
This is less than ideal, but I think this will probably satisfy your problem scenario. The following is some quick pseudo code on what that solution might look like, I think it should work.
<img src="blah.png" onmousemove="JavaScript:triggerOn(event)" onmouseout="JavaScript:triggerOff(event)"/>
...
<script type='text/javascript'>
var TriggerActive = false;
function triggerOn(e){
e = e||window.e;
if( !TriggerActive){
TriggerActive = true;
// Do something
} else {
// Trigger already fired, ignore this event.
}
}
function triggerOff(e){
e = e||window.e;
if(TriggerActive)
TriggerActive = false;
}
</script>
You can find some great mouse event information including browser compatibility notes here.
Use document.querySelectpor and onload/onready events.
var a = document.querySelector('#a:hover');
if (a) {
// Mouse cursor is above a
}
else {
// Mouse cursor is outside a
}
There is no way to get the mouse coordinates aside from listening for mouse events, namely mousemove, mouseover etc. However, these events are very sensitive in the sense that moving the cursor by just one pixel is enough to trigger them, so having the cursor hover over your image while perfectly still should be somewhat unusual.