Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 6 of 6
  1. #1
    jkd
    jkd is offline
    Senior Coder jkd's Avatar
    Join Date
    May 2002
    Location
    metro DC
    Posts
    3,163
    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):
    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.

    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;
    }
    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.

  • #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”

  • #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>

  • #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:
     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(fromx,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 ( 
    sx && ex && sy && 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));
      } 

  • #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;
    }

  • #6
    New to the CF scene
    Join Date
    Sep 2010
    Posts
    1
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Good Job. I just was looking for that


  •  

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •