Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dispatching a mouse event to an element that is visually "behind" the receiving element

Tags:

javascript

css

On my web page I have two divs, A and B:

enter image description here

The DOM looks as follows (simplified). The main thing to note here, is that the divs are on the same level in the DOM hierarchy and they are not ordered by anything. Also, the outer div does not only contain A and B but also more divs C,D,E etc. B may not necessarily be overlapped by A. It could also be C, lying behind A.

<div>
    <div style="...">B</div>
    <div style="...">A</div>
</div>

A click handler is attached to the outer div, capturing mouse click events on A or B in the bubbling phase. A click into the intersection of A and B will cause an click event on A which bubbles up to my click handler that is now fired.

Now the problem: Under certain conditions the handler decides that the event should not be handled by A but should belong to B. If an event should be handled by B, the same click handler will be fired, but the event objects currentTarget property will be B instead of A.

How can I achieve this?

I've already looked at css3 pointer-handler and some event dispatching methods in JS, but could not really come up with a solution here.

Possible solution 1 (not cross-browser compatible) ?:

I think i might be possible to use the pointer-events css3 property. If the handler decides that the event should be handled by B, it sets the pointer-events to none. Then, it retriggers the mouse click. Question here: Is it possible to retrigger a mouse click with only the coordinates, not specifying a specific element?

Anyway, the support of pointer-events is limited: http://caniuse.com/pointer-events

like image 819
Erik Avatar asked Oct 23 '22 04:10

Erik


1 Answers

You can try to pass the event to B (using jQuery):

(pseudo-code)

var $A = $('#A'), $B = $('#B');

$B.on('click', function () {
  console.log('B clicked');   
});

$('#outer').on('click', function (evt) {
    if (Math.random() < 0.1) { // << pseudo condition
        $B.trigger(evt);
    }
});​

http://jsfiddle.net/hzErh/


Update:

using a function like this:

// this function should (did not enough testing) return all elements at a given
// point in the order of top to bottom.
var elementsFromPoint = function (x, y, root) {
  var buffer = [], result = [], el, i = 0, lim;

  while ((el = document.elementFromPoint(x, y)) && [root, document.body, document.body.parentNode].indexOf(el) < 0) {
    result.push(el);
    buffer.push({el: el, visibility: el.style.visibility});
    el.style.visibility = 'hidden';
  }

  for (lim = buffer.length; i < lim; i += 1) {
    buffer[i].el.style.visibility = buffer[i].visibility;
  }

  return result;
};

you can get the elements for a given x/y coordinate. It's a bit hacky and needs more testing though.

$('#outer').on('click', function (evt) {
  var elements = elementsFromPoint(
    evt.pageX + $(this).offset().left,
    evt.pageY + $(this).offset().top,
    this
  );

  console.log(elements);
});

demo: http://jsfiddle.net/hzErh/1/


Update 2: this version should give better performance for many elements:

// this function should (did not enough testing) return all elements at a given
// point, though the order is not representative for the visible order.
var elementsFromPoint = function (root, x, y) {
  var result = [], i = 0, lim = root.childNodes.length, bb;

  for (; i < lim; i += 1) {
    bb = root.childNodes[i].getBoundingClientRect();
    if (bb.left <= x && x <= bb.right && bb.top <= y && y <= bb.bottom) {
      result.push(root.childNodes[i]);
    }
  }

  return result;
};

demo: http://jsfiddle.net/CTdZp/2/

like image 149
Yoshi Avatar answered Oct 27 '22 11:10

Yoshi