homepage Welcome to WebmasterWorld Guest from 54.166.65.9
register, free tools, login, search, pro membership, help, library, announcements, recent posts, open posts,
Become a Pro Member
Home / Forums Index / Code, Content, and Presentation / CSS
Forum Library, Charter, Moderators: not2easy

CSS Forum

    
Position-fixed in IE6 with "expression()" and no jittering
Achernar




msg:3592526
 2:16 am on Mar 6, 2008 (gmt 0)

Position fixed in IE6 with "expression()" and no jittering

Why and how...

We know that there is a solution for the lack of position:fixed in Internet Explorer 6. It consists in some javascript code placed in a style rule thanks to a IE-only feature: one can declare style using the expression() operator/function, and fill it with javascript code.

If you want a div to stay at the top of the window the style would look like this:
#divFixed { 
position: absolute;
top: expression(0+((e=document.documentElement.scrollTop)?e:document.body.scrollTop)+'px');
left: expression(0+((e=document.documentElement.scrollLeft)?e:document.body.scrollLeft)+'px');}
}
(The test on "document.documentElement.scrollTop" makes this code compatible in both standard and quirks modes)

Those who have tried this method know that, when you scroll the page, the fixed element will jitter and jump from a scrolled down (or up) position, to its correct (fixed) position. This is due to IE scrolling the page, updating the display, and only then, calculating the new values of all the expression().

Recently I've read a technical article dealing with the method (sorry, I don't remember the site's URL). The writer was lamenting on the fact that, unfortunately, we'll have to live with the jittering effect since it's the closer we can come to a working solution to position:fixed. In a follow-up comment he put forth a possible solution not working for him (and for me when I first tried) : someone told him that having a background image fixed on the body element was getting rid of the jittering.
In fact, this solution really works. When the background image is fixed, IE is forced to process the "layers" (bdcolor+bgimage+tags) before displaying the page, and the end result is that the page is refreshed with the expression() rules already updated.

Technical possibilities

For pages without background image, the fix is easy: add a transparent, fixed, background, and you're done. For pages that already have a background image: if it is fixed, no need for a fix ; if it's not fixed, you'll have to make it fixed. This is not always a good solution. No everyone wants the background image becoming fixed as a side effect of a "dirty" fix.

