‘DOM-foolery’ with Images

At the risk of repeating myself to those who read the Design View, I thought I’d post this little idea from yesterday’s issue.

This week, we were handed a small challenge in an otherwise elegant little site design — the presentation called for the all the ‘content images’ to have rounded corners.

In the past, there wasn’t much choice. You made a nice framing template in Photoshop or Fireworks, dropped your picture behind it and exported your new image.

The problem with that method is that, although the image itself is considered ‘content’, the rounded corners are a ‘design whim’ that is only relevant to the current look and feel. If the client decided that the next redesign should be a little more slick and hard-edged, we would need to either re-author every image, or just grin and bear it — not good choices.

So, what are the alternatives? As it turns out, not even CSS3 or Mozilla specific styles can offer us much help. Moz can do borders that curve behind the corners — a nice enough look, but not what we’re after here. Safari can hold multiple backgrounds but that’s not much use elsewhere. The only viable solution appeared to me to be a touch of ‘DOM-foolery’.

Example of the problemFirstly, we’ll need to work out a repeatable method to get the effect, forgetting (for now) about sound markup technique.

Since images are foreground elements, we’re going to need to levitate our ‘corner graphics’ over the image. That means we’ll need to wrap each image with it’s own ‘relatively-positioned’ DIV and then use that ‘wrapper’ to position our ‘absolutely-positioned’ corners.

As you can see in the diagram, the wrapping DIV is called ‘.wrapper’ and needs to positioned relatively, and floated either left or right, to force it to tightly enclose our image.

div.wrapper {
  position:relative;
  float:left;
}

Our four inner DIVs are then set to ‘position:absolute‘, which automatically brings them to the front of our image. Each has the same width and height (7x7px — the dimensions of the corner GIFs), but different positions and background graphics. Something like this:

div.wrapper div{    /* set all the inner DIVs */
    position:absolute;
    width: 7px;
    height: 7px;
}
div.wrapper div.tl{
    background:transparent url(tl.gif) top left no-repeat;
    left:0;
    top:0
}
div.wrapper div.tr{
    background:transparent url(tr.gif) top right no-repeat;
    right:0;
    top:0
}

div.wrapper div.bl{
    background:transparent url(bl.gif) bottom left no-repeat;
    left:0;
    bottom:0
}
div.wrapper div.br{
    background:transparent url(br.gif) bottom right no-repeat;
    right:0;
    bottom:0
}

Combine the markup with the CSS, and it hangs together nicely.

Ok, now, that’s all very nice’n’all but obviously we can’t go around delicately wrapping each new image by hand, like little ‘virtual sushi rolls’. We need a nice little function can do the dirty work for us.

In our case we’ve decided to target only images within a specified ‘DIV#content‘ — the editable content area of the CMS. However it wouldn’t be hard to alter the script to target all images, or only those images with a certain class if required.

I’ll run through the script quickly but the example is well-commented if you’re interested

To start, we’ll declare a new function (roundedImages), grab all the elements inside #content, then filter out everything but the images. They’re waiting for us to use in an ‘imgs[]’ array.

function roundedImages() {
var content = document.getElementById ('content');
var imgs = content.getElementsByTagName ('img');

Next, we start a loop to process our list of images. We create a brand new DIV, attach the ‘wrapper’ class to it, select the first image, and replace it (for now) with our new DIV.

for (var i = 0; i < imgs.length; i++) {
var wrapper =
document.createElement('div');
wrapper.className = 'wrapper';

var original = imgs[i];
original.parentNode.replaceChild
(wrapper, original);

Now we’ll need to inject our four corner DIVs into the document and attach the appropriate classes to each. The code is fairly straight-forward.

var tl = document.createElement('div');
   tl.className ='tl';
   var br = document.createElement('div');
   br.className ='br';
   var tr = document.createElement('div');
   tr.className ='tr';
   var bl = document.createElement('div');
   bl.className ='bl';

Ok, we have our corner DIVs but they aren’t actually in the document. We can use ‘appendChild‘ to glue them into our wrapper DIV. To complete the loop, we glue our original image back into the wrapper; then we’re ready to grab the next image.


  wrapper.appendChild(tl);
  wrapper.appendChild(tr);
  wrapper.appendChild(bl);
  wrapper.appendChild(br);

   wrapper.appendChild(original);
  }
}

Lastly, we run the function on page load.

window.onload = roundedImages;

And that’s it. Pop the script into your <head> with your CSS, and you’re rounding corners like an INDY500 driver. You can see it in action here.

