![]() |
|
|
|||||||
![]() |
|
|
Thread Tools | Rate Thread |
|
|
PM User | #1 | |
|
Super Moderator ![]() ![]() Join Date: May 2002
Location: metro DC
Posts: 3,168
Thanks: 1
Thanked 18 Times in 18 Posts
![]() |
elementFromPoint() in gecko
I recently had need for the method document.elementFromPoint() found in Internet Explorer (http://msdn.microsoft.com/workshop/a...tfrompoint.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-Le...-dispatchEvent): Quote:
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. Code:
// 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/a...tfrompoint.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; }
|
|
|
|
|
|
|
PM User | #2 |
|
Senior Coder ![]() Join Date: Aug 2002
Posts: 3,467
Thanks: 0
Thanked 0 Times in 0 Posts
![]() |
Nice work
__________________
My Site | fValidate | My Brainbench | MSDN | Gecko | xBrowser DOM | PHP | Ars | PVP “Minds are like parachutes. They don't work unless they are open” “Maturity is simply knowing when to not be immature” |
|
|
|
|
|
PM User | #3 |
|
New to the CF scene Join Date: Nov 2004
Posts: 2
Thanks: 0
Thanked 0 Times in 0 Posts
![]() |
Fails for textboxes and textareas
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> |
|
|
|
|
|
PM User | #4 |
|
New to the CF scene Join Date: Nov 2004
Posts: 2
Thanks: 0
Thanked 0 Times in 0 Posts
![]() |
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. PHP Code:
|
|
|
|
|
|
PM User | #5 |
|
New to the CF scene Join Date: May 2007
Posts: 1
Thanks: 0
Thanked 0 Times in 0 Posts
![]() |
A more complete version of elementFromPoint
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 www.javeline.net Code:
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;
}
|
|
|
|
![]() |
| Bookmarks |
| Thread Tools | |
| Rate This Thread | |
|
|