nrkn

Image Slicing for fluid CSS designs

Alpha transparency

An interesting feature of the PNG format is that it supports alpha transparency. Sometimes you may want to create layouts that take advantage of this, as shown in the detail in the illustration. As you can see, the whole layout is slightly transparent and the page background shows through.

We are going to add a background image to our page, so that the alpha effect is obvious, and we are going to swap all of our layout PNG files (and our one jpeg file) for new images that use alpha transparency. We will also be bringing our old friend mainC.png back. This is the all white PNG file that we got rid of when we were optimising our page. Now we will be needing it again.

Here are our new PNG files. Because they are against a white background it may not be immediately obvious that they have an alpha channel, but try saving them and opening them with your image editor to see the alpha transparency. I've surrounded them with a border to help them stand out against the white background a little better.

  1. The new background image
  2. hrC.png
  3. hrL.png
  4. hrR.png
  5. mainC.png
  6. mainL.png
  7. mainR.png
  8. mainTB.png
  9. mainTLBL.png
  10. mainTRBR.png
  11. And in our content folder, nrkn.png

The first thing you should note is that they are all 32 bit PNG files. All the work we did optimising the images by reducing their colour depth or, in the case of mainTRBR, saving as a jpeg, goes down the toilet. Our main layout images now weight in at a hefty 58KB! This is something to keep in mind if you think your layout requires the use of alpha transparency.

Another thing worth noting is that alpha transparent PNG files don't work in Internet Explorer versions less than 7. Once we've got the layout working in other browsers, I'll show you how to apply the AlphaImageLoader hack to get alpha transparency in IE 6 (as well as 5 and 5.5, but I can't guarantee that the layout will work in those browsers as I don't have access to them to test it).

Now that we have our new, fancy alpha PNG files, let's see what they look like plugged into our existing layout. We have to do a couple of things first though, like change the body background to our new bg.png, change references to mainTRBR.jpg to mainTRBR.png, and change the background of .mainC:

            
body {
  background: url( bg.png );
  color: black;
  font-family: sans-serif;
  font-size: 100%;
  line-height: 1.5em;
  min-width: 440px;
} 

.mainT div {
  background: url( mainTRBR.png ) no-repeat top right;
}

.mainC {
  background: url( mainC.png );
}

.mainB div {
  background: url( mainTRBR.png ) no-repeat bottom 
    right;
}
            
          

The result?

Oh dear. Not so hot, is it? This is a consequence of the way we were previously layering our nested div elements. Before, we could rely on the top left and top right div elements obscuring the background image of the outermost div, but now that we are using transparency, that background image is showing through.

Luckily, we don't actually have to change much in our CSS to get it working, and we don't have to change our HTML at all. Let's fix the top of the layout first. First, we swap these two lines in our CSS:

            
.mainT {
  background: url( mainTB.png );
}

.mainT div {
  background: url( mainTRBR.png ) no-repeat top right;
}

.mainT div div {
  background: url( mainTLBL.png ) no-repeat top left;
  height: 220px;
}
            
          

This makes the innermost div element, which is also the topmost element, be the element with our mainTB.png background:

Now all we have to do is apply a left and right margin to the topmost div, and the background won't be drawn all the way to the edges and it won't overlap the other div elements anymore:

            
.mainT div div {
  background: url( mainTLBL.png ) no-repeat top left;
  height: 220px;
  margin-left: 220px;
  margin-right: 220px;
}
            
          

As you can see, the top part of our layout is now working properly:

Time to rinse and repeat. This time we will fix the middle row of our layout, where the content lives. As before, we swap the background rule for .mainC with the background rule for .mainC div div. This time our margins are a different size, the width of mainL.png and mainR.png, which is 75px:

            
.mainC {
  background: url( mainL.png ) repeat-y left;
}

.mainC div {
  background: url( mainR.png ) repeat-y right;
}

.mainC div div {
  background: url( mainC.png );
  margin-left: 75px;
  margin-right: 75px;
}
            
          

We're making progress, but because we've added those margins, the #content element has inherited them as well:

