Showing posts with label css. Show all posts
Showing posts with label css. Show all posts

Expanding a DIV to Size of 'background-image'

I struggled with this for a bit yesterday and found naught but forums telling me 'it can't be done,' or 'why are you doing that, it's stupid,' so I thought I'd share the solution I developed.

The Problem


Here's the scenario: You have a DIV that will contain some text. You have different background-images that you want displayed under the text depending on which page is showing. But the background images are of varying sizes.

What you want, therefore, is a way to expand your DIV to the dimensions of whatever background-image you happen to stick inside it, without having to explicitly declare width and height values, either at your style sheet or in-line.

...and now you're off the rails.

One could surely pose a number of "why not" scenarios, such as: 'Why not just declare the width and height in-line?' or 'Why not just make the text part of the images?' Or one could pontificate upon the myriad of client and server-side solutions for querying the image size and declaring the DIV dimensions equal by script. But instead, let's just assume that we are coders and geeks, and that if there is a simpler solution requiring less explicit declaration and more uniform code distribution, then that elegance of design is our tacit desire and reward.

Solving


First off we must accept and acknowledge the limitations before us. The 'background-image' declaration does not care for width and height. It will fill a DIV with the image you choose, but it cannot under any circumstances be used to alter the boundaries of the DIV. Give that up right now!

Besides which, a 'background-image' declaration here would have to be placed in-line to meet our need of changing it across different pages, and I think it a best-practice to avoid in-line styles wherever possible, favoring HTML tags for dynamic content.

The solution, therefore, must be implemented from within the DIV. Anything placed inside a DIV of undeclared width and height will expand that DIV to fit. So in place of a 'background-image' declaration, we will set an <img> tag inside the DIV. The IMG's dimensions will be determined upon page load and the DIV will expand to match.

<div id="my_div">
<img src="images/bg1.gif">
</div>


Now comes the text we want displayed atop our IMG. Just shoehorn it in there with a <p> tag. We'll make it work in a minute.

<div id="my_div">
<p>Contact Us!</p>
<img src="images/bg1.gif">
</div>


So now we have a DIV containing two blocks: P and IMG. What we want is for the P tag to overlay the IMG tag, creating the effect of IMG as a background. We accomplish this by use of negative margins and 'z-index.'

First off, the P tag has its own vertical margins which we'll want to zero so we can have a simple text-block with no extraneous white-space to work with. As for making our IMG into a background, we can force the P tag to overlap IMG's boundaries by giving IMG a negative 'margin-top.'

#my_div p { margin: 0 }
#my_div img { margin-top: -80px }


If you looked now, you would find that the text of the P tag has more or less disappeared behind your IMG. Here the IMG tag has a higher natural z-index than the text, meaning it is being drawn after, or overtop of P. We can change that by giving the IMG a negative z-index, which places it dimensionally beneath P. Note here that when you set a 'z-index' you must also make the 'position' of the element explicit, or it will not take effect.

#my_div p { margin: 0 }
#my_div img {
margin-top: -80px;
position: relative;
z-index: -1;
}


Finally your P tag should be sitting proudly atop your IMG which has effectively become a background-image. Huzzah! Full code follows...

The Solution



<head>
<style type="text/css">
#my_div p { margin: 0 }
#my_div img {
margin-top: -80px;
position: relative;
z-index: -1;
}
</style>
</head>

<body>
<div id="my_div">
<p>Contact Us!</p>
<img src="images/bg1.gif">
</div>
</body>


Beautiful isn't it? Just four lines of CSS and you can now switch out one background for another of an entirely different width - and even a different height if the design is right - without so much as a whisper regarding those properties. All you declare explicitly is how far down to drop your text over the image. "Can't be done," my eye!

There are some pitfalls to watch out for when implementing this method. For instance, it might seem just as easy to make the 'margin-bottom' of P a negative instead of the 'margin-top' of IMG, but if you do it this way Internet Explorer, in particular, just might chop off the vertical edges of your IMG if P's block fails to fill the DIV out to the full height of IMG. This can be countered by augmenting line-height, but then you're fighting battles you don't have to. Yeah, I may have learned that one the hard way.

There you go: Dynamically expanding a DIV to the size of a background image. Tada!

Collapsing Margins / Borders in CSS

Here's a simple example of the kind of unexpected behavior caused by collapsing margins in CSS.

<div style="background-color: red">
<h2 style="background-color: cyan">
Hello flawed world!
</h2>
</div>


The Problem


When displayed, (tested in Firefox 3.5.7) the code above produces the text "Hello flawed world!" on a cyan background, and nothing else. This is not as expected.

The <h#> tags have a built-in top and bottom margin to them of around 20px. Now, a margin, specifically, is white-space surrounding a tag, outside of it. Margins push out from a block's position to create empty space around it. With this in mind, the code above should produce 20px worth of red - the background color of the <div> containing the <h2> - above and below our cyan-backed text. But it doesn't.

Meet the concept of collapsing margins. Because nothing whatsoever has been placed in the space between the div's borders and the h2's borders, the browser is taking it upon itself to remove what it perceives as empty-space by collapsing the margins together.

