Form Highlighting Redux

Monday, March 1, 2004 at 6:57 am | Comments off

It seems the form highlighting effect I was experimenting with ended up being fairly well liked overall, so I thought I'd write up an explanation of how this was done, and how you can implement it on your own sites, if you wish.

First, I realized that I was going to need to create a "custom border" effect, so to speak. I decided on an approach similar to the one that Søren Madsen exemplifies in his A List Apart article, "CSS Design: Creating Custom Corners & Borders". You'll find that my method of creating the borders is nearly identical to that of Søren's with one main exception - my method has no height constraint. To save time and space, I'll simply recommend that you read Søren's article, and will explain the one change I did make.

Ok, the change. I wasn't happy with the idea of using one image for the top and left side of my elements. Since I wanted to keep this technique fairly portable, not knowing if I will be using it in the future, I wanted to account for any height, rather than needing to know the maximum possible height as you would with Søren's method. I also decided that width was not much of an issue with me, as this site has a fixed width, and generally speaking, I'd be more likely to know the width of my elements than the height. Structurally speaking, the only major change from my method to that of Søren's is the fact that mine requires one more block-level element in the header element. I chose to use divs, as this is only being used to convey a visual effect, and divs do not have any semantic meaning. Once I added that div, my code looked like this: (editorial line breaks designated with »)

<div class="contact_form">
  <div class="boxhead">
    <div></div>
  </div>
  <div class="boxbody">
    <input type="text" name="name" id="name" »
    onfocus="setBg(this);" onblur="removeBg(this);" />
  </div>
  <div class="boxfoot">
    <div></div>
  </div>
</div>

One important thing to note, is that you will want to compress the entire code above on to one line, with no spaces between the elements. Stupid? Yeah, probably. The reason? Once we get to writing the JavaScript, we are going to browse through the DOM to set the necessary styles. Of course, the browsers can't play nice together, and if there are spaces between the elements, Gecko browsers assign them a child node, while IE does not. Typical. Anyway, rather than battle this in the JavaScript, I decided to just throw all the source code on one line. Such is life.

But enough of that, let's see the CSS. I'm only going to explain the bare minimum, I'm going assume you know the basics or can figure them out.

.contact_form {
	width: 160px;
	font-size: small;
}
.contact_form input {
	border: 1px solid #b2b2b2;
	width: 146px; /* IE */
}
html>body .contact_form input {
	width: 148px; /* Others */
}
.contact_form_box {
	width: 360px;
}
.contact_form_box textarea {
	border: 1px solid #b2b2b2;
	width: 346px; /* IE */
	height: 150px;
}
html>body .contact_form_box textarea {
	width: 348px; /* Others */
}
.boxhead {
	height: 5px;
	font-size: 0px;
}
.boxhead div {
	height: 5px;
}
.boxbody {
	margin-left: 5px;
}
.boxfoot {
	height: 5px;
	font-size: 0px;
}
.boxfoot div {
	height: 5px;
}

Basically, I'll just say that we need to set a width on .contact_form, to constrain our borders. If you don't understand why I used the width's that I did, read about the box model. So why did we specify different widths on our input and textarea styles for IE than Gecko browsers? Because IE sucks, that's why. ;) Also, I needed to specify a font-size of small for the .contact_form div. Not exactly sure why, but if this wasn't set to a value such as small (.9em, 10px, etc also worked), there were small gaps in Firefox and Netscape 7 (and possibly other browsers). I didn't play around with it long enough to try to figure out why, or if there was a better fix. If anyone know's why this happens, drop me a line.

Finally, let's get to the JavaScript:

if (navigator.userAgent.toLowerCase().indexOf»
('applewebkit') == -1) { // filter out Safari, »
as it has it's own form field highlighting
  // preload
  left = new Image();
  left.src = 'images/left.png';
  top_left = new Image();
  top_left.src = 'images/top_left.png';
  top_right = new Image();
  top_right.src = 'images/top_right.png';
  right = new Image();
  right.src = 'images/right.png';
  bottom_left = new Image();
  bottom_left.src = 'images/bottom_left.png';
  bottom_right = new Image();
  bottom_right.src = 'images/bottom_right.png';

  function setBg(elem) {
    if (document.getElementById) {
      box = elem.parentNode.parentNode;
      box.style.background = 'url(images/left.png) »
      repeat-y left';
      box.childNodes[0].style.background = 'url(images/»

      top_left.png) no-repeat top left';
      box.childNodes[0].childNodes[0].style.background = »
      'url(images/top_right.png) no-repeat top right';
      box.childNodes[1].style.background = 'url(images/»
      right.png) repeat-y right';
      box.childNodes[1].childNodes[0].style.border = '1px »
      solid #fff';
      box.childNodes[2].style.background = 'url(images/»
      bottom_left.png) no-repeat bottom left';
      box.childNodes[2].childNodes[0].style.background = »
      'url(images/bottom_right.png) no-repeat bottom right';
    }
  }

  function removeBg(elem) {
    if (document.getElementById) {
      box = elem.parentNode.parentNode;
      box.style.background = 'transparent';
      box.childNodes[0].style.background = 'transparent';
      box.childNodes[0].childNodes[0].style.background = »
      'transparent';
      box.childNodes[1].style.background = 'transparent';
      box.childNodes[1].childNodes[0].style.border = »
      '1px solid #b2b2b2';
      box.childNodes[2].style.background = 'transparent';
      box.childNodes[2].childNodes[0].style.background = »
      'transparent';
    }
  }
}

