Grow your CSS skills. Land your dream job.

Moving Highlight

Published by Chris Coyier

I recently noticed a subtle and nice effect in the Google Chrome UI. As you mouse over inactive tabs, they light up a bit, but also have a gradient highlight that follows your mouse as you move around on them.

The guys from DOCTYPE told me it was their inspiration for the navigation on their website. They are doing it just like I would have, with CSS3 gradients and jQuery. So I decided to snag their code and play around with it.

Radial Gradient

The highlight itself will be created from a radial gradient. One possible way to do that would be to create an alpha transparent PNG image and use that. We like to be all hip and progressive around here though. Radial gradients can be created through CSS3, which saves us a resource request and allows easier changing of colors.

Webkit and Mozilla based browsers (only) can do radial gradients. The syntax:

background: -webkit-gradient(
  /* radial, <point>, <radius>, <point>, <radius>, <stop>,  [<stop>, ] <stop> */
  radial, 500 25%, 20, 500 25%, 40, from(white), to(#ccc)
);

background: -moz-radial-gradient(
  /* [<position>,] [<shape> || <size>,] <stop> [, <stop>], <stop> */
  500px 25%, circle, white 20px, #ccc 40px
);

JavaScript Mouse Postion

Now we know how to apply a CSS3 gradient, but the point of this is to apply the gradient highlight where the mouse is. CSS isn't capable of giving us mouse position coordinates, we'll need JavaScript for that. So here is the plan:

  1. When the mouse moves over the area
  2. Detect mouse position
  3. Apply gradient to area in that position
  4. Remove gradient when mouse leaves

We'll be using jQuery.

var x, y;

$('#highlight-area').mousemove(function(e) {

  x  = e.pageX - this.offsetLeft;
  y  = e.pageY - this.offsetTop;

  // apply gradient using these coordinates
	
}).mouseleave(function() {			

  // remove gradient

});

The Trouble With Vendor Prefixes in Values

Vendor prefixes as properties is fine. You want to rotate something cross-browser (with a dynamic JavaScript value), you need to use -webkit-transform, -o-transform, and -moz-transform. If you need to do it with jQuery, you could do:

var angle = 30;

$(".rotate-me").css({
  "-webkit-transform" : "rotate(" + angle + "deg)",
  "-moz-transform" : "rotate(" + angle + "deg)"
  "-o-transform" : "rotate(" + angle + "deg)"
});

That works because each of the properties is different. With gradients, the property is always the same, the background-image property. So just like this won't work:

$(".wont-make-purple").css({
  "color" : "red",
  "color" : "blue" // overrides previous
});

This won't work either:

$("#highlight-area").css({
  "background-image" : "-webkit-gradient(radial, " + xy + ", 0, " + xy + ", " + gradientSize + ", from(" + lightColor + "), to(rgba(255,255,255,0.0))), " + originalBG;
  "background-image" : "-moz-radial-gradient(" + x + "px " + y + "px 45deg, circle, " + lightColor + " 0%, " + originalBGplaypen + " " + gradientSize + "px)"
});

But somehow, inexplicably (and thankfully) this does work:

var bgWebKit = "-webkit-gradient(radial, " + xy + ", 0, " + xy + ", " + gradientSize + ", from(" + lightColor + "), to(rgba(255,255,255,0.0))), " + originalBGplaypen;
var bgMoz    = "-moz-radial-gradient(" + x + "px " + y + "px 45deg, circle, " + lightColor + " 0%, " + originalBGplaypen + " " + gradientSize + "px)";
					
$(this)
	.css({ background: bgWebKit })
	.css({ background: bgMoz });

There must be some magic smarts worked in there somewhere were it doesn't override the previously set values.

Highlighted Tabs

So let's give ourselves the actual tabs in which to highlight. Nothing special in the markup:

<ul class="nav clearfix">
   <li><a href="#">Home</a></li>
   <li><a href="#">About</a></li>
   <li class="active"><a href="#">Clients</a></li>
   <li><a href="#">Contact Us</a></li>
</ul>

And the CSS to make them tab-like:

.nav { 
	list-style: none; 
	border-bottom: 1px solid #a0a0a0; 
	padding: 10px 10px 0 10px;	
}
.nav li { display: inline; }
.nav li.active a {
	position: relative;
	z-index: 1; 
	bottom: -2px;
	margin-top: -2px;
	background: #eee;
	padding-top: 8px;
	padding-bottom: 8px;
}
.nav li a { 
	float: right; 
	text-decoration: none;
	position: relative;
	padding: 7px 50px; 
	margin: 0 0 0 -8px; 
	color: #222;
	background: #d8d7d8; 
	-webkit-border-top-right-radius: 20px 40px;
	-webkit-border-top-left-radius: 20px 40px;
	-moz-border-radius-topleft: 20px 40px;
	-moz-border-radius-topright: 20px 40px;
	-webkit-box-shadow: inset 1px 1px 0 white;
	-moz-box-shadow: inset 1px 1px 0 white;
	border: 1px solid #a0a0a0;
}

So now each of those tabs is the area in which we plan to apply the highlight. For every non-active tab, we'll put together all the things we've covered:

var originalBG = $(".nav a").css("background-color");

$('.nav li:not(".active") a')
.mousemove(function(e) {

    x  = e.pageX - this.offsetLeft;
    y  = e.pageY - this.offsetTop;
    xy = x + " " + y;
	   
    bgWebKit = "-webkit-gradient(radial, " + xy + ", 0, " + xy + ", 100, from(rgba(255,255,255,0.8)), to(rgba(255,255,255,0.0))), " + originalBG;
    bgMoz    = "-moz-radial-gradient(" + x + "px " + y + "px 45deg, circle, " + lightColor + " 0%, " + originalBG + " " + gradientSize + "px)";

    $(this)
      .css({ background: bgWebKit })
      .css({ background: bgMoz });
		
}).mouseleave(function() {
	$(this).css({ background: originalBG });
});

View Demo   Download Files

Comments

  1. Permalink to comment#

    The css vendor prefixes are getting ridiculously confusing and long. Coding/Web work can be frustrating enough with out bringing long mathematical algorithms into it. What’s next { -webkit-triangle: Pythagorus theorem divided by Pi * 8 divided by 0; }

    • Gradients are certainly one of those CSS3 property/values that can feel confusing. Especially when the syntax is as different as it is amongst the different browsers. But consider what a complex task this is. Describing a colored gradient with all the flexibility a designer would ever want is no simple chore. The more powerful and flexible you make it, the more complex the syntax becomes (and people complain about that). The easier you make the syntax, the less powerful it is (and people complain about that).

      I’m sure if you (or me or anybody else) had an idea to make a sytnax for gradients that is both extremely powerful and extremely succinct, people would listen.

    • use compass/sass.. nuff said..

  2. Nice. I just created a version of this myself yesterday using the exact same techniques. It’s interesting to see how standardizing things like gradients and shadows in CSS3 means more and more there’s an obvious “best” way to implement certain design elements.

  3. This:

    $("#highlight-area").css({
      "background-image" : "-webkit-gradient(radial, " + xy + ", 0, " + xy + ", " + gradientSize + ", from(" + lightColor + "), to(rgba(255,255,255,0.0))), " + originalBG;
      "background-image" : "-moz-radial-gradient(" + x + "px " + y + "px 45deg, circle, " + lightColor + " 0%, " + originalBGplaypen + " " + gradientSize + "px)"
    });

    didn’t work because, what was really sent to jQuery.CSS method was this object:

    {
      "background-image" : "-moz-radial-gradient(" + x + "px " + y + "px 45deg, circle, " + lightColor + " 0%, " + originalBGplaypen + " " + gradientSize + "px)"
    }

    Which differ from the second example which works…

    $(this)
            .css({ background: bgWebKit })
            .css({ background: bgMoz });

    … because as we can see there are multiple call on .css() so bgMoz didn’t override bgWebkit. I didn’t check this, but it seems that jQuery understand that “background-image” can has multiple -prefix- values.

    • Right.

      There must be some magic smarts worked in there somewhere were it doesn’t override the previously set values.

      jQuery must just know not to unset certain properties with certain values, like gradients. Because if you tried the same double .css() idea with a property like color it would certainly get overridden.

  4. People who use Windows Vista/7 will also be familiar with this technique. That same effect happens when you hover over the taskbar items. It’s a neat little gem.

    Another cool thing Windows Vista/7 does is, it changes the highlight color of the taskbar items based on the colors in the icon. I read somewhere they created an algorithm that finds the most prominent color in the icon and sets that as the highlight color. For example, the highlight color for Firefox is orange because the most prominent color is the orange on the fox. Would be neat to try to tackle using web technologies… *wink wink* :P

    • That’s will be really interesting, but i think that is really difficoult to implement at the same time couse, as far as i know, there’s no native way to read the colours of an image using javascript (without using canvas).
      So you could hardcode the whole thing by creating different classes and script for each color you want to use.

    • Permalink to comment#

      Hey i was just about to point this out and did a ctrl + f to see if anyone had pointed this out.

      Great minds… James : – )

  5. It’s all nice, but what about browsers which (will) support the the properties without prefixes? You are always omitting them! If you are doing this to save space/bytes, I’d say you’re ommitting the most important parts. It’s the code that’s working NOW, and is not anything close to be future-proof.

    • What would you include? Opera doesn’t support gradients yet, and the two browsers that have do it in totally different ways. I would never implement something on a theoretical assumption of how a browser might implement something in the future (unless it’s the absolute final spec).

    • At least box-shadow and border-radius in the css.

      I guess it’s a common best practice to include a non-prefixed version of a rule after the prefixed ones to be sure that browsers that implement it use it.

      And by the way webkit is changing its gradient syntax in favor of the standard one.

    • What will be the standard syntax for gradients?!

    • It’s the one Firefox implements.

      http://dev.w3.org/csswg/css3-images/#gradients

  6. Nice effect, but you might wanna add the fade-in effect when mouse initially hovers, otherwise it just “pops”. It’s a bit more subtle in Chrome :)

    • I worked on that for a long time, I was planning to include a version that did that, it was just FAR more complicated. It involved adding an additional element only for the highlight, which had to be specially positioned, and then the text needed another element as well to keep it above the gradient. And there were other troubles as well. I thought it complicated the tutorial too much to be worth it, but I agree, much nicer with a fade.

    • Pradeep
      Permalink to comment#

      hey chris –

      Nice effect -

      there is a typo in the heading “JavaScript Mouse Postion”

  7. Nice subtle effect; this is the perfect kind of thing to stick into a design without mentioning it to anyone, just as some added detail.

  8. Permalink to comment#

    Awesome tutorial. Never knew that about CSS3 gradianets!
    Thanks, Chris

  9. Permalink to comment#

    Wow, this is so cool!
    I like you tutorial. Thanks for sharing.

  10. wow its a cool, can be light there…
    thanks^^

  11. Permalink to comment#

    I always wanted to know how to do this, and searched numerous times on Google trying to find it. Finally someone has explained how it’s done!

    Thanks!

  12. Nice one! I’ve been attempting to make a couple of variations to my website, and this one would be far too cool to try, thanks so much for posting this.

  13. Permalink to comment#

    Call me crazy but I’ve never seen this effect in Chrome and just specifically looked for it and it is definitely not happening; neither on Vista or Ubuntu. Any ideas?

    • It happens on windows 7 taskbar if you enable the AEREO theme.

    • JGarrido
      Permalink to comment#

      Same here, I’m on Windows 7 Enterprise using Aero running Google Chrome Beta 9.0.597.45 without any custom theme or even extensions, and I don’t see that effect; only on the Windows Taskbar (as others have mentioned).

  14. Huh, I was noticing a higher number of new Twitter followers and YouTube subscribers today. Thanks for the shoutout! :) Jim actually wrote that code, not me, so props to him for sure.

  15. Permalink to comment#

    its nice, agree to pavel that it is best practice to include a non-prefixed version of a rule after the prefixed ones .

  16. Permalink to comment#

    Hi Chris,

    I have a question that has nothing to do with the post but thought it was the best way to contact you sorry for that but I did that Lynda.com Tutorial you did which BTW I recommend to everybody and I have a little problem how to publish the wordpress website after building it in wamp I mean the path and the database thanks for your help

  17. Very cool,

    This was one of the first things I noticed about google chrome’s UI and wondered how someone would do it for the web. This is such a great tutorial.

    I think the only thing that would be missing is making the highlight effect fade in and out instead of just appear when hovering over the different “tabs” or “links”, just like Chrome does. Then this would be flawless.

    Great tutorial; can’t wait to try it out myself. This is a great subtle effect with multiple possibilities .

  18. Josh
    Permalink to comment#

    It works only if you have the standard google chrome v8 theme installed

  19. That is a really cool effect. I might try that some projects in the near future. I like reading your posts where you see something interesting and then try and recreate it as simply as possible for the web. Very inspiring.

  20. Gyonka
    Permalink to comment#

    Safari?

  21. Is it just me, or do the tabs (in the live demo) break in Chrome?

    The backgrounds behind the curved edges are white…

    • Chris
      Permalink to comment#

      Great demo – made me want to copy this for our site’s nav – however I noticed that in Chrome 8 (on PC only) the curved edges are in fact white. Is there a workaround for this?

    • Permalink to comment#

      That’s a bit odd…
      toss the -webkit-box-shadow: white 1px 1px 0px inset; and it works in Chrome 8… yet to see what that does to the other webkit browsers though…

  22. sayid
    Permalink to comment#

    Hi,
    thanks for this nice tutorial. I really liked it
    I thins one addition will make this highlight more fantastic which is fading out the highlight when u move out to another tab :)

  23. @Chris

    It’s the box-shadow that is in white

  24. Very cool,

    This was one of the first things I noticed about Google chrome’s UI and wondered how someone would do it for the web. This is such a great tutorial.

    Your tutorials keep getting better and better!

  25. Keyamoon
    Permalink to comment#

    Nice effect. Any reason why you didn’t use e.layerX and e.layerY? (e.pageX – this.offsetLef) can be wrong if the container doesn’t touch the edges.

    https://developer.mozilla.org/en/DOM/event.layerX

  26. Permalink to comment#

    i try to use this code on my page, but the gradiant is still always on the wrong position.

    when i use e.layerX it works only for the first list element, at the second element the gradient is on the wrong position again.

    anybody knows where iam go wrong with that?


    %nav
    %ol
    %li= link_to "link1", root_path
    %li= link_to "link2", root_path
    %li= link_to "link3", root_path


    $('nav li').mousemove(function(e)....

  27. I’m just think about it and google it. Thanks for realisation !

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".