Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Problems with reliably retrieving DOM element bounds in FireFox


:P
On this page:

I've been banging my head against the wall again with FireFart (as Peter Bromberg lovingly calls FireFox) DOM programming.

 

What I'm doing is simple enough – I dynamically load up a <div> tag with HTML via innerHTML property and then need to draw a related control with the same dimensions.

 

  var Panel = $(Context.ControlId);

Panel.innerHTML = Result;

 

// incorrect in FireFox, correct in IE

var Height = Panel.offsetHeight; 

 

ShowShadow(Panel.id);

 

where ShowShadow is a function that creates a shadow dynamically for the control. Obviously ShowShadow is critically in need of the proper bounds for the target control and this is where the problem comes in. If you look at the value of Height at the point above it will be the correct height of the newly updated element with the new content in Internet Explorer, and have some stale value from a previous setting in FireFox.

 

Normally the .offsetHeight property should do the trick but FireFox is not behaving. The value returned with .offsetHeight is rarely consistent depending on the style height setting, but even when no style setting is made at all the .offsetHeight is not correct.

 

Interestingly enough the width works just fine – it's retrieved without any issues. It's only the height that has issues.

 

I've fought this particular problem for some time off and on. I get it to work, then I add a little code here or there in the framework code and it breaks again in FireFox.  Just to be sure I'm not completely off my nut I looked at ATLAS and what it's doing to get control coordinates which is pretty much the same I use:

 

function GetControlLocation(element)

{

    var offsetX = 0;

    var offsetY = 0;

    var parent;

   

    for (parent = element; parent; parent = parent.offsetParent) {

        if (parent.offsetLeft) {

            offsetX += parent.offsetLeft;

        }

        if (parent.offsetTop) {

            offsetY += parent.offsetTop;

        }

    }

 

    return { x: offsetX, y: offsetY };

}

function GetControlBounds(element) {

    var offset = GetControlLocation(element);

   

    var width = element.offsetWidth;

    var height = element.offsetHeight;

   

    return { x: offset.x, y: offset.y, width: width, height: height };

}

 

But as expected that doesn't work any better; I still end up with my related controls mis-sized.

 

So in my never ending ignorance of all issues DOM I stumbled onto the solution to the problem by accident. The reason for the behavior FireFox has is that it doesn't update the DOM element properly until after the event or active function call completes and the browser event loop gets control back.

 

To get around this I ended up trying to delay the call to the routine that calls the Shadow rendering. Instead of calling it directly I use a window.setTimeout() call and lo and behold everything starts working correctly.

 

window.setTimeout("ShowShadow('" + Panel.id +"')",50);

 

does the trick in this scenario! On my machine the lowest value I can use is 50 – anything lower and I get spotty behavior where it sometimes works and sometimes not.

 

Since the Shadow generation function requires this behavior consistently I figured I better build support for it into the function itself so it can call itself back:

 

function ShowShadow(CtlId,Opacity,Offset,DelayShadow)

{

   var Ctl = $(CtlId);

   if (Ctl == null)

      return;

 

   if (Opacity == null)

      Opacity = ".35";

   if (Offset == null)

      Offset = 8;

     

   if (TimeFlag != null)

   { // Force a delay to allow target control to resize properly

     window.setTimeout("ShowShadow('" + CtlId + "'," + Opacity.toString() + "," + Offset.toString() + ",null)",50);

     return;

   }

  

}

 

If you think this is ugly as hell raise your hand <g>. But it works. I've had issues with this particular problem on a number of occasions and this explains the funky behavior I've been seeing. Apparently, this problem has gotten worse in a recent FireFox update because I had the original code working previously. It's obviously a timing issue – so it was probably spotty even back then. I suspect machine speed also plays into that - a faster machine might blaze through JavaScript code faster than the DOM is being updated in the background. But this seems one hell of a lame design if that's really the case.

 

In the meantime this hack solves the problem for me. Hope this helps someone out…


The Voices of Reason


 

Peer van der Nagel
May 30, 2006

# re: Problems with reliably retrieving DOM element bounds in FireFox

Rick, I love your comment on FireFart :-)
Peer

Horses
May 30, 2006

# re: Problems with reliably retrieving DOM element bounds in FireFox

An alternative solution is to use the DOM methods, like createElement() and appendChild(). Of course depending upon the number of elements you're adding, it could just be faster to use innerHTML anyway.

Quirksmode has some good speed tests comparing innerHTML to DOM methods: http://www.quirksmode.org/dom/innerhtml.html

And they also mention the innerHTML problem: http://www.quirksmode.org/dom/w3c_html.html (scroll down to innerHTML).

Interestingly enough, he suggests using the same solution you describe when dealing with innerHTML.

Rick Strahl
May 30, 2006

# re: Problems with reliably retrieving DOM element bounds in FireFox

Yeah definitiely using CreateElement() on the dom is better because you know what you're getting. But that won't work with a generic routine.

The routines in question are behavior functions that attach to existing objects such as a ToolTip, and a Fadeout behavior for a control. It'd be tough to generically add elements <g>.

Speaking of which for the tooltip shadow I resorted to just that approach (prior to finding this solution). By using clean elements you apparently get the size set to a nulled out value that will properly be read by FireFox immediately after assignment of innerHTML.

Thanks for the links. I couldn't find anything in my searches yesterday, so I'm glad there is more info out on this mess.

Ben Strackany
May 31, 2006

# re: Problems with reliably retrieving DOM element bounds in FireFox

You could also try using FireBug, it's a FireFox plugin DOM browser/javascript debugger/ajax tracer. It won't solve these issues, but may help you resolve future problems faster.

https://addons.mozilla.org/firefox/1843/

Duncan Simey
September 27, 2007

# re: Problems with reliably retrieving DOM element bounds in FireFox

This describes my element sizing firefox fiasco perfectly!!!!!
Luckily I already know which browser is calling the script, so I can make the delay Firefox specific.
Much thanks - Duncan

P.S. I love FireBug too, but for this bug, simply stopping at a breakpoint allows the div to achieve it's correct height, hence FireBug can only be used to post-mortem the variables, not inspect what is happening in the DOM.

Serg
June 09, 2008

# re: Problems with reliably retrieving DOM element bounds in FireFox

Have you tried using
// incorrect in FireFox, correct in IE
var Height = Panel.offsetHeight + 'px'; 


It worked for me :-)

Liam
August 05, 2008

# re: Problems with reliably retrieving DOM element bounds in FireFox

This issue can be shown in the simplest code:

<html><body>
<img id='i' src='test.jpg'>
<script>  alert( document.getElementById("i").width ) </script>
</body></html>


On first load IE retrieves and fills the metrics for the image from its header straight away - before it is rendered. FF does not. On refresh FF gets it right, but that's a bit late. The issue is not seen if the page is pulled from cache or coded relative positioning is not used.

None of the other DOM metrics are usable either on first load in FF.

The technique of putting in a delay before rendering subsequent/dependent elements becomes complex and clumsy if there are multiple dependencies.

An alternative is to force a refresh of the page if using FF and if the page is on its first load. This requires a timestamp to be added to the location URL. It keeps messy code out of the page body but it is still slightly cumbersome.

In my view the best solution is the use of PHP which has access to the image file and can fill the explicit dimensions automagically, completely avoiding the FF issue. But then, that's PHP and not client side JS.

In the longer term let's hope that FF gets some co-ordinated leadership and action on their key bugs but history suggests we have a long wait.

(Thanks for a really useful site).

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2025