views:

102

answers:

4

I am trying to implement a simple fold/unforld effect for elastic div's, i.e. when actual dimensions of an element are not set through CSS and thus are not trivially determinable. So I need equivalents of jQuery's width() and height() for arbitrary elements; the methods should return something that would be assignable to *.style.width/height to achieve the fold/unfold effect. The methods can be setters as well, though not necessarily as it's trivial anyway (or is it not?)

Compatibility: IE8+ and the adequate gang of 3: Firefox, Chrome, Safari. S.O. doesn't have an answer for this other than "use jQuery" which I don't want to do at the moment. The thing is I don't need to support such archaic browsers as IE6/7 and Netscape. I want my code to be just simple, maiden JavaScript for modern browsers. Dropping IE8 for IE9 is an option, too.

I think the answer may be useful to many web developers. Thanks!

Edit: I understand of course, analyzing jQuery (and dropping archaic quirks) is a good option, but I thought someone has done this already and could share with us.

+4  A: 

I think the most practical approach here would be to analyze the jQuery source and build your functions from there.

Moses
+1  A: 

You can use window.getComputedStyle available in the modern browsers you support.
It returns a collection of all real style properties applied to an element.

Here is a description: https://developer.mozilla.org/en/DOM:window.getComputedStyle

Mic
Sounds interesting, although it's not available in IE8, only coming in IE9 (according to my googling). Still something to try, thanks.
mojuba
A: 

element.offsetHeight,element.offsetWidth work pretty much the same across browsers, return integers

element.clientWidth,element.clientHeight return integers, but have some x-browser differences (padding,border width)

The browsers currentStyle or computed style return strings, and they may be in pixels, ems, percents- hard to manipulate x-browser.

kennebec
According to the spec at least getComputedStyle() always returns everything in pixels, although yes, in strings.
mojuba
+2  A: 

Here is an answer to my own question I came up with today after playing with IE8, FF, Chrome and Safari.

Let's say we want to fold and unfold a div element vertically (so we'll be talking only about the height for simplicity) with minimal possible JavaScript and no jQuery. Could be an inline error message box that you want to show and hide nicely on the page. The message box itself is elastic and you don't know its actual dimensions. There are two general problems to be solved here:

Problem #1: offsetHeight, clientHeight and scrollHeight properties are notoriously inconsistent across browsers. You can't rely on them especially when margin/padding/border are set to something other than 0 and when the CSS height is something other than default (i.e. when actually folding/unfolding the element). There is getComputedStyle(), but it's available only on sane browsers and not in IE. OTOH, IE's currentStyle property doesn't return computed values as you might expect (e.g. you may see "auto" as the height value). It turns out, with no margin/padding/border offsetHeight is reliable and compatible on all modern browsers.

Problem #2: the only way (that I know of) to animate your div element is to change style.height, which, however, doesn't include padding and border, and thus you can't animate the box down to 0 or start your animation from 0 for elements with non-zero padding and border. (On top of that, IE doesn't accept 0px as style.height, so the minimal size would be 1px anyway).

The solution to both problems is to wrap your box around with another div with default style, i.e. padding/margin/border all set to 0, which is the default style for a new div. The box you want to fold and unfold should be inside and it shouldn't have a margin setting, only padding and border if desired. This way, you will be able to (1) reliably determine the height of your elastic inner box via offsetHeight, and (2) animate the height of the outer box to and from 1 pixel.

So, let's say we want to animate this:

<div id="errmsg" style="display:none">
    <div class="errmsg">
        <div class="window-close" onClick="javascript:showErrMsg('', this.parentNode.parentNode)"></div>
        <div id="errmsg.text"></div>
    </div>
</div>

The box is invisible in the beginning. The "window-close" class is a small box on the right with a cross which is used to hide the box when clicked. Calling showErrMsg() with empty string folds the message box (see below).

My JavaScript code to animate this error message box is as follows (I'm also animating opacity which is omitted here for simplicity):

var ANIMATE_STEPS = 10;
var ANIMATE_DELAY = 20;


function _obj(obj, parent)
{
    if (typeof obj == 'string')
        obj = (parent ? parent : document).getElementById(obj);
    return obj;
}

function _setStyleHeight(obj, h)
{
    _obj(obj).style.height = Math.round(h) + 'px';
}

function _animateVertFold(obj, cur, by, lim)
{
    obj = _obj(obj);
    cur += by;
    if (by < 0 && cur <= 1)
    {
        obj.style.display = 'none';
        _setStyleHeight(obj, 1); // IE doesn't accept 0
    }
    else if (by > 0 && cur >= lim)
    {
        _setStyleHeight(obj, lim);
    }
    else
    {
        _setStyleHeight(obj, cur);
        setTimeout('_animateVertFold(\'' + obj.id + '\', ' +
            cur + ', ' + by + ', ' + lim + ')', ANIMATE_DELAY);
    }
}

function _expandElem(obj)
{
    obj = _obj(obj);
    if (obj.style.display == 'none')
    {
        obj.style.overflow = 'hidden';
        _setStyleHeight(obj, 1);
        obj.style.display = 'block';
        var innerDiv = obj.getElementsByTagName('div')[0]; // better way?
        var h = innerDiv.offsetHeight;
        if (h > 0)
            _animateVertFold(obj, obj.offsetHeight, h /  ANIMATE_STEPS, h);
    }
}

function _shrinkElem(obj)
{
    obj = _obj(obj);
    if (!obj.style.display || obj.style.display != 'none')
    {
        obj.style.display = 'block';
        obj.style.overflow = 'hidden';
        var h = obj.offsetHeight;
        _animateVertFold(obj, h, - h /  ANIMATE_STEPS, h);
    }
}


function showErrMsg(msg, id)
{
    id || (id = '_errmsg');
    obj = _obj(id);
    if (msg != '')
    {
        _obj(id + '.text').innerHTML = msg;
        _expandElem(obj);
    }
    else
        _shrinkElem(obj);
}

You can call showErrMsg() and the code will take care of the rest. You will have to define the window-close class as something that mimics a GUI close-window button. And of course the "errmsg" class as a box with distinct background color, border and possibly font face as well.

mojuba