33

I have an iframe with an inherited pointer-events: none; but when I have inserted a <div> inside the iframe with a pointer-events: auto; the div won't react to clicks or any mouse hover events.

The reason for this is that the iframe is inside a <div style="position:fixed;"> so it is kind of like a little menu. but I would like the user to click through the iframe, but not through the links and divs in the iframe.

And yes, it absolutely needs to remain an iframe.

How could I bypass this? Can I even bypass this?

Here's a simple example: jsfiddle.net/MarcGuiselin/yLo119sw/2

Marc Guiselin
  • 3,442
  • 2
  • 25
  • 37

6 Answers6

3

I've searched a lot and figured out a workaround myself.

// the iframe element
const frame = document.getElementsByTagName("iframe")[0];
frame.onload = function () {
  const frameDocument = frame.contentDocument;
  document.addEventListener("mousemove", onMouseMove, true);
  frameDocument.addEventListener("mousemove", onMouseMove, true);

  function onMouseMove(e) {
    let coord;
    if (e.currentTarget === document) {
      coord = {
        x: e.pageX - frame.offsetLeft,
        y: e.pageY - frame.offsetTop,
      };
    } else {
      coord = { x: e.clientX, y: e.clientY };
    }

    const el = frameDocument.elementFromPoint(coord.x, coord.y);
    // you can compare to your own element if needed
    frame.style.pointerEvents =
      !el || el === frameDocument.body ? "none" : "auto";
  }
};

The iframe will auto toggle its pointer-events and all events just works seamlessly.

Manas Khandelwal
  • 3,790
  • 2
  • 11
  • 24
troy
  • 387
  • 1
  • 9
3

What we want is not allowed by the spec, at least not with an absolutely positioned iframe.

We're all wanting to do the same thing:

  • Render a container (typically for a Chrome extension), positioned over the page and filling the viewport
  • Disallow not allow the container itself from capturing pointer events
  • Allow the container's children to capture pointer events

This lets us "click through" the absolutely positioned box, but still interact with its children, which may be buttons or just boxes with hover events or whatever.

We can achieve this behavior if and only if the boxes share the same document. Disabling pointer events for an absolutely positioned iframe disables them for all of its children.

An alternative approach is rendering the content directly into the document, i.e. into a Shadow DOM for styles sandboxing. This is the only way to achieve this behavior as we are looking for it, and was how I previously approached the problem before trying to refactor to an iframe and finding it can't be replicated this way.

See https://iframe-pointer-events.vercel.app for a demo.

Lewis
  • 4,285
  • 1
  • 23
  • 36
2

You're working with position:absolute, according to the example you've uploaded on jsfiddle... Adding a z-index to the "you can't click me because i'm behind the iframe" button allows me to click it.

Z-Index

EDIT: If you want to do a pointer-events: none; everywhere except in one div, you can add the pointer-events in each element instead of the iframe. According to your example in the fiddle, if you want to save the Nicholas Cage but block the other events, you can do something like this:

switchbutton.onclick = function () {
  this.innerHTML = "reload to reset";
  //iframe.style.pointerEvents="none";
  cantclick.innerHTML = "You can click me now! :)";
  iframe.contentDocument.body.innerHTML =
    "<button>You can't click me now because my parent iframe is click-through! :(</button><br>or save this gorgeous image of your favorite actor which may or may not be relavant to the problem.<br><img id='nicholasCage' src='http://media2.popsugar-assets.com/files/2014/01/06/008/n/1922283/131b4126c7b8738f_thumb_temp_image333458751389045360.jpg.xxxlarge/i/Best-Nicolas-Cage-Memes.jpg'/>";

  var iframeBody = iframe.contentDocument,
    elements = iframeBody.querySelectorAll("*"),
    i,
    len = elements.length;

  for (i = 0; i < len; i++) {
    elements[i].style.pointerEvents = "none";
  }

  iframeBody.getElementById("nicholasCage").style.pointerEvents = "all";
};

If you can use jQuery you can do it faster just using $("iframe *").css("pointer-events","none");

Manas Khandelwal
  • 3,790
  • 2
  • 11
  • 24
Kamae
  • 541
  • 2
  • 9
  • 19
  • 3
    obviously. the goal isn't to click the button over the iframe, but instead to figure out how to allow mousevents to penetrate through the iframe when the mousevent isn't on something inside the iframe – Marc Guiselin Jul 15 '16 at 11:30
  • Sorry, I think I'm not getting it (my fault 'cos my English sucks a bit...). Ok now I've checked deeply your jsfiddle, when NCage appears, you want to click on the button "You can't click me..."? You can do it using javascript, is that are you asking for? (Apologies again) – Kamae Jul 15 '16 at 11:53
  • OK so you want to disable all the things inside the iframe except (example given) the image of NCage. Are you able to use jQuery or just javascript? – Kamae Jul 15 '16 at 12:06
  • 1
    no this is a css/html thing. for example if i wanted a chrome extension to insert a menu into each page that is inside an iframe(so it doesn't mess up the website it is on) and that menu covers the entire page. if i were to click in the middle of the iframe (where none of the menu is) i would still be clicking the iframe instead of the stuff behind it. you can't do this efficiently with javascript. It would be monstrously complex and wouldn't work on most sites. but if some way you could make the iframe click-through, you could fix all those problems. that is what i am looking for. – Marc Guiselin Jul 15 '16 at 12:55
  • Aaah OK sorry, I didn't understand! Well, in this case I don't know if you can do it just with CSS... I got it using JS. In this example you can click both of them, but it's a bit tricky... https://jsfiddle.net/Kamae/e51zykkh/ it's just making the iframe "z-index -1", getting the mouse position, triggering a click, and making the iframe "z-index 0" again... It's all I can do, sorry if this doesn't help you at all. Good luck! – Kamae Jul 15 '16 at 13:55
  • There are other methods how to not mess with the page content. You can eg superprefix your classes. But i think that this is case for using something like html5 shadow DOM (shadow-root). I haven't worked with it myself though. – actimel Jul 25 '16 at 16:50
0

There's no way you can do it, since it would be another security issue along with clickjacking.

Styles inside iframe don't cooperate in any way with styles inside of host site. So if you even set z-index/pointer-events or something else on iframe and would try to override the rule inside of it, it won't apply to host site rules in any way.

So what's the solution? You have to split your iframe into multiple ones, and tinker with theirs position.

deathangel908
  • 8,601
  • 8
  • 47
  • 81
-3

Have you tried this?

pointer-events: inherit;

I know you already got your answer, but I thought this might work, too.

wingit86
  • 1
  • 2
-3

Don't give the whole iframe pointer-events: none. Just inside the iframe put a CSS rule body * { pointer-events: none;}

This way the iframe does not block events, however elements inside the iframe are not clickable.

O_Z
  • 1,515
  • 9
  • 11
  • 3
    The goal is to figure out how to allow clicks to penetrate through the iframe when the click isn't on something inside the iframe. You want to be able to click on stuff inside the iframe, otherwise that ruins the purpose. – Marc Guiselin Sep 24 '16 at 15:40