So, back to the CSS to apply some fixes. We need to do two things, make sure that div elements that are inside of .mainC div div don't inherit that margin we just added, and remove the existing margin from #content:

            
.mainC div div div {
  background: none;
  margin: 0;
}

#content {
  color: #b95207;
  margin-left: 70px;
  margin-right: 70px; 
  padding-left: 1em;
  padding-right: 1em;
  position: relative;
  top: -30px;
}
            
          

The last part of the layout is exactly like the first, swap the inner and outermost background images and add some margins:

            
.mainB {
  background: url( mainTLBL.png ) no-repeat bottom left;
  margin-top: -168px;
}

.mainB div {
  background: url( mainTRBR.png ) no-repeat bottom right;
}

.mainB div div {
  background: url( mainTB.png ) bottom;
  height: 220px;
  margin-left: 220px;
  margin-right: 220px;
}
            
          

We also need to fix up our .hr divider:

            
#content .hr {
  background: url( hrL.png ) no-repeat left;
  margin: 1em 0;
}

#content .hr div {
  background: url( hrR.png ) no-repeat right;
}

#content .hr div div {
  background: url( hrC.png );
  height: 14px;
  margin-left: 102px;
  margin-right: 102px;
}
            
          

Now there's just one last thing to do before we apply the Internet Explorer fixes. Remember when we were making the layout fit the content better, in order to remove the large space below the content, we moved the bottom part of the layout up by 168px? Here's the CSS we used:

            
.mainB {
  background: url( mainTLBL.png ) no-repeat bottom left;
  margin-top: -168px;
}
            
          

Unfortunately, now the two alpha transparent sections overlap:

Again, this is an easy fix, although it does mean that we will have some wasted space under the content. First, we remove the CSS rule highlighted in red above. Secondly, we can get rid of some of the gap, because the curve on the bottom section (which is 220px high) actually peters out about 180px from the bottom. If we change the height of .mainB div div from 220px to 180px, we get about 40px of wasted space back:

            
.mainB div div {
  background: url( mainTB.png ) bottom;
  height: 180px;
  margin-left: 220px;
  margin-right: 220px;
}
            
          

It's looking pretty good now... except in IE 6, where none of our nice alpha effects actually work. IE 6 flattens them all out into a grey colour. Also, in both IE 6 and IE 7, another bug has surfaced which is preventing part of the page drawing properly:

Almost always when you see this kind of thing in Internet Explorer, it's a problem that is caused by IE not setting an internal flag which it calls HasLayout. You can force HasLayout by giving the element an explicit height. In this case the problematic element is #content, and we will give it a height of 1%, because IE will see that it needs to be bigger than that to fit its content and reset the height back to what it was, but at the same time we will trigger HasLayout and the bug (I think it's the Peekaboo Bug) will go away. We make use of conditional comments so that only IE will see the hack. We add this to our HTML (new code in red):

            
    <style type="text/css">
      @import url( layout/layout.css );
    </style>
    <!--[if lte IE 7.0]> 
    <style type="text/css">
#content {
  height: 1%;
}    
    </style>
    <![endif]-->      
            
          

That nailed the Peekaboo bug on the head, now we're going to fix the problem with the alpha in IE 6 and lower.

If, for some reason, you have the good luck not to have to support IE 6, you can stop now. The alpha layout is done, and will work in all modern browsers, including IE 7. You can view the layout without any hacks to cater to IE 6 or you can download a zip file containing all of the files that make up the alpha layout without IE 6 hacks (ZIP, 67KB).

Unfortunately, most of the time we're not that lucky. At the time of writing, IE 6 is still the most common browser. Sigh. Read on to find out how to make the alpha layout work in IE 6. Warning: things are about to get ugly!

First, we create a new stylesheet in our layout folder called "lteIe6.css". Then we add another conditional comment to our code to link it in when the visitor is using IE 6 or lower:

            
    <style type="text/css">
      @import url( layout/layout.css );
    </style>
    <!--[if lte IE 6.0]> 
    <style type="text/css">
      @import url( layout/lteIe6.css );
    </style>
    <![endif]-->     
    <!--[if lte IE 7.0]> 
    <style type="text/css">
