...

elementFromPoint() in gecko

jkd
06-15-2003, 11:04 PM
I recently had need for the method document.elementFromPoint() found in Internet Explorer (http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/elementfrompoint.asp).

My immediate method for implementing this was artificially creating an arbitrary mouse event (decided on mousemove) at the specified coordinates, setting up an event listener on the document, firing the event, record the target, and remove the listener.

However, I apparently didn't pay close enough attention to this (http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget-dispatchEvent):

The target of the event is the EventTarget on which dispatchEvent is called.


However, I had a hunch that Mozilla was merely redirecting the event to the document to be correct, and actually stored somewhere the "correct" target. After dumping the event object, I found a property which must have been recently introduced (Mozilla 1.4 branch apparently) called "explicitOriginalTarget" which is like originalTarget, but apparently never targets anonymous content, and in this case, also differs by referring to the element where the coordinates of the mouse event were.

Anyhoo, here's some code. I've tested it in various Mozilla 1.4 builds successfully (and double checked explicitOriginalTarget in < 1.4 builds to find that it doesn't exist). Firebird 0.6 is also based on 1.4, so it works in that too. By using the browser's box object and mouse events, instead of hacking my own, I don't think there should be any issues with it picking up elements that can't be seen.


// Program: document.elementFromPoint(int clientX, int clientY) in Gecko
// Author: Jason Karl Davis (www.jasonkarldavis.com)
// Date: 15 June 2003
// Purpose: Emulate Internet Explorer's document.elementFromPoint method as described here:
// http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/elementfrompoint.asp
// Requirements: A browser built off of the 1.4 branch of Mozilla (or better)
// Distribution: You may freely distribute and use this script as long as these comments remain intact

if (navigator.product == "Gecko") {
Document.prototype.elementFromPoint = function(x, y) {
this.addEventListener("mousemove", this.elementFromPoint__handler, false);
var event = this.createEvent("MouseEvents");
var box = this.getBoxObjectFor(this.documentElement);
var screenDelta = { x: box.screenX, y: box.screenY };
event.initMouseEvent("mousemove", true, false, this.defaultView, 0,
x + screenDelta.x, y + screenDelta.y, x, y,
false, false, false, false, 0, null);
this.dispatchEvent(event);
this.removeEventListener("mousemove", this.elementFromPoint__handler, false);
return this.elementFromPoint__target;
}
Document.prototype.elementFromPoint__handler = function (event) {
this.elementFromPoint__target = event.explicitOriginalTarget;

// reparent target if it is a text node to emulate IE's behavior
if (this.elementFromPoint__target.nodeType == Node.TEXT_NODE)
this.elementFromPoint__target = this.elementFromPoint__target.parentNode;

// change an HTML target to a BODY target to emulate IE's behavior (if we are in an HTML document)
if (this.elementFromPoint__target.nodeName.toUpperCase() == "HTML" && this.documentElement.nodeName.toUpperCase() == "HTML")
this.elementFromPoint__target = this.getElementsByTagName("BODY").item(0);

event.preventDefault();
event.stopPropagation();
}
Document.prototype.elementFromPoint__target = null;
}


And the reason I prototyped Document instead of adding it to document directly is that any extra documents you may have loaded on the same page inherit those methods. For example, an iframe or something. And it /should/ be usable in other, non-HTML documents as well, such as MathML, SVG, XUL, etc. :)

beetle
06-18-2003, 06:26 PM
Nice work :thumbsup:

cknight19
11-29-2004, 10:47 AM
Hi,

The elementFromPoint above works fine on all elements except 'textboxes' and 'textareas'. For these elements it always returns the body object!

Whilst debugging the script I noticed the 'event.rangeParent' object had the correct target, however this object seems to be protected from script access.

Please try the code below as it explains my problem better. I've added some lines to your origonal code that should work for textboxes and textareas however if you look at the JavaScript console you'll see the permission denied error messages. (running Firefox 1.0 on win xp sp2)

Is there any way to fix this problem? Thanks in advance.

