Post #330

Flexible floats 3

28th March 2004, evening time | Comments (22)

Code information
Status
Code usage No longer in use on this blog
Updates None
Demo Available
Downloads Available

Good God, man! Not another revision!?

Yes, another revision. Shudupa ya face.

Quick overview of the code development

Version 1 naively attacked the problem at the thin end of the wedge, by looping through all the <dl>s on the page and pixel-sizing their widths.

Version 2 took a step forwards from that and used CSS style-switching to alter the width attributes of the individual <dl>s. However, the loop still remained.

This version (and I hope it’s the last, but I doubt it), takes a big leap forwards, and re-focuses the Javascript and CSS. No longer am I looping through and targeting the <dl>s directly, now I’m simply altering the class of their containing <div> (div#left), and letting the browser’s CSS engine do all the work.

Let’s take a look at the revised Javascript…

The Javascript

The first thing to note is that I’m using Javascript to block Opera and Mozilla 1.4. Why? Well, those are the two browsers which don’t like my floated, ‘rows and columns’ layout. So until they learn to behave, they only get the simple styles.

The second thing to note is that all that pixel-sizing and looping has gone, to be replaced with a single conditional rule. Depending on the outcome of this rule a class (.wide for wide browser windows, and .narrow for narrow browser windows) is applied to div#left, allowing the relevant visual changes occur.

  1. // Event Listener
  2. // from http://scottandrew.com/
  3. function addEvent(obj, evType, fn)
  4. {
  5. if (obj.addEventListener)
  6. {
  7. obj.addEventListener(evType, fn, false);
  8. return true;
  9. }
  10. else if (obj.attachEvent)
  11. {
  12. var r = obj.attachEvent('on'+evType, fn);
  13. return r;
  14. }
  15. else
  16. {
  17. return false;
  18. }
  19. }
  20.  
  21. // from http://www.kryogenix.org/
  22. addEvent(window, "load", resizeBooks);
  23. addEvent(window, "resize", resizeBooks);
  24.  
  25. function resizeBooks(e)
  26. {
  27. // adapted from http://www.dithered.com/javascript/browser_detect/
  28. //**************************************************************//
  29. // sniff user agent
  30. var userAgent = navigator.userAgent.toLowerCase();
  31.  
  32. // if Mozilla 1.4 then quit
  33. if ((userAgent.indexOf('gecko') != -1) && (userAgent.indexOf('gecko/') + 14 == userAgent.length) && (parseFloat(userAgent.substring(userAgent.indexOf('rv:') + 3)) == '1.4')) return;
  34.  
  35. // if Opera then quit
  36. if (document.all && window.Event) return;
  37. //**************************************************************//
  38.  
  39. // check this browser can cope with what we want to do
  40. if (!document.getElementById) return;
  41. var leftDiv = document.getElementById('left');
  42. if (!leftDiv) return;
  43. if (!leftDiv.offsetWidth) return;
  44.  
  45. // check if div#left is wide enough to accept two or more floated <dl>s
  46. leftDiv.className = (leftDiv.offsetWidth >= 621) ? "wide" : "narrow";
  47. }
  48. Download this code: 330a.txt

BTW, if you’ve never seen this kind of statement before:

  1. leftDiv.className = (leftDiv.offsetWidth >= 621) ? "wide" : "narrow";
  2. Download this code: 330b.txt

It’s called a Conditional Operator, and it’s a great space saver (in both PHP and Javascript).

The CSS

The CSS rules have also been simplified. div#left’s default class (applied in the XHTML itself) is .narrow. This ensures all browsers start off with the same, easy to handle layout; where each book/author is laid out one to a line.

When the browser window widens, Javascript switches div#left’s class to .wide, and all the floaty styles are applied. When the window narrows again, the class is switched back to .narrow.

Nice and simple.

  1. /* default style/style for narrow windows */
  2. body#reading div.narrow dl {
  3. border-bottom: 1px solid #ddd;
  4. display: block;
  5. float: none;
  6. height: 170px;
  7. margin: 0 0 20px 0;
  8. padding: 0 0 0 113px;
  9. position: relative;
  10. width: auto;
  11. }
  12.  
  13. /* style for wide windows */
  14. body#reading div.wide dl {
  15. border-bottom: 1px solid #ddd;
  16. float: left;
  17. height: 170px;
  18. margin: 0 25px 20px 0;
  19. padding: 0 0 0 113px;
  20. position: relative;
  21. width: 150px;
  22. }
  23.  
  24. /* style the div that contains the book image */
  25. body#reading dl dd div {
  26. background: url('/blog/commonpics/bg_image.gif') no-repeat bottom right;
  27. left: 5px;
  28. position: absolute;
  29. top: 5px;
  30. }
  31. Download this code: 330c.txt

Why is this better than the previous methods?