#content {
  height: 1%;
}    
    </style>
    <![endif]-->
            
          

Painful isn't it? OK, now that we have a brand new stylesheet just for fixing shortcomings in IE 6, we'll fix our first issue. Add this to the lteIe6.css stylesheet to fix the alpha on nrkn.png:

            
h1 {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='content/nrkn.png');
  height: 47px;
  width: 113px;
}

h1 img {
  display: none;
}            
            
          

That's the nrkn logo fixed. Now it starts getting really nasty. Because of the limitation of the AlphaImageLoader, we can't use our combined images trick with IE 6. The AlphaImageLoader can't position backgrounds and it can't tile backgrounds either. We're going to have to cut our alpha images up into nine slices, just like we did for the basic layout. These images are only going to be used by IE 6, other browsers will still use the proper combined slices. We can use the existing mainL.png, mainR.png and mainC.png with IE 6, but we will also need the following six new images:

  1. ieMainTL.png
  2. ieMainT.png
  3. ieMainTR.png
  4. ieMainBL.png
  5. ieMainB.png
  6. ieMainBR.png

The next headache is that our existing HTML doesn't play nicely with the AlphaImageLoader either. In order for the AlphaImageLoader to work, certain conditions must be met, such as the element that it is being applied to having an explicit width. Unfortunately, being a fluid layout, we don't want elements that have an explicit width, we want our layout to fit the available space.

So here's where things get really dirty. The easiest way to get IE 6 to emulate a fluid site, using elements that we can apply the AlphaImageLoader to, is to use a table.

Yep, you read that right. It's not possible any other way, according to my research so far. Please let me know if you know otherwise, I love being proven wrong! If IE 6 supported the CSS display-table property then we could write a table-less layout, but it doesn't, along with a large chunk of other CSS 2 properties. So we need to add two more conditional comments to our code, the first (new code in red, existing code in black):

            
<div class="mainT"><div><div></div></div></div>
<!--[if lte IE 6.0]>
  <table border="0" width="100%" cellpadding="0" cellspacing="0">
    <tr>
      <td class="ieTL"><div></div></td>
      <td class="ieT" width="100%"><div></div></td>
      <td class="ieTR"><div></div></td>
    </tr>
  </table>
  <table border="0" width="100%" cellpadding="0" cellspacing="0">
    <tr>
      <td class="ieL"><div></div></td>
      <td class="ieC">
<![endif]-->
<div class="mainC"><div><div>            
            
          

And the second conditional comment (again, new code in red, existing in black):

            
</div></div></div>
<!--[if lte IE 6.0]>
          </td>
      <td class="ieR"><div></div></td>
    </tr>    
  </table>
  <table border="0" width="100%" cellpadding="0" cellspacing="0">
    <tr>
      <td class="ieBL"><div></div></td>
      <td class="ieB" width="100%"><div></div></td>
      <td class="ieBR"><div></div></td>
    </tr>
  </table>
<![endif]-->
<div class="mainB"><div><div></div></div></div>            
            
          

All of this may have you questioning whether or not you actually want to use alpha images at all. It was pretty straightforward getting them working with the other browsers, but IE 6 makes it a bit of a nightmare. Unfortunately, at the time of writing this tutorial, IE 6 still commands the majority share of the browser market. It will probably be some years before you can start ignoring it. In the meantime, if you want to do a layout that is both fluid and uses alpha transparency, this is what you have to put up with.

Now that we've polluted our markup with that table code (remember, because it's inside conditional comments only IE 6 and earlier will see the table markup, all other browsers will ignore it), we need to go back to our lteIe6.css spreadsheet and add some CSS to apply the alpha background images to the table cells. The first thing we do is add this to our IE 6 CSS file to hide our existing mainT and mainB sections from IE, as we're using tables to display those instead:

            
.mainT, .mainB {
  display: none;
}
            
          