<html>
<head>
<title>Element From Point Test</title>
<script>
Document.prototype.elementFromPoint = function(x, y)
{
this.addEventListener("mousemove", this.elementFromPoint__handler, false);
var event = this.createEvent("MouseEvents");
var box = this.getBoxObjectFor(this.documentElement);
var screenDelta = { x: box.screenX, y: box.screenY };
event.initMouseEvent("mousemove", true, false, this.defaultView, 0,
x + screenDelta.x, y + screenDelta.y, x, y,
false, false, false, false, 0, null);
this.dispatchEvent(event);
this.removeEventListener("mousemove", this.elementFromPoint__handler, false);
return this.elementFromPoint__target;
}
Document.prototype.elementFromPoint__handler = function (event)
{
this.elementFromPoint__target = event.explicitOriginalTarget;

if (this.elementFromPoint__target.nodeType == Node.TEXT_NODE)
this.elementFromPoint__target = this.elementFromPoint__target.parentNode;

if (this.elementFromPoint__target.nodeName.toUpperCase() == "HTML" && this.documentElement.nodeName.toUpperCase() == "HTML")
this.elementFromPoint__target = this.getElementsByTagName("BODY").item(0);

//****added this code to check for textboxes and textareas
if ( this.elementFromPoint__target.nodeName=="#document" )//possible textbox or textarea
{
rp = event.rangeParent;
alert("event.rangeParent = " + rp);
if ( event.rangeParent.nodeType == Node.TEXT_NODE )//textbox with a value
this.elementFromPoint__target = event.rangeParent.parentNode.parentNode;
else if ( event.rangeParent.nodeName == 'div' )//textbox without a value
this.elementFromPoint__target = event.rangeParent.parentNode;
}
//****end. However this cause permission denied as the rangeParent object appears to be private!

event.preventDefault();
event.stopPropagation();
}
Document.prototype.elementFromPoint__target = null;
</script>
</head>

<body onclick="alert('elementFromPoint return value = ' + document.elementFromPoint(event.clientX,event.clientY));">

textbox doesn't work <input type=text name=myin id=myin> <br/>
textarea doesn't work <textarea>blah de blah blah</textarea> <br/>

everything else works fine :-

<input type=radio name=myra id=myra>

<input type=checkbox name=mych id=mych>

<select><option name=myop id=myop>blah</option></select>

<p>paragraph</p>
</body>
</html>

cknight19
11-30-2004, 10:28 PM
Hi,

Sorry to keep posting in this forum, however I feel its the rightfull place as it would be confusing to post problems regarding completed scripts in another forum.

I've spent some time trying to answer my own question and stumbled across an even bigger problem with the script for emulating IE's elementFromPoint. The problem is confusion over where the event value "explicitOriginalTarget" actually comes from. JDK's script relies on the explicitOriginalTarget being set by the initiated event in the prototype function, but it is actual set by the user event that accurred prior to the prototype method being called.

For example, if the user clicked on a <p> element and the documents onclick handler called 'document.elementFromPoint(e.clientX,e.clientY)', JDK's method would return the <p> element (correct). However the x,y parameters are irrelevent, no matter what you set them too they will always return the <p> element, because it was the onclick event that set the explicitOrigonalTarget and not the initiated event at coords x,y. The best example of this is to call elementFromPoint from a timer event and try to find the <p> element. Its not possible, you will always get the document returned, however run the same test with IE and you'll get the <p> element.

You might be wondering why this matters and be using this prototype quite happily, in which case there's no problem for you. However I used IE's elementFromPoint to handle a drag and insert operation. For example the user would drag a 10x10pixel div element and insert it into a table cell. When the onmouseup event fired on the div I would call elementFromPoint and offset the event X coord 20 pixels to the right. This would return the second cell in the table. I would then call parentNode (the <tr> element) and then get the first child (the table cell under the div). I would then insert the div into the cell. However the prototype for Firefox failed because no matter how much I offset the X coord the div element would always be return ( as it was the explicitOriginalTarget on the onmouseup event).

The basic upshort of this post is that I spent yesterday trying to find a better solution that more accurately emulated IE's elementFromPoint, and I came up with the relatively simple code below. Recursing through childNodes is very processor intensive however I'm certain its the way forward as initiated events will never work.

I'd appreciate other members thoughts on this and improvements to the algorithm as I'm sure its got flaws and could be faster.

The 'efp' function (elementFromPoint) takes an extra parameter in ( from ). This would probably be 'document.body' however a container element could be passed instead to help narrow down the search. It could be prototyped in the same way as JDK's script as IE would probably ignore the extra parameter.


var g_trg; var g_zIdx = 0;
function efp(x,y,from)
{
if (!from)from=g_d;
efpi(from,x,y);
return g_trg;
}

function efpi(from, x,y)
{
var n,i;
for(i=0;i<from.childNodes.length;i++)
{
n=from.childNodes[i];
if( n.nodeType != Node.TEXT_NODE && n.style.display != 'none' )
{
var sx = getElementPosX(n); var sy = getElementPosY(n);
var ex = sx + n.offsetWidth; var ey = sy + n.offsetHeight;
if ( x > sx && x < ex && y > sy && y < ey )
{
if (n.style.position != 'absolute')
{
g_trg=n;
efpi(n,x,y);
} else if (n.style.visibility=='visible' && (n.style.zIndex &&
n.style.zIndex >= g_zIdx) || (!n.style.zIndex && g_zIdx < 1)
)
{
g_trg=n;
if ( n.style.zIndex ) g_zIdx = n.style.zIndex;
efpi(n,x,y);
}
}
}
}
}