To my knowledge it runs like a charm in IE5+, Moz1+, Safari and Opera 8.0. There seems to be a bug in Opera 7 I haven’t figured yet.

This is, of course, a first generation experiment and has some limitations. ‘DIV.wrapper’ needs to be floated to tightly enclose the image, which means your images will always appear to be floated too. However it would be quite possible to remove this requirement by having the script pull out the dimensions of each image, and then use them to manually size the wrapper.

It would also be nice if the script was able to pass any style information attached to the image (particularly alignment data) up to the wrapper, allowing users the choice to float their images left or right. For now, we’re requiring the client to have right-floated images in their content.

There also seems to be a wacky bug in IE6 (No! Never!) that causes a 1px mis-alignment on the right and bottom sides of images with odd widths and heights. Totally bizarre. It appears to be a rounding error (no pun intended) and, at the moment, the best solution I can proffer is to use even-numbered image dimensions. If you have some better IE voodoo, we’d certainly love to hear it.

Otherwise, it seems to be quite solid. Certainly it’s fast, degrades elegantly and there are endless potential variations on this simplest of themes.

I’ve got some ideas for some variations which I might take on in a later post, but let me know if you find it useful.

PostScript

With some excellent suggestions from Joshua Paine, we’ve made a few minor improvements to this script.

  • Firstly, a rounding error in IE made the corners positioned with ‘right:o‘ creep 1 pixel out of alignment depending on the browser width). The fix is allow the top-right and bottom-right corners to stretch the full width of the wrapper, and to align their background-image to the right side instead. To do this we need to use the DOM to ‘forcibly set’ the width of each wrapper to the width of it’s contained image — originally we didn’t specify it’s width
  • It’s quite common to want to link the images. This causes a ‘reordering of the tree’ and stuff is no longer where it’s expects it to be cuasing errors. We’ve added a line to make the script step up and over the link.

Both of these suggestions have now been incorporated into the example. Thanks Joshua.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Pingback: Pig Pen » DOM Foolery With Images - its an adventure

  • Etnu

    The div doesn’t necessarily need to be floated. Give the div display inline, and everything should wrap tightly so long as margins and padding is zeroed out.

  • http://www.sitepoint.com AlexW

    The div doesn’t necessarily need to be floated. Give the div display inline, and everything should wrap tightly so long as margins and padding is zeroed out.

    Etnu, I may be wrong, but I think that while ‘inline’ would make it wrap, I’d have trouble positioning the internal DIVs with right, left, top and bottom which only really apply to block level elements.

    But if you have an alternative solution, all the better. I can see myself using this type of effect quite a bit in the coming year.

  • http://fairsky.us/home Joshua Paine

    You can fix the IE rounding bug thusly:

    Instead of making tr and br positioned with right:0; and width:7px;, position them with left:0; and width:100%;. You can actually do this to all four corners to shrink the CSS with no ill effects.

    For this to work, though, you need to add one line to the javascript:

    wrapper.style.width = imgs[i].width+'px';

    You can put this anywhere inside the for loop once wrapper is defined.

  • http://www.sitepoint.com AlexW

    Haven’t tested that out yet, but it sounds like a perfect solution, Joshua. Nice work.

  • Pingback: OliverBrown.me.uk

  • Hannes

    I’m really impressed from this nice Function though I’ve noticed some serious problems with – guess who – IE. If you try to link an Image e.g.

    the IE would unfortunately crash. Is there anything you could make the IE not to crash? :-)

  • http://fairsky.us/home Joshua Paine

    Is there anything you could make the IE not to crash?

    Immediately before the line
    original.parentNode.replaceChild(wrapper, original);
    add
    if(original.parentNode.tagName.toUpperCase()=='A') original = original.parentNode;

    I haven’t tested that, but it should work and should be compatible with my IE rounding bug fix above.

  • Hannes

    Works perfectly, thank you very much!

  • chris

    This doesn’t quite work on the mac (OS X) using either Safari or IE5.

  • http://fairsky.us/home Joshua Paine

    Looks fine in Safari 2.0.1 to me, but no it’s never going to work in IE5 Mac.

  • chris

    It doesn’t work on Safari 1.0.0 or 1.0.3 using OS X 10.2. I was thinking about using this on my site, but I’m not sure if it won’t work for some users. On IE5, it just shows the normal photo (which is good), but on Safari 1.0 it actually shows the corners in different places on the page (which is bad).

  • http://fairsky.us/home Joshua Paine

    Chris, do you still see the problems with IE5 if you apply my two fixes posted above?