Now we apply some height and widths to div elements inside table cells (we are using these div elements in much the same way as people used to use spacer GIFs), and apply the AlphaImageLoader to the table cells to draw the backgrounds for the top and bottom parts of the layout (we'll come to the middle section with the content in it shortly):

            
.ieTL div, .ieTR div, .ieBL div, .ieBR div {
  height: 220px;
  width: 220px;
}

.ieT div, .ieB div {
  height: 220px;
}

.ieTL {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainTL.png');
}

.ieT {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainT.png');
}

.ieTR {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainTR.png');
}

.ieBL {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainBL.png');
}

.ieB {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainB.png');
}

.ieBR {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainBR.png');
}
            
          

Next, we turn backgrounds and margins off for middle row (above we just dealt with the top and bottom parts of the layout) and add AlphaImageLoaders to draw the backgrounds for the content area:

            
.mainC, .mainC div, .mainC div div {
  background: none;
  margin: 0;
}

.ieL div {
  width: 75px;
}

.ieR div {
  width: 75px;
}

.ieL {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/mainL.png');
}

.ieR {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/mainR.png');
} 

.ieC {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/mainC.png');
}
            
          

Finally, we add some CSS to the IE stylesheet that uses AlphaImageLoader to display a simplified version of our .hr divider. We could have wrapped this inside a table as well so that it looked the same as it does in our version that works in all other browsers, but for the divider it's a bit overkill, so we just do this:

            
#content .hr, #content .hr div, #content .hr div div {
  background: none;
  margin: 0;
}

#content .hr {
  margin: 1em 0;
}

#content .hr div div {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/hrC.png' ); 
}
            
          

The divider no longer has the nice fading out at the edges effect in IE 6 but it still looks OK.

Our HTML and CSS is shown below, or you can view the page here, or you can download a zip file containing all of the files that make up the alpha layout with IE hacks (ZIP, 126KB).

HTML:

            
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>Basic Fluid CSS Layout with Dummy Content</title>
    <script type="text/javascript"></script>
    <style type="text/css">
      @import url( layout/layout.css );
    </style>
    <!--[if lte IE 6.0]> 
    <style type="text/css">
      @import url( layout/lteIe6.css );
    </style>
    <![endif]-->        
    <!--[if lte IE 7.0]> 
    <style type="text/css">
#content {
  height: 1%;
}    
    </style>
    <![endif]-->      
  </head>
  <body>
    <h1><img src="content/nrkn.png" alt="nrkn"></h1>
    <div class="mainT"><div><div></div></div></div>
    <!--[if lte IE 6.0]>
      <table border="0" width="100%" cellpadding="0" cellspacing="0">
        <tr>
          <td class="ieTL"><div></div></td>
          <td class="ieT" width="100%"><div></div></td>
          <td class="ieTR"><div></div></td>
        </tr>
      </table>
      <table border="0" width="100%" cellpadding="0" cellspacing="0">
        <tr>
          <td class="ieL"><div></div></td>
          <td class="ieC">
    <![endif]-->
    <div class="mainC"><div><div>
      <div id="content">
        <div id="spacerL"></div>
        <div id="spacerR"></div>      
        <h2>
          A really long title, this is a level 2 header as it is the 2nd most 
          important header on the page
        </h2>
        <div class="hr"><div><div><hr></div></div></div>
        <h3>
          This is our level 3 header, as it is the 3rd most important header on
          the page
        </h3>
        <p>
          If we had more than one section on this page, each of them would get
          a level 3 header, as each section is of equal importance.  If we had
          some sub-sections inside these sections that needed a header, they 
          would get a level 3 header, and so on and so forth.
        </p>
        <ol>
          <li><span>This is the first item in the list</span></li>
          <li><span>This is the second item in the list</span></li>
          <li><span>This is the third item in the list</span></li>
        </ol>
      </div>  
    </div></div></div>
    <!--[if lte IE 6.0]>
              </td>
          <td class="ieR"><div></div></td>
        </tr>    
      </table>
      <table border="0" width="100%" cellpadding="0" cellspacing="0">
        <tr>
          <td class="ieBL"><div></div></td>
          <td class="ieB" width="100%"><div></div></td>
          <td class="ieBR"><div></div></td>
        </tr>
      </table>
    <![endif]-->
    <div class="mainB"><div><div></div></div></div>
  </body>