function getElementPosY(myObj)
{
return (myObj.offsetTop + ((myObj.offsetParent) ?
getElementPosY(myObj.offsetParent) : 0));
}

function getElementPosX(myObj)
{
return (myObj.offsetLeft + ((myObj.offsetParent) ?
getElementPosX(myObj.offsetParent) : 0));
}

RubenDaniels
05-24-2007, 02:08 AM
When calculating the element from a point there are some difficulties taking position and overflow into account. I needed this function for drag&drop as well, so I rewrote it to be suited in more cases. Its still a bit slow (110ms on a coreduo 2ghz - depending on your html doc size of course), but posting it anyway. Seems that the first solution on the top of this thread doesnt work anymore. If anyone knows of an alternative, faster (native??) solution for this problem... I'd be gratefull.

Regards,

Ruben Daniels
www.rubendaniels.com (http://www.rubendaniels.com)
www.javeline.net (http://www.javeline.net)


Document.prototype.elementFromPoint = function(x, y){
//define globals
FoundValue = [];
FoundNode = null;

var from = document.body;
efpi(from, x, y, 0, [], getElementPosX(from), getElementPosY(from));

return FoundNode;
}

function getStyle(el, prop) {
return document.defaultView.getComputedStyle(el,'').getPropertyValue(prop);
}

function efpi(from, x, y, CurIndex, CurValue, px, py){
var StartValue = CurValue;
var StartIndex = CurIndex;

//Loop through childNodes
var nodes = from.childNodes;
for(var n,i=0;i<from.childNodes.length;i++){
n = from.childNodes[i];
if( n.nodeType != Node.TEXT_NODE && getStyle(n, 'display') != 'none' ){
var sx = px + n.offsetLeft;//getElementPosX(n);
var sy = py + n.offsetTop;//getElementPosY(n);
var ex = sx + n.offsetWidth; var ey = sy + n.offsetHeight;

//if(Child is position absolute/relative and overflow == "hidden" && !inSpace) continue;
var isAbs = getStyle(n, "position");
isAbs = isAbs == "absolute" || isAbs == "relative";
var isHidden = getStyle(n, "overflow") == "hidden";
var inSpace = (x > sx && x < ex && y > sy && y < ey);
if(isAbs && isHidden && !inSpace) continue;

CurIndex = StartIndex;
CurValue = StartValue.copy();

//if (Child is position absolute/relative and has zIndex) or overflow == "hidden"
var z = parseInt(getStyle(n, "z-index"));
if(isAbs && (z || z == 0) || isHidden){
//if(!is position absolute/relative) zIndex = 0
if(!isAbs) z = 0;

//if zIndex >= FoundValue[CurIndex]
if(z >= (FoundValue[CurIndex] || 0)){
//if zIndex > CurValue[CurIndex];
if(z > (CurValue[CurIndex] || 0)){
//CurValue = StartValue.copy();

//set CurValue[CurIndex] = zIndex
CurValue[CurIndex] = z;
}

CurIndex++

//if(inSpace && CurIndex >= FoundValue.length)
if(inSpace && CurIndex >= FoundValue.length){
//Set FoundNode is currentNode
FoundNode = n;
//Set FoundValue is CurValue
FoundValue = CurValue;//.copy();
}
}
//else continue; //Ignore this treedepth
else continue;
}
else if(inSpace && CurIndex >= FoundValue.length){
//Set FoundNode is currentNode
FoundNode = n;
//Set FoundValue is CurValue
FoundValue = CurValue;//.copy();
}

//loop through childnodes recursively
efpi(n, x, y, CurIndex, CurValue, isAbs ? sx : px, isAbs ? sy : py)
}
}
}

function getElementPosY(myObj){return (myObj.offsetTop + ((myObj.offsetParent) ? getElementPosY(myObj.offsetParent) : 0));}
function getElementPosX(myObj){return (myObj.offsetLeft + ((myObj.offsetParent) ? getElementPosX(myObj.offsetParent) : 0));}

Array.prototype.copy = function(){
var ar = new Array();
for(var i=0;i<this.length;i++)
ar[i] = this[i] && this[i].copy ? this[i].copy() : this[i];

return ar;
}

Marinity
09-22-2010, 07:35 PM
Good Job. I just was looking for that



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum