I need to capture certain user events (i.e. double-click) but let the rest pass to the iframe below. Some of these events may be enabled/disabled over time. I also don't want to block the iframe from receiving simple events like click or scroll. It seems, however, that iframe gets dibs on both bubble events (makes sense) and capture (this doesn't make sense, as it violates the order of propagation).
It seems like the only way to prevent the iframe from stealing all events is by putting an invisible div above it. In that case, however, I'd need to write handlers for all events to create a fake fall-through to the iframe, because even the events the div doesn't capture will no longer hit the iframe.
I see the following potential problems with this approach:
I may not be able to pass-through/simulate a click event into a foreign iframe (most iframes would be generated via srcdoc, so they'd be local, but some may reference foreign location via src)
I will need to write handlers for just about every mouse event to simulate a pass-through
It may be problematic to send the event to iframe itself and let it resolve the coordinates rather than detecting which element inside the iframe should receive the event
I may be wrong about my assumptions, so feel free to correct me.
Another approach I played around with involves detecting when iframe gets focus:
function clickListener() {
var monitor = setInterval(function(){
var elem = document.activeElement;
if(elem && elem.tagName == 'IFRAME'){
message.innerHTML = 'Clicked';
setInterval(function() {
message.innerHTML = '';
}, 100);
clearInterval(monitor);
elem.blur();
clickListener();
}
}, 100);
}
clickListener();
iframe {
width: 500px;
height: 300px;
}
<iframe id="iframe" src="//example.com"></iframe>
<div id="message"></div>
Problems with this approach:
the 100ms loop isn't ideal when I have 20+ elements on the page doing this
it ignores hover events and lumps all click-like events into a click
These two problems (especially the 2nd) are actually pretty severe show-stoppers, as I want to be able to detect double-click and drag events as well.
Does anyone have suggestions for how to tackle this?
You can check for the click coordinates on the fly and see if they match to your iframe. If they do, then its a double click on iframe, else, let other elements handle the event.
document.addEventListener("dblclick", check);
function check(event) {
if(document.elementFromPoint(event.clientX, event.clientY).id == "message") {
alert('iframe clicked');
} else {
alert('something else dude');
}
}
And obviously, stop iframe from receiving any clicks:
iframe {
pointer-events: none;
}
Here's a fiddle: https://jsfiddle.net/rg59yaau/1/
Related
I have some window 'keydown' event listeners in the main DOM added.
I have also an youtube api iframe in the DOM. When I click on any element in the DOM, all keydown listeners are called, but when I click in the youtube iframe (eg. to volume up), all keydown listeners stop working.
I know that iframe has its own document and window objects and perhaps the browser switches the window and listen the iframe listeners, but is there any way to keep the parent window keyboard listeners working event when the iframe is focused?
There is no way to listen to these events "when the iframe is focused".
However, if you don't really need this iframe has the focus, then you can try to force it not gain the focus.
To do so is a bit hackish, different browsers seems to have different behaviors, but the gist is quite simple:
Listen for your main Window's blur event, and from there, call its focus() method.
As said previously, that's mainly an hack, and requires tweaks for different browsers; Firefox needs it to be called right away, Chrome needs a little timeout (luckily both are not conflicting though).
Also note that this will obviously break YoubeTube's own key listeners and thus their keyboard controls.
Use at your own risk, and be sure to test in various environments:
document.onkeydown = e => {
e.preventDefault();
console.log(e.key);
}
onblur = e => {
if (e.currentTarget === e.target) {
focus(); // FF
setTimeout(focus, 20); // Chrome
}
};
overlay.onclick = function() {
this.remove()
};
#overlay {
position: absolute;
text-align: center;
background: white;
width: 100vw;
height: 100vh;
}
<div id="overlay"><button>begin demo</button></div>
<iframe srcdoc="<input value='can not type here'><button onclick='alert(`clicked`)'>button still works</button>"></iframe>
And as a jsfiddle which allows iframes to YouTube.
I want a click handler that gets fired whenever the user clicks anything on the page. I don't need to know what they clicked on, just that they clicked. (See the note at the end for why I want this.)
Adding an event handler to the document.body works for most pages:
document.body.addEventListener("click", userActivityDetected);
The main thing that doesn't work is if the page has iframes inside it. These are iframes showing content from the same origin (I know that you can't detect anything in cross-origin iframes). If I add the event handlers to each iframe's contentDocument element, I can get the behavior I want. The problem is, my code needs to be generic, so I don't know in advance whether, or how many, iframes a page will have, or even if iframes will be dynamically added after the page loads.
What I really want is a way to add an event listener that applies to the current page and all (same-origin) iframes inside of it. If that's not possible, I'd like a way to detect when an iframe element is added to the page so that I can add the event handler to it.
Why I want this behavior: I want to keep the user's session from timing out if they're "active" on the page. I'm taking clicks and key presses as "active" (I would also be fine with focus changes).
Per #RyanWilson's comment, the fallback solution of detecting when an iframe element is added, and then adding the click handler to it would look like:
function userClicked() {
console.log("user clicked");
}
function addClickHandler(iframe) {
iframe.contentWindow.addEventListener("click", userClicked);
}
// Listen to all the iframes that already exist
for (let iframe of document.getElementsByTagName('iframe')) {
addClickHandler(iframe);
}
// Watch for new iframes
var observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
for (let addedNode of mutation.addedNodes) {
if (addedNode.nodeName.toLowerCase() === 'iframe') {
addClickHandler(addedNode);
}
}
}
});
observer.observe(document.body, {childList: true, subtree: true});
I have a canvas in iframe.html which gets loaded in index.html. Within iframe.html, the game gets keyboard input via window.addEventListener('keypress', press, false);.
This works great. However, if at any time the user presses tab, the iframe loses focus, and can't get it back (even if you click back on the iframe/canvas). Mouse input continues to work- but keyboard is ignored.
One solution I've found is putting this in index.html:
setInterval(function(){document.getElementById("content").contentWindow.focus();},100);
("content" is the id of the iframe).
The problem with this is: 1. it's constantly running this BS code, and 2. I may-or-may-not have control of what goes in index.html in the future (I might be hosting this on third party sites).
So I assume there's a solution that doesn't constantly execute re-focusing code, and doesn't require any code within the iframe-containing web page.
Note- I'm totally fine if "tab" loses focus- but there needs to be a way for the user to re-give the iframe focus.
Within the iframe document, can you add an event listener on the document that calls event.preventDefault(); on keydown? Also, in the same way, could you add a click event listener that calls focus() on whatever you want focused? If it's not a naturally focusable element, add tabindex="1".
There may be some feature of iframes that I'm not considering, but that seems like it should work.
document.querySelector('div').addEventListener('click', ()=>{
document.querySelector('div').focus();
});
document.querySelector('div').addEventListener('keydown', (e)=>{
if (e.key === 'Tab') {
e.preventDefault();
}
});
div {
background-color: red;
}
div:focus {
background-color: blue;
}
<div tabindex="0">I'm bad for accessibility!</tabindex>
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.
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.