Well, for starters the whole thing conforms to the ideals of working with Javascript: give people something perfectly acceptable and usable to start with, and then use Javascript to enhance that for the more advanced browsers. In this case the default layout (.narrow) is the one that every browser can render, but with the application of some Javascript, it swaps to the nicer looking, and more functional floaty style (.wide).

Secondly, by applying the CSS changes higher up the tree (to the containing <div> itself) I remove the need to find and then loop through all the <dl>s on the page.

And lastly, because it lets me directly block the floated-layout from those browsers which can’t handle it. I previously depended on CSS hacks to filter out Opera, but Mozilla 1.4 always slipped through the net. This way I catch both of them.

Why is this worse than the previous methods?

The only downside to this method is that the Javascript doesn’t kick in until the page has fully loaded. Why’s that bad? Well, imagine you’re on dial-up, and the page is going to take twenty seconds to fully load. After five seconds the text appears, and you start to read it. Fifteen seconds later, when you’re halfway down the page, the Javascript runs — it re-styles everything, and suddenly you’ve lost your place. You have to scroll up again to find it.

Not so good.

I guess I’ll have to wait and see what the response is like on this, before I decide if it’s a problem.

Feedback and thanks

I’m always happy to get feedback and suggestions for improvement on this (and anything else I post), so please let me know if sommit’s wrong.

Many thanks to Mark Wubben and Dean Edwards for suggesting I look at this thing in a slightly different way. Also thanks also to Ethan, Drew, and Reid, who kindly checked the pages on their browsers. Again and again, and again.

Jump up to the start of the post


Comments (22)