Since BODY has a parent element (HTML), I've tried applying a background to it. It has the same effects as a background on BODY, with some nuances:

  • the background image doesn't need to be fixed to trigger anti-jittering.
  • you can't specify a background color (other than transparent) or the anti-jittering trick won't work.
  • if you set a background (color or image) to the HTML tag, the margin on the BODY tag will behave like in other tags. That is, margins are not painted with the background color or image (of the BODY), and the background-attachment coordinates (0,0) are aligned with the margins.


    Notes on quirks mode:

  • IE6 handles HTML and BODY (almost) as if they were a single tag.
  • setting a background image on HTML does not trigger the anti-jittering effect.
  • No transparency is applied to BODY to allow the display of the HTML element underneath.
  • consequence, if you apply a transparent image (spacer) to BODY, IE won't display the background of HTML.

    Knowing that, the only possible fix for documents in quirks mode is to apply "document-attachment: fixed" to BODY, and set a transparent background image if none is set.
    Also, the content of expression() is parsed differently: () inside a 'string' are interpreted as real javascript (). Which cause a parsing error. This could be overcome if the entire script code is enclosed inside a (function(){})() .

    css: expression('someString with ()'...);// fails in quirks 
    css: expression((function(){'someString with ()'...})());// works

  • Back to standard mode

    Possibilities:

  • if a fixed background image is set, do nothing
  • if no background image is set, set a spacer.
  • if the background image isn't fixed, move its declaration to the HTML tag.

    We still have a problem if we need the background color and background image to be seen simultaneously (partially transparent image, or image not repeated in a direction and relying on the bg color to fill the gap). Since HTML can't have a background color, after the fix has been applied the background color of the page is white (or the default background color set in the display settings in Windows).

    If we use margin:0px on the body element, we can put a background on the HTML element and keep the normal background on the BODY element without it being moved by the margin. In this case BODY background doesn't need to be modified ; adding a background image to HTML is enough to trigger the anti-jittering effect.
    Example page available...

    Houston, we have a problem...

    However, applying this solution triggers a side-effect. If the document height is smaller than the available space in the browser, the body's height won't span the entire viewport's height, and its background will only be seen on the area that IE thinks is used. We can apply height:100%;, but, if the body has padding and/or border, 100% will be too much and scrollbars will appear.
    Once again expression() comes to the rescue (in standard mode).

    height:
    expression((function(){with(document.body.currentStyle)document.documentElement.clientHeight -((parseInt(marginTop)¦¦0) +(parseInt(marginBottom)¦¦0)+(parseInt(borderTopWidth)¦¦0) +(parseInt(borderBottomWidth)¦¦0) +(parseInt(paddingTop)¦¦0) +(parseInt(paddingBottom)¦¦0))+'px'})());

    Example page available...

    Remarks on expression():

  • We have to ensure that the enclosed javascript code is completely evaluated. Sometimes it is easier to use the (function(){})() syntax (proven by the note on IE6's handling of '()' in quirks mode). IE seems to parse the whole expression before running it, and will refuse to run it if something "seems" wrong. More on that below.
  • Sometimes IE blocks until you prefix a string with a
    ' '+
    (space). This is why you'll see this syntax used. Adding the space directly inside the string might work, but usually you'll need the concatenation.
  • I have had problems when ending an expression with
    &&(this¦¦that)
    even if
    this¦¦that
    is true. If I reorder and end the expression with
    &&(here==there)
    it works.
  • One can set expression() on elements from inside javascript, and so, also from inside an expression(). This will help us in allowing "the fix" to add expression() on other DOM elements when needed. However we need to enclose it inside a setTimeout().
  • We can use this to set an expression to pass modifications of BODY bg position to the HTML tag, and to correctly set BODY height=100%.
  • IE refuses to let you set style on the element targeted by the current expression() rule (element which can be accessed from inside the expression with "this"). One can bluff it by using eval() and setTimeout().
  • I've seen some cases where
    (this!=that)
    is not accepted but
    !(this==that)
    is, and vice versa.

    The expression is applied to HTML. I think it's the tag with the lower risk of style's declaration collision.
    The current URI for the spacer is /s.gif. Place the image file at the root of you site. Feel free to move it elsewhere, and update the URI accordingly. Note that the image path is relative to the HTML page, not to the CSS file.

    The script

    html {
    top: expression(

    prevent the expression to be run more than once. (not necessary, see #3).
    (typeof _E=='undefined')

    wait until DOM tree is built
    && (document.body)

    set the "blocking" variable. Shortcut for body's style (read only).
    && (_E=document.body.currentStyle)

    we need this shortcut too (write only)
    && (_dbs=document.body.style)

    #1
    check if body has an image. If yes goto #2
    && ((_E.backgroundImage!='none')

    if no image, add a spacer (not forgetting the current bgcolor)
    ¦¦(_dbs.background=' '+_E.backgroundColor+' url("/s.gif") fixed'))

    #1 end
    #2
    is bgimage fixed? if yes goto #3
    && ((_E.backgroundAttachment=='fixed')

    do we have margin=0 ? If yes, add spacer on HTML. (buggy without "height=100%") goto #3
    will soon add expression for "body.height=100%" here (needs testing)
    ¦¦ ((_E.margin=='0px')
    &&(this.style.background=' '+'url("/s.gif") no-repeat'))

    #2b moving most bg info from BODY to HTML
    setting bg on HTML (img, repeat, attachment, default position)
    ¦¦ ((_P=_E.backgroundPositionX+' '+_E.backgroundPositionY)
    && (this.style.background=' '+'transparent '+_E.backgroundImage
    +' '+_E.backgroundRepeat+' '+_E.backgroundAttachment+' '+_P)

    clear BODY bgcolor and bgimg ('none' allows background-position to still be modified by CSS)
    &&(_dbs.backgroundImage='none')
    &&(_dbs.backgroundColor='transparent')

    add an expression to update bg position if the style is updated on BODY
    && (setTimeout(function(){
    document.documentElement.style.setExpression(
    "backgroundPosition",
    "document.body.currentStyle.backgroundPositionX+' '
    +document.body.currentStyle.backgroundPositionY")},0))
    )

    #2b end
    #2 end
    )

    #3
    we apply "top=0px" to HTML. This results in the removal of the expression()
    && (setTimeout(function(){eval('document.documentElement.style.top="0px"')},0))
    );
    }

    The script is executed once, and removes itself from the CSS rules associated to the DOM node. There is no CPU gain in replacing it with human written rules.

    Current version

    (disregarding the background color problem when the background image does not cover the entire page). Working only in standard mode.

    html {
    top: expression((typeof _E=='undefined') &&(document.body) &&(_E=document.body.currentStyle) &&(_dbs=document.body.style) &&((_E.backgroundImage!='none')¦¦(_dbs.background=' '+_E.backgroundColor+' url("/s.gif") fixed')) &&((_E.backgroundAttachment=='fixed') ¦¦((_P=_E.backgroundPositionX+' '+_E.backgroundPositionY) &&(this.style.background=' '+'transparent '+_E.backgroundImage+' '+_E.backgroundRepeat+' '+_E.backgroundAttachment+' '+_P)&&(_dbs.backgroundImage='none') &&(_dbs.backgroundColor='transparent') &&(setTimeout(function(){document.documentElement.style.setExpression("backgroundPosition", "document.body.currentStyle.backgroundPositionX+' '+document.body.currentStyle.backgroundPositionY")},0)))) &&(setTimeout(function(){eval('document.documentElement.style.top="0px"')},0)));
    }

    How to use ?

    You have several possibilities:

  • use a single CSS file for everything, even for styles limited by browser hacks
  • use a CSS file for IE hacks. Either only for IE6, or for IE<=6.
  • do not declare the style in a way that allows IE7 to use it.

    How to ensure that the hack is only for IE6 ?

  • External CSS-fix for IE <= 6: In you html pages:
    <!--[if lte IE 6.0]><link rel="stylesheet" href="/IE6-fix.css"><![endif]-->
    IE6-fix.css: (the '\' limits the style to IE6)
    h\tml {top: expression(...);}

  • External CSS-fix for IE6 only:
    <!--[if IE 6.0]><link rel="stylesheet" href="/IE6-fix.css"><![endif]-->
    IE6-fix.css:
    html {top: expression(...);}

  • Single CSS declaration, or file, for every browser: '_' limits to IE4-6, '\' to IE6-7 (and FF, Op, ...). Combination of the two is IE6-only.
    h\tml {_top: expression(...);}

    If you don't want a conditional comment (propositions #1 and #2), you can still use the following method:

  • Add this line in your main CSS file. Same hack as above: '\' is IE6-7 (and other browsers), '_' is IE4-6, combination is IE6-only.
    @i\mport _url("IE6-fix.css");
    IE6-fix.css:
    html {top: expression(...);}

    #1 and #2 will not cause CSS warnings in non-IE browsers. #3 and #4, will probably (not visible to the end-user).

    To do

  • enabling code for BODY pseudo-height=100%
  • separating the expression's variables from the global scope
  • see if the use of the syntax (function(){})() is more efficient
  • allow easy modification of the spacer's uri.

    Note
    The full text, with correct code, example pages, correct list items indentation, and valid pipe character (¦), is available on this site [motus.ath.cx].

  •  

    fside




    msg:3592622
     5:09 am on Mar 6, 2008 (gmt 0)

    As I said in the other thread:

    I came across this simple solution, as well. It uses css expressions. But perhaps IE 6 deserves to be 'punished', I don't know, for not supporting fixed as it should. Ironically, fixed, actually is used.

    So in the BODY:

    background: url("https://") fixed;

    And in the style for the top bar:

    top: expression( (( t=document.documentElement.scrollTop) ? t: document.body.scrollTop) +'px');

    And that's it. Fixed at the top. And no jitters. It works quicks or strict in IE6. And I'd only load it conditionally for IE6 (maybe 5).

    As you explain, it would make sense that updating is done first, because that's what we see - no jittering. The https is apparently needed because http could cause a problem. But you don't actually need a background image.

    I like having the option of different background tiled images, selected from a preferences menu. But it's not applied to the body, but to a div named, container (or comtainer, or whatever have you), that's become fairly standard practice. So the child of BODY, is this container div. It's got a few of its own 'containers'. But it, rather than body, tends to be treated as the topmost element.

    Your inline script does a lot of validating. But could that be more clearly accomplished in the css itself? If you want no margin, just set margin:0 ?

    As with you, I have no problem writing a main css, and then loading a fixer css for the down-level IE6. So all this would go in that IE6 specific css, too. As for how to call it, I don't mind calling from the hard coded page. These are typically all generated anyway. So change the template, and regenerate them all again. BUT once the site become substantial, I would agree, you'd want this done just in the main style sheet, once, and that's it. Apparently there are various import hacks for IE6. The problem would be if another browser, or IE8, 9, etc, overlook the hack and load the css. But since this import is only in one place, in one file, I think it's worth the risk. If it needs repair, in future, you know right where to go, and before and after it's good for every old/legacy page on your site.

    I noticed particularly with margin, that zooming in/out in IE6 can disturb the relative placement. A item at the bottom of the navbar winds up near the top at full zoom, etc. But that's even true with 0 margin. They still slide around too much. The script module I suggested doesn't do this. Even at full zoom, the various 'fixed' elements are about where you'd expect them to be on screen (with 0 margins set). But of course, they 'jiggle', they 'jitter'. But since the main navbar is now free from that, the distraction might not be as much.

    Achernar




    msg:3592873
     1:32 pm on Mar 6, 2008 (gmt 0)

    fside, your solution needs a container. My solution keeps the markup untouched. 4 lines of style specific to IE6 and you're done.

    If you use the correct conditional comments to target only IE6, I don't see how you would need to correct the style once IE8 IE9 are available.

    Your inline script does a lot of validating. But could that be more clearly accomplished in the css itself? If you want no margin, just set margin:0 ?

    My script (anti-jittering) checks the current layout of the page. It doesn't touch or add margins (in fact, it doesn't even care). There is one remark about margin on BODY, and it could be used as a trigger for an alternative method which would preserve the background-color/background-image duo.

    I noticed particularly with margin, that zooming in/out in IE6 can disturb the relative placement.

    I don't see this in my test case.

    [edited by: Achernar at 1:34 pm (utc) on Mar. 6, 2008]

    MarkFilipak




    msg:3593385
     8:27 pm on Mar 6, 2008 (gmt 0)

    Thank YOU! Works like a champ in all browsers. In my case, the implementation is via javascript:

    if (element.style.setExpression) { // IE
    element.style.position = 'absolute';
    element.style.setExpression('top', "(document.documentElement ¦¦ document.body).scrollTop +'px'");
    element.style.setExpression('left', "(document.documentElement ¦¦ document.body).scrollLeft +'px'");
    document.body.style.backgroundImage = "url('/pixel.gif')";
    document.body.style.backgroundAttachment = 'fixed';
    }
    else element.style.position = 'fixed'; // !IE

    [edited by: MarkFilipak at 8:33 pm (utc) on Mar. 6, 2008]

    fside




    msg:3593456
     9:55 pm on Mar 6, 2008 (gmt 0)

    >If you use the correct conditional comments to target only IE6<

    I agreed with you. I like the idea, though, of doing this in just one place rather than using IE conditionals on every html page. Most of these are hard-coded by an offline generator, not created at the server.

    Instead of the slash/underbar hack, perhaps one could use script to attach to the HEAD. Script has to be on, anyway, for this to run.

    >I don't see this in my test case.<

    Do you have an example of two or three divs that have to be fixed in some relative position to each other. And when zooming in or out in IE6, do they go out of position? Let's say you float a copyright string to the bottom of the navbar. Let's say you have an auxiliary menu on the left, about a "W"s spacing below the bottom of the navbar. And you've got your own custom status bar at the bottom.

    SuzyUK




    msg:3594593
     11:36 pm on Mar 7, 2008 (gmt 0)

    Achernar, thank you so much for taking the time to post this, great post!

    I like the fact it can be completely separated and although I haven't read and digested it all yet, I have a question.. I'm not too hot on javascript so excuse my terminology.

    I recently heard and have read previously that CSS Expressions continue to execute on every mouse move/user action/window scroll etc but that you can stop that happening or lessen the server calls by making sure something is done in the expression, is that what you mean by

    >>We have to ensure that the enclosed javascript code is completely evaluated.

    sorry if I'm not terming this correctly.. but if I knew the terms I'd search ;)

    this thread is bookmarked!
    -Suzy

    Achernar




    msg:3594671
     1:50 am on Mar 8, 2008 (gmt 0)

    What I've noticed by experimenting (not by reading any specific information on the subject), is that when IE is unable to fully execute (eg: the last operation that was executed in the chain of operations, returned "false"):
    (a=DOM.value)&&(b==true)
    say "b" is not true, "a" will not be initialized. I don't know why, or if there is another reason involved in my situation, but it's what I've noticed. And no javascript error is logged in IE. Sometimes it's only a matter of syntax: "!(a==b)" is preferred over "(a!=b)" (or the reverse).

    Overall, beyond this "problem", some expressions are constantly evaluated (when the mouse moves over the window), some only once. This is why you'll notice that in some scripts the coder has used a variable assignment (which forces regular evaluation).
    My anti-jittering fix, for example, is constantly executed. Hence it needs to remove itself.
    In another script I'm currently testing, I declare 4 functions inside an expression, and it appears to be exectuted only once (if I overwrite one of the function, it does not revert to the original version by miracle).

    Expressions don't put load on server, only on the client machine.

    If an expression generates a javascript error (syntax error), it is not evaluated more than once (on startup).
    If an expression is a "constant", it is not executed more than once.
    * {width: expression('10px');}
    will screw your display, but will not overload the CPU (at least not on my computer).

    [edited by: Achernar at 1:54 am (utc) on Mar. 8, 2008]

    fside




    msg:3595636
     3:19 pm on Mar 9, 2008 (gmt 0)

    > conditional comments to target only IE6 >

    I was saying that I agreed it might be better to place it in one file, one place, rather than hard-coding the IE6 downlevel fix into every webpage on the site with IE conditionals. And your slash/underbar hack for the css was the alternative in the one, single css file. But it might conceivably fail in IE8 or 9. You never know. One might otherwise count on the HEAD conditional, however, to be supported by future IE versions.

    I notice that you didn't reply about various of these 'fixed' elements holding position with regard to one another when zooming in and out in IE. And I also found it very interesting that you were able to say that you could remove the fix and still have it work every time on scroll. Those css expressions DO fire a lot. It seems that you've overcome perhaps the practical objection to this if you can do so. You'll still have the 'purists' objections that you're 'behaving' in the css, and not 'styling'.

    Achernar




    msg:3595729
     6:33 pm on Mar 9, 2008 (gmt 0)

    I notice that you didn't reply about various of these 'fixed' elements holding position with regard to one another when zooming in and out in IE.

    Because it has nothing to do with my fix ;) , and I had to do some tests to see what is the problem. The problem could be with the expression you've used to fix the element. But in your situation I would only fix the main one, and position the others relative to it.

    And I also found it very interesting that you were able to say that you could remove the fix and still have it work every time on scroll.

    The fix is an expression, but it only set "true" css rules that, once in effect prevent the jittering. The other expression that keeps the element fixed is always running.

    Those css expressions DO fire a lot. It seems that you've overcome perhaps the practical objection to this if you can do so. You'll still have the 'purists' objections that you're 'behaving' in the css, and not 'styling'.

    I has been a long time since I last cared about purists. ;)
    They are the ones that lose 4 days torturing themselves to come up with a "pure" CSS solution, and still have it break in some situations ; while a simple table layout would have taken 10min of work.
    The worst are the ones that don't do this themselves, but encourage you to take this path.

    If a single IE6-fix.css is enough to make a site work in IE, while keeping the page's HTML and CSS valid, it's perfect for me.
    And as long as this fix is only slowing IE6 a little, It's not a problem. You see, I'm also testing sites on an old Celeron 450Mhz, and there is no usability problem. It's at least not slower than a site with a fixed background. So...

    ewwatson




    msg:3611741
     12:52 am on Mar 27, 2008 (gmt 0)

    Check out this! Works like magic!

    * html {
    background-image: url(image.jpg);
    }
    * html #nav {
    position: absolute; /* position fixed for IE6 */
    top: expression(114+((e=document.documentElement.scrollTop)?e:document.body.scrollTop)+'px');
    left: expression(35+((e=document.documentElement.scrollLeft)?e:document.body.scrollLeft)+'px');
    }

    Achernar




    msg:3612264
     3:10 pm on Mar 27, 2008 (gmt 0)

    I know, that's exactly what the script does. But it takes into account how your page is styled (background image and/or color, or not). You don't have to modify your pages.

    Global Options:
     top home search open messages active posts  
     

    Home / Forums Index / Code, Content, and Presentation / CSS
    rss feed

    All trademarks and copyrights held by respective owners. Member comments are owned by the poster.
    Home ¦ Free Tools ¦ Terms of Service ¦ Privacy Policy ¦ Report Problem ¦ About ¦ Library ¦ Newsletter
    WebmasterWorld is a Developer Shed Community owned by Jim Boykin.
    © Webmaster World 1996-2014 all rights reserved