The first thing to do is preload all the images. This will keep the users from seeing the images load the first time the focus on a form field, which frankly ruins the effect. Next, we set up our setBg() function, which simply works it's way through the DOM, setting up some CSS properties. Since explaining the DOM is way, way beyond this article, I'll just point you to the specs. Finally, we set up a removeBg() function, which removes (or sets back to the defaults) the backgrounds and border that we just set up.

That should be it. Now you can take a look at an example of what this looks like. Also, I've zipped up all the files needed to do this to make it easy on you. I'm so nice.

Edit: I've made some changes to the JavaScript, due to the issue that Justin French brought up of competing with Safari's own form styling. Many thanks to Justin for testing this out for me in Safari.

Comments

peofeo (nick)
March 3rd, 2004
6:20 AM | #

This looks really nice... but that is a lot of code for this affect. This looks really nice and all, but why not just use a similar effect like
<input type="text" onfocus="style.borderColor='#000000';"

onblur="style.borderColor='#cccccc';" style="border:1px solid #cccccc;" />
?

peofeo (nick)
March 3rd, 2004
6:21 AM | #
Ryan
March 3rd, 2004
6:53 AM | #

Yes, I could have gone that route, but what I really liked was the faded iTunes/Safari look that this method affords. Simply changing the border color was not the effect I was shooting for.

Brad
March 6th, 2004
12:41 AM | #

Neat effect! But it looks totally ghetto in Safari which already highlights the borders! So it ends up with a weird double-border effect.

Ryan
March 6th, 2004
1:17 AM | #

Ah, I forgot to update the example file and .zip download. It should all be good now, thanks for pointing that out.

Ritz
March 8th, 2004
9:16 PM | #

Looked at this in many browsers and it bomb-tastic. Great job! I noticed explorer on pc has to download the pics each time and on a slower browser and it's a bit wierd trying to fill out a form when the field dissapears for a couple seconds while it downloads each time. Just another reason to consider onfocus or preloading the images, but this is very nice.

[m]
March 13th, 2004
8:43 PM | #

IE is stupid, we all know that. Among other thing, it gives us no caching of pics inseted with css, mesning it has to download the pic every time it is 'removed' from the document like Ritz pointed out.

A work around for that is not to remove the background, but to simply move it out of the way: Fast rollovers, no preload. IE doesn't need to remove the background, removing the need to reload the picture.

I don't know how to fit that technique in exactly, but that shouldn't be too hard.

Ryan
March 13th, 2004
8:51 PM | #

Does this mean you are having trouble with this in IE? It works fine for me in IE 5, 5.5 and 6. No lag at all.

How
March 19th, 2004
6:41 PM | #

Looks pretty - nice one...

However, it is a lot of code, and I'm not sure if normal peeps (i.e. non geeks like silver surfers) will just get freaked out by it.

Having said that, I used a similar effect on a website I manage to highlight fields that haven’t been filled in correctly....

!!blue
April 3rd, 2004
8:47 PM | #

I'm using IE 6.0 on a Win 2000 Prof Ed machine and the effect I get is the blue border starts out at the top left corner and sorta "fills in" towards the bottom right corner.

Even now as I click over to the email addy box, the effect happens again. Looks cool, but I know that's not the effect you're going for cuz in Mozilla Firefox 0.8 it just highlights the whole textbox.

Looks great, tho, I like it!

Remko
June 10th, 2004
4:36 PM | #

Looks great. Preloading the images using Javascript would make this script even nicer :).

Ryan
June 10th, 2004
4:54 PM | #

It does preload the images. ;)

rehfeld
November 30th, 2004
1:50 AM | #

to anyone who is having problems w/ IE re-downoading background images everytime the image is swapped/changed.

THIS IS THE FAULT OF YOUR SERVER.

your server is not sending the proper expires headers to browsers, and they can rightfully re-download as they wish. if your server does not tell them how long to keep the image, it has to guess. IE often assumes css background should be re-downloaded every time to make sure it has the latest version of your image. although annoying, it is not wrong in its assumption.

to tell all browsers to cache the images, you can use apache's .htaccess

put this in a .htaccess file in your images folder. this will cause ALL files/images in that folder to be cached for 2 months.

ExpiresActive On
ExpiresDefault "access plus 2 months"

once you do that apache will send the proper headers, and IE will behave how your expecting.


even those who do not think they have problems, should do this. caching is good when it comes to images. also, some of your visitors may have different default caching settings for their browser than you do, and they may have the image download "lag" while you dont. this will fix it.

DrSlump
December 24th, 2004
12:07 AM | #

Nice article, I was going to implement it after seeing the effect in a friend's mac but luckly I found your nice article :)

Analyzing a bit the code I think that the whole process can be simplified by adding a bit more of JS. Since JS must be enabled anyway to see the effect, why don't create the extra "divs" using the DOM?
It'll make the script some lines longer but for large forms it'll be a real time saver! Not to say that it could be easily reused or even applied to older forms :)

I'm gonna play a bit with the code and will post here any enhancements.

ciao, Ivan

George
December 12th, 2005
11:48 PM | #

Does this work for a scoll down menu?

Mike
March 28th, 2006
3:41 PM | #

How do you change the width of the text boxes while keeping the effect?

Comments are no longer open for this entry