Jump down to the comment form ↓

  1. Steven:

    Looks ok on my end Dunstan

    Posted 2 hours, 3 minutes after the fact
  2. Hans Hyttinen:

    Firebird: 2 rows
    Opera: 1 row
    Netscape: 1 row
    It-Which-Must-Not-Be-Named: 3 rows

    Looking great.

    Posted 6 hours, 30 minutes after the fact
  3. Andrew:

    I've still got stacked piccurs, all appearing in the top left of the content bit - Firebird 0.6

    Works fine until the JavaScript kicks in.

    Posted 13 hours, 24 minutes after the fact
    Inspired: ↓ Dunstan
  4. Andy:

    Looking great, 4 columns in IE :-)

    I am getting the '47 items to download' still when resizing in IE, although it doesn't seem to be crashing the browser.

    It is the bg_image.gif file (47 times), any idea why this isn't cached?

    Posted 13 hours, 30 minutes after the fact
    Inspired: ↓ Dunstan, ↓ Doug
  5. Dunstan:

    I found the same thing, Andy, I have no idea why it behaves like that. That image file is the dropshadow for all the pictures, applied via CSS - maybe IE doesn't cache images that are mentioned in CSS?

    Posted 13 hours, 52 minutes after the fact
    Inspired by: ↑ Andy
    Inspired: ↓ Mark Wubben, ↓ Doug
  6. Dunstan:

    Ah, thanks Andrew, I'll have to sniff that out as well. Damnit.

    Posted 13 hours, 53 minutes after the fact
    Inspired by: ↑ Andrew
  7. Mark Wubben:

    Don't forget that the non-caching in IE is usually do to your own caching settings. Dave Shea wrote about that some months ago.

    Posted 18 hours, 49 minutes after the fact
    Inspired by: ↑ Dunstan
    Inspired: ↓ Doug, ↓ Dunstan
  8. Mark Wubben:

    Nice work, Dunstan. Don't really see a thing which can be improved now :)

    Please don't make assumptions on classNames, though. Imagine a user who uses another class on div#left, your script will brake it.

    Elaborating on that, you could make it more general by letting the user specify the ID of the div - I mean, element - which is to be resized, and perhaps also specify the minimum width. Then you'd need another function name as well:

    function resizeElement(oNode, nWidth){
    /* thinking about it, it'd be easier to let the user pass the node on, it'll allow him to use a node without an ID. Make sure it's an element, though! */

    // do stuff
    }

    In this (http://alistapart.com/discuss/tableruler/1/) discussion about the table ruler article on ALA some code was mentioned to do the className manipulation:

    var class = e.currentTarget.className.split(/\s+/);
    class.push('IEhover');
    e.currentTarget.className = class.join(' ');

    That should work pretty well.

    Hmm, improvements could still be made, but only to make the script more usuable for others (without forcing them to adapt it).

    Posted 18 hours, 59 minutes after the fact
  9. César:

    I know it's silly, but if I open the page in Firefox and reduce the type size (Ctrl--), the layout goes from 2 columns to one (!?).

    Amazing hack, though. :-)

    Posted 21 hours, 48 minutes after the fact
    Inspired: ↓ Dunstan
  10. Dunstan:

    That's because the overall containing div (div#container) is sized to a max-width of 60em. So when you reduce your font size, you reduce the width of the container, which reduces the width of the lefthand div (div#left) which forces the floated <dl>s to wrap.

    If you look closely you'll see that it doesn't actually swap to the .narrow style, because neither the text, nor the bottom borders of the <dl>s, extend the width of their containing div.

    All that's happened is that they've wrapped due to lack of space, as any good floated item will :o)

    If you left your font size small, and then reloaded the page (or altered the width of your browser), you'd see the JS kick in and load the .narrow styles proper, allowing the text and bottom borders to spread out.

    Hope that makes sense :o)

    Posted 21 hours, 59 minutes after the fact
    Inspired by: ↑ César
  11. Ben Hollis:

    A technique I've seen in some unobtrusive JavaScripts is to have a class you can apply to an element, and then the JavaScript loops through all elements with that class. That way you don't have to rely on having a div#left, you could have a div.filling, and then you could make any div do this. Then again, I doubt that more generalization is really practical.

    Posted 22 hours, 45 minutes after the fact
    Inspired: ↓ Dunstan, ↓ Mark Wubben
  12. Dunstan:

    Ben, that's pretty much what I was doing in Version 2, but I really wanted to get rid of the loop. Loops take time, and I'd like to make the thing as fast as possible.

    But yes, if I was trying to make code for people to copy and paste into their sites, assigning a class to the objects being manipulated would be an easy way to solve the problem :o)

    Posted 22 hours, 50 minutes after the fact
    Inspired by: ↑ Ben Hollis
    Inspired: ↓ Ben Hollis
  13. Ben Hollis:

    Heh, sorry, I suppose I posted a little hastily... you seem to have pretty full command of Javascript. The stuff you've got going here is amazing...

    Posted 22 hours, 54 minutes after the fact
    Inspired by: ↑ Dunstan
    Inspired: ↓ Dunstan
  14. Dunstan:

    Not at all, Ben, I just bumble my way through these things with the help of much cleverer friends :o)

    Posted 22 hours, 58 minutes after the fact
    Inspired by: ↑ Ben Hollis
  15. Doug:

    No longer crashes my IE6 on Win2000. Three columns when maximized. Downloads 47 drop shadows on every resize. Mark, how do I stop that? Do you have a link? Though it is kind of fun watching the drop shadows pop in one at a time.

    Posted 23 hours, 40 minutes after the fact
    Inspired by: ↑ Andy, ↑ Dunstan, ↑ Mark Wubben
    Inspired: ↓ Dunstan, ↓ Andy
  16. Dunstan:

    I guess Mark meant this:
    http://www.mezzoblue.com/archives/2003/09/19/rollovers_in/

    Posted 1 day after the fact
    Inspired by: ↑ Mark Wubben, ↑ Doug
    Inspired: ↓ Andy
  17. Andy:

    Yeah, that fixed it, turning caching in IE to 'Automatically' from 'Every Visit'.

    Heh, a developer only bug.

    I've actually moved my browser cache to a RAM drive, as with over a gig of RAM, 40 odd meg for a RAM drive makes a big difference to cache and temp files without impacting much else and it's self cleaning :-)

    Posted 1 day, 2 hours after the fact
    Inspired by: ↑ Doug, ↑ Dunstan
  18. Mark Wubben:

    Looping through all elements to search for specific classes isn't really a smart thing to do, especially if you need to do multiple loops. I once wrote a script which did only one loop, but it still needs improvement.

    The fastest method is by using XPath, but of course that's Mozilla only.

    Posted 1 day, 19 hours after the fact
    Inspired by: ↑ Ben Hollis
  19. Zoe Gillenwater:

    I'm using Netscape 7.1 on WinXP and seeing the same old Opera and Safari error where the images are all stacked on top of each other, overlapping the text at the top. Any fix for this yet?

    Posted 4 days, 21 hours after the fact
  20. Janos:

    Tip:
    Try CSS Querys (only Opera supports them). It is experimental but this could be the pure CSS solution in the future.
    Here is a demo:
    http://www.literarymoose.info/=/synopsis/stretch.xhtml

    Posted 1 week, 4 days after the fact
  21. Simon Whatley:

    Hi Dunstan,

    You do something funky with your menu items by displaying the accesskey associated with them. Can you tell me how you do this? Is this achieve with CSS or javascript, or a combination?

    I did notice that the effect works in Firefox but not IE, which isn't a great loss :-)

    Thanks,
    Simon.

    Posted 3 months, 2 weeks after the fact
    Inspired: ↓ Dunstan
  22. Dunstan:

    Simon you do it like this:

    a:focus::after {
    content: " [" attr(accesskey) "]";
    }

    HTH.

    Posted 3 months, 2 weeks after the fact
    Inspired by: ↑ Simon Whatley

Jump up to the start of the post


Add your comment

I'm sorry, but comments can no longer be posted to this blog.