</html>            
            
          

layout.css:

            
* {
  margin: 0;
  padding: 0;
}

body {
  background: url( bg.png );
  color: black;
  font-family: sans-serif;
  font-size: 100%;
  line-height: 1.5em;
  min-width: 440px;
} 
    
.mainT {
  background: url( mainTLBL.png ) no-repeat top left;
}

.mainT div {
  background: url( mainTRBR.png ) no-repeat top right;
}

.mainT div div {
  background: url( mainTB.png );
  height: 220px;
  margin-left: 220px;
  margin-right: 220px;
}

.mainC {
  background: url( mainL.png ) repeat-y left;
}

.mainC div {
  background: url( mainR.png ) repeat-y right;
}

.mainC div div {
  background: url( mainC.png );
  margin-left: 75px;
  margin-right: 75px;
}

.mainC div div div {
  background: none;
  margin: 0;
}

.mainB {
  background: url( mainTLBL.png ) no-repeat bottom left;
}

.mainB div {
  background: url( mainTRBR.png ) no-repeat bottom right;
}

.mainB div div {
  background: url( mainTB.png ) bottom;
  height: 180px;
  margin-left: 220px;
  margin-right: 220px;
}

#content {
  color: #b95207;
  padding-left: 1em;
  padding-right: 1em;
  position: relative;
  top: -30px;
}

h1 {
  position: absolute;
  top: 75px;
  left: 75px;
}

#content .hr hr {
  display: none;
}

#content .hr {
  background: url( hrL.png ) no-repeat left;
  margin: 1em 0;
}

#content .hr div {
  background: url( hrR.png ) no-repeat right;
}

#content .hr div div {
  background: url( hrC.png );
  height: 14px;
  margin-left: 102px;
  margin-right: 102px;
}

ol {
  color: #5ac90f;
  font-weight: bold;
  margin-left: 1.5em;
}

ol span {
  color: #b95207;
  font-weight: normal;
}

li {
  margin-top: 0.5em;
} 
            
h1, h2, h3, h4, h5, h6 {
  color: #5ac90f;
  font-family: "Arial Rounded MT Bold", sans-serif;
  font-weight: normal;
  margin-bottom: 1em;
}

h2 {
  text-align: center;
}

p {
  margin: 1em 0;
}

#spacerL {
  float: left;
  height: 1em;
  width: 60px;
}

#spacerR {
  float: right;
  height: 1em;
  width: 60px;
}            
            
          

lteIe6.css:

            
h1 {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='content/nrkn.png');
  height: 47px;
  width: 113px;
}

h1 img {
  display: none;
}

.mainT, .mainB {
  display: none;
}

.ieTL div, .ieTR div, .ieBL div, .ieBR div {
  height: 220px;
  width: 220px;
}

.ieT div, .ieB div {
  height: 220px;
}

.ieTL {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainTL.png');
}

.ieT {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainT.png');
}

.ieTR {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainTR.png');
}

.ieBL {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainBL.png');
}

.ieB {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainB.png');
}

.ieBR {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/ieMainBR.png');
}

.ieL div {
  width: 75px;
}

.ieR div {
  width: 75px;
}

.ieL {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/mainL.png');
}

.ieR {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/mainR.png');
}

.mainC, .mainC div, .mainC div div {
  background: none;
  margin: 0;
}

.ieC {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/mainC.png');
}

#content .hr, #content .hr div, #content .hr div div {
  background: none;
  margin: 0;
}

#content .hr {
  margin: 1em 0;
}

#content .hr div div {
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, 
    sizingMethod=scale, src='layout/hrC.png' ); 
}            
            
          

That concludes our tutorial. If you know of a cleaner or simpler way to implement any of these things (especially getting the alpha layout to work fluidly in IE 6 in a nicer way), or if you have any questions about how or why I've done things the way I have, or you have any other questions or suggestions, I would love to hear from you. You can email me at nrkn.com@gmail.com.