I don't know why it might be beneficial for the browser to collapse margin-space this way, nevertheless here we are. We must work around it.

The Solution


In order to get the desired margins to appear we must prevent the browser from perceiving them as empty. The simplest, most unobtrusive thing to shove in the gap is a bit of padding. Padding is sort of the inverse of margins, in that it creates white space within the block, rather than outside it; pushing inward from the borders. 1px worth of padding will keep your margins and borders from falling in on themselves.

<div style="background-color: red; padding: 1px 0;">
<h2 style="background-color: cyan">
Hello flawed world!
</h2>
</div>


The corrected code inserts 1px worth of top and bottom padding to the div. Again, note that the padding is added to the outer container. It pushes inward, giving the previously empty space between the edge of the h2 block and the edge of the div block a tiny bit of content. In the example I've used the 2-value format of the 'padding' style, where the first value represents top and bottom padding, and the second left and right.

I've always looked at margins and padding as being the same thing in a different direction. Apparently this isn't entirely true. For some reason the CSS standard views white-space created by margins as sacrificial when unoccupied, but maintains a hallowed view of white-space declared as padding. Preserving padding makes sense. Not preserving margins... I'm sure there's a reason, but from the coders perspective it seems an inconsistency.

Posting Blocks of Code on Blogger

I had a hell of a time getting code blocks to look and behave as I wanted across the varied browsers on Blogspot. Simply, I wanted the white space preserved so I could indent lines, and I didn't want my text wrapped. Sounds like the <pre> tag should cover it, right? Ha! There's a whole litany of issues that arise in Internet Explorer when invoking the horizontal scroll-bar; Blogger's template was telling IE to word-wrap the <pre>; And even Firefox - though rightly - managed to give me a bit of the run around. Before I get into it all here's the final, working CSS I've discovered for code blocks. Copy it now! (Though don't run off without reading the Template Tip toward the bottom of the post.)

pre.code {
width: 94%;
overflow: auto;
overflow-y: hidden;
display: block;
line-height: 1.2em;
background-color: #f5f5ff;
padding: 0.5em 1em;
border: 1px solid #bebab0;
padding-bottom:15px;
}


From the CSS you can divine that I'm using <pre class="code"> to wrap my blocks. I started out using <pre><code>, and perhaps that could be made to work as well, but somewhere throughout my cross-browser compatibility struggles I ditched the code tag. It's redundant anyway. Pre tags preserve your whitespace and set a monospace type. Code tags set a monospace type. So why bother saying the same thing twice? Why? Why say it twice? Seriously, why should I say it twice?

I also had a lot of trouble with the <pre><code> arrangement where, because I was wrapping one in the other, IE would favor the <code>, ignore the <pre>, eat my whitespace and word-wrap my text. Meanwhile Firefox looked perfect - that is until you grabbed the scrollbar. Scrolling the code block would leave the background-color and border behind, sliding us into a void of undeclared white-space. In the end it proved too much hassle keeping the redundant <code> tag in just for it's own sake.

Even with the <code> tag out the window, I ran into the much maligned IE v-scroll bug. When a Mozilla browser adds a horizontal scroll bar to a block, it does it on the outside of the block. IE does it on the inside... You say tomato, I say flibbidy floo. Problem is, IE doesn't seem to account for the space the h-scroll has come to occupy. It doesn't stretch the container out, it just slaps the bar over the top of it. Now you've got a horizontal bar blocking out the last line in your <pre>, which necessitates that IE stamp a vertical scroll bar onto the block as well!

Sure, this is an ugly and unnecessarily contrived way of getting the job done: granted; but there's a bigger downside than aesthetics here. If you try to post a single line of code into a <pre>, Internet Explorer's intrusive horizontal and vertical scroll bars completely obscure your text. Scroll up, scroll down, the most you'll ever see is a sliver of what's there. Your one-line block of code is now a tasty block of scroll bar.

The simple style sheet above cures all that ails. I'm not going to explain it, just take it and go! Oh, but not until after reading this next part, of course.

The Aforementioned Template Tip


Even with the CSS implementation described here IE continued to word-wrap my code blocks. Testing outside of Blogspot proved the CSS good. So I started peeling through Blogspot's template, looking for some overriding declaration. I didn't catch it myself, but eventually found an old post on the Blogger Help forums containing the offending declaration's locale.

#main-wrap1 {
...
word-wrap: break-word; /* fix for long text breaking sidebar float
in IE */
}


If IE refuses to respect the <pre> tags on your Blogspot site, search your template for a word-wrap: break-word; being declared in the #main-wrap1 id and comment it out. If your template doesn't have a #main-wrap1 id, then you'll have to poke around until you find what your template calls it.

Caution: You'll likely find more than a few word-wrap: break-word; declarations throughout the template, and it's best not to remove them all. It should be obvious - by their names - which declarations belong to the sidebars, headers, footers, etc., and which belongs to the div(s) where your posts live.

Why, you guys? Why should I say it twice?