Forwarding Mouse Events Through Layers

by Shea Frederick on September 30th, 2009

Anyone who has worked with web apps has likely created a masking element at some point, and the great thing about a masking element is that it intercepts user interaction, letting us create a pseudo-modal user interface. The masking element enables us to mask the entire screen, bringing focus to a particular element, or just create a window like effect. This behavior is demonstrated in the ExtJS libraries Ext.Window when modal is set to true, among other places.

Yeah, so what?

layers-masked1The problem comes when we want to mask a part of the screen, but also want the elements behind that mask to continue responding to user interaction. This behavior is counter to most native behavior. What we are left with, is having to forward mouse events through the masking layer to the layer below, an option that simply does not exist in the standard JavaScript/DOM API.

Why do this?

A plugin that I recently wrote - DataDrop - used a textarea overlaying a grid to act as the receiver of dragged data from spreadsheet type programs. One of the limitations that I was not so happy about was the fact that this overlay could only exist in the 'empty' area of the grid, not over rows of data, headers, toolbars, etc. otherwise it would cause all of those elements below to become unresponsive to user input. If our textarea that receives the data were positioned over the entire grid area, our GridPanel could not be controlled using the mouse anymore.

How to fix this?

This is where my friend Nige White (Animal) came in to help, it turns out that he had an idea about how to forward these mouse events using a couple of not well understood methods in the JavaScript/DOM API used for creating mouse events. Here are the steps taken:

  1. The textarea (my masking element) that is positioned over the grid receives mouseover, mousemove, mousedown , mouseup and other events.
  2. The top masking layer is hidden for a moment, so we can figure out what element is below the mask at the event location.
  3. The event is re-fired - this is where the W3 DOM Event model and the simpler Microsoft equivalent come into play.
  4. Start the process again - ready for the next event.

layers-steps1

What's element to fire upon?

Knowing the cursor position and event that occurred, the only obstacle now is determining what DOM element to fire the simulated event through. It turns out that finding which element is the topmost element at a certain document coordinate position is quite simple in modern browsers. The document.elementFromPoint method is supported in IE, Mozilla, Webkit and Opera.

So it is possible to capture the document coordinates of a mouse event fired on the masking element, momentarily hide that masking element, and then ask the document what is under that coordinate position before showing the mask again.

A new event can be then fired on the found element. Obviously there are some complexities such as firing mouseover and mouseout events when the found element is different from the last time, but that is the gist of the technique. To see it in action, check out the screencast or play with the live Grid DataDrop example or simple DOM element example yourself.

I have posted an override of Ext.Element that Nigel created on my Google Code site for all to enjoy, it is now a requirement for the ExtJS DataDrop plugin. A big thanks goes out to Nige (Animal) for digging into the nuts and bolts of JavaScript to find a solution to my problem.

The Next Step

Having the data that is dropped into the grid automatically insert into the grids rows at the correct position is my next task for the ever-improving DataDrop plugin. Should be fun :/

UPDATE (12-1-09): Seems that Mozilla has also noticed this issue.
27 Comments
  1. This is awesome dude.

  2. morfeo permalink

    The scroll wheel doesn’t work :’(

  3. Animal permalink

    Mousewheel support is being worked on!

    • Mousewheel event is wrongly inplemented in Safari. Oddly they combine W3C and MS implementations. Here is a correction for Safari

      IE also gets it wrong. Wheel moves in multiplum of 120 and direction is reversed -> so multiply by -1 and divide by 40

    • The issues with the Mousewheel that we are trying to overcome right now has to do more with elementFromPoint returning an element that was at the position before the scroll happened, instead of the element that exists at the position after the scroll. Nige is racking his brain for a solution.

      Of course this issue only exists in IE – but im sure you could have guessed that.

  4. I implemented drag and drop using native custom events this way as well.

    Note that Opera implements document.elementFromPoint incorrectly returning a reference to a text node if the target is a text node. Here is a fix

  5. Cool stuff!

    I, for one, am astonished that something like “document.elementFromPoint” even exists. :\

    A useful method supported in all major browsers? You’ve got to be kidding me. :P

  6. Simply brilliant. I had absolutely no idea that “document.elementFromPoint” even existed. Thank you!

  7. Animal permalink

    Thanks for the head-up Dok. Seems like Opera authors don’t know the diff between an element and a node! I’ll take a look at my code.

  8. In WebKit and Firefox, you can simply use the pointer-events CSS property for this :)

    • The pointer-events CSS property is part of the SVG spec, so the fact that works in other situations im just going to attribute to luck.

      Either way, I need to code for all browsers, not just a couple.

  9. Andrea permalink

    Doesn’t work in Chrome, but you say webkit is supported.

  10. Enrique Pessoa permalink

    Hi, Shea.

    I guess Andrea was talking about the mouse weel.
    In fact, it has not worked in Chrome for me.

    Congratulations for the magic.

  11. Someone got an alternative for Flash for the same problem ?

    elementFromPoint does not seem to exist in Actionscript :(

  12. Chris permalink

    Hi, this is great! But im wondering if its possible to position a SWF over an iFrame and still click the links in the on the URL loaded in the iFrame?

    Ive been trying a bit but cant get it to work!?

    All the best!

  13. Dmitriy Pashkevich permalink

    DUDE THANKS A LOT!!! I recently faced this issue and only managed to dig the problem until manually firing events but had no idea how to determine the element at coordinates! This is really awesome!

Trackbacks & Pingbacks

  1. Forwarding Mouse Events Through Layers | VinylFox
  2. Ajaxian » Hide and Seek via Mouse
  3. Hide and Seek via Mouse - Programming Blog
  4. Tweets that mention Forwarding Mouse Events Through Layers | VinylFox -- Topsy.com
  5. Hide and Seek via Mouse | Guilda Blog
  6. Hide and Seek via Mouse
  7. Forwarding Mouse Events Through Layers | VinylFox | My Web Development Bookmarks
  8. uberVU - social comments
  9. Myšlenky dne otce Fura » Blog Archive » Monitoring embeded video plays

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS