Worst name ever, but I was having a hard time naming it and that seemed to fit the bill. This is the end result:
It covers a variety of things I thought were interesting:
- jQuery 1.4’s new element creation syntax which is pretty cool and we haven’t covered
- Writing a little plugin to prevent repeated code (and keep it in the spirit of jQuery)
- Touches on what I am starting to consider object-oriented CSS
The Goal
What we have here is some boxes of content. The goal is that when you mouse over them, they darken and a link appears in the exact center of them. I realize this isn’t going to be an ideal UI choice for many things. Making things unreadable as you mouse over them is an unusual design choice. But it might be perfect in some circumstances. Moral of the story: don’t make snap judgments based on demos.
HTML
Just a div with some text in it…
<div class="widget widget-one rounded">
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>
</div>
What you might want to do is include a hyperlink in each widget that will go to the same place the link we are going to add with JavaScript does (degradability). Again, this is just a demo and the circumstances will be different in live scenarios.
CSS
I’m calling our little boxes “widgets”. Notice I gave the div wrapper a class of widget. This is the basic widget:
.widget {
width: 300px;
padding: 20px;
border: 1px solid #999;
margin: 0 20px 20px 0;
float: left;
position: relative;
}
But note that in the live demo and the image above, the widgets have rounded corners. We all know how to make rounded corners in CSS by now. There is the standard border-radius and its sister vendor extensions. Why aren’t we adding them right to the widget CSS itself? This is where the object-oriented CSS comes in…
The way my brain and CSS typically work is that I try and keep the HTML as absolutely clean and semantic as possible. I was always taught than using a CSS class name like “red” is mega bad practice because “red” isn’t semantic, is specifically descriptive. Instead you should use something like “important” or “warning”, because then later if you decide that those things should been green instead of red, your “red” class is embarrassingly irrelevant.
Object oriented CSS throws some of that thinking on it’s head. Object oriented CSS uses class names specifically to describe design characteristics of that “object” (page element). Notice I also gave the widget a class of “rounded”. In our CSS, the rounded class will be this:
.rounded {
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
}
Blasphemy? I used to think so, but lately I’m really liking this approach. There may be 20 things on the page that are “rounded”. Now all we need to do is apply the rounded class name to each of them and they become rounded. Need to adjust that border radius? Adjust it in a single place and all the rounded objects match. Without doing it this way we’d be updating 60 different places in our CSS.
This may be right for some sites and not for others. That feels like a cop-out thing to say, but it’s true. Perhaps it’s unrealistic for a site with loads of legacy content. It might be brilliant for a brand new site with less content where there just nearly just as much CSS as there is HTML.
The JavaScript is ultimately going to apply a new div to our widgets, which will have the job of darkening the widget. We’ll call this class “overlay”. Here is the CSS for that:
.overlay {
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
background: url(../images/black75.png);
background: rgba(0,0,0,0.6);
text-align: center;
}
It uses RGBa to do the darkening for modern browsers (more flexibility, may save an HTTP Request), and falls back to an alpha-transparent PNG (for IE 7). Another way to deal with this would be to use a solid black background color and use regular transparency, but since in this demo we’re going to have the “middle box link” inside the overlay, and we don’t want that to be transparent as well, this works fine.
jQuery JavaScript
To repeat our goal quickly: when a widget is rolled over, we want to darken it and add a link in the dead center of it. In our demo we have four widgets. Some of them are of a different height, so we’ll need to accommodate for that. Each widget also has different text displayed as the “middle button link” and also a different URL. That means some of this code will be different, but much will be the same. This calls for some abstraction. In JavaScript we could go right for a custom function, which we could call for each widget. In the spirit of jQuery though, let’s make it a little plugin. This means that we’ll keep intact sweet jQuery-specific features like chainability.
Notice that we’ve added a unique class name to each widget. That is so we can target widgets individually with that hook in the JavaScript. We could have used ID’s as well, which is a bit more efficient, but hey, you might have two widgets on the same page that use the same text and link. Using the same class name will work in that circumstances while ID’s would not. This is the abstraction we desire:
$(function() {
$(".widget-one").middleBoxButton("Read More", "#read");
$(".widget-two").middleBoxButton("Go to the Store", "#store");
$(".widget-three").middleBoxButton("Continue", "#more");
$(".widget-four").middleBoxButton("Behold!", "#bazinga");
});
Of course “middleBoxButton” isn’t a function. That’s what we’ll be creating as our plugin. Here’s the whole shebang:
var $el, $tempDiv, $tempButton, divHeight = 0;
$.fn.middleBoxButton = function(text, url) {
return this.hover(function(e) {
$el = $(this).css("border-color", "white");
divHeight = $el.height() + parseInt($el.css("padding-top")) + parseInt($el.css("padding-bottom"));
$tempDiv = $("<div />", {
"class": "overlay rounded"
});
$tempButton = $("<a />", {
"href": url,
"text": text,
"class": "widget-button rounded",
"css": {
top: (divHeight / 2) - 7 + "px"
}
}).appendTo($tempDiv);
$tempDiv.appendTo($el);
}, function(e) {
$el = $(this).css("border-color", "#999");
$(".overlay").fadeOut("fast", function() {
$(this).remove();
})
});
}
So when a widget is rolled over, a brand new <div> is created. This div has a class of “overlay” and “rounded”. We already know what “rounded” means (OOCSS). We also already know what the “overlay” is, it ensures the div is exactly the same size is the div by setting absolute positioning and the top, right, bottom, and left values all to zero and deals with darkening. Nothing is done with this div quite yet though.
Then we create an anchor link. We give it the text and href attribute that we passed the plugin explicitly for this purpose. Then we append this link to the overlay div we just created and then append them both together to the widget. This element creation business is made nice and succient by jQuery 1.4’s new element creation syntax. This part:
$("<a />", {
"href": url,
"text": text,
"class": "widget-button rounded",
"css": {
"top": (divHeight / 2) - 7 + "px"
}
});
Notice that I have all the “keys” (the part before the colon) in quotes. Most of the keys will work without quotes, but I was having issues with the key “class” not working in IE. Turns out it’s not a bug, it’s just that “class” is a reserved word in JavaScript, and it’s bad form not to quote it. Also turns out it’s good form to just quote everything when using JSON-like syntax like this uses.
Since one of our goals here is that the link appears in the exact middle, let’s talk about that. Centered it horizontally is easy, the overlay div has text-align: center, so that’s that. Vertical centering is a bit trickier. There are CSS ways of dealing with it, but none that are super clean and easy. We’re using JavaScript anyway, so let’s exploit that. We’ll just measure the height of the widget, cut it in half, and kick the link down that distance. The one kinda kludge-y part is the value “7”. You gotta hate hard-coding a value like that when what I really mean is “half the height of the button”. The problem is I couldn’t calculate the height of the button because it doesn’t exist yet when I’m setting the other attributes. If you have an idea, let me know. Also note, the height of the widget is calculated not just by using the .height() function, but by using that and adding in the top and bottom padding.
The callback function for the hover (mouse out), just sets the border back to its original color and fades out (then removes) the overlay div. Done!
This is a really neat effect.
Couldn’t you use outerHeight() for the height of the widget?
I think you would use innerHeight(), because outerHeight() gives you the height including the padding and border, whereas innerHeight() gives you the height including the padding but not the border.
Sorry, should have been more clear… outerHeight(false) returns height+paddingTop+paddingBottom. Optional parameter determines whether margin is included.
You should call it an “Overlay Link”. Just my two cents.
I think this would be great for overlaying links over images, Flash style, such as hiding a play button on a image that links to a video until the user hovers over it.
Wow
That’s great !
Cool
Great article, but I disagree with the term object oriented CSS, because it is not object oriented at all.
Object oriented primarily means combining both data and logic into a single entity, an object. Since your CSS contains zero logic, it is not object oriented, it’s just a simple data structure that groups attributes, called a “struct” in many programming languages.
There is also no real inheritance, encapsultation or polymorhism here, since you are applying multiple classes to your elements.
I’m not saying this because I like to play word games. It’s just that using the term this way may confuse people.
Check out the slideshow and explaination here:
http://ajaxian.com/archives/nicole-sullivans-object-oriented-css
This is just a micro-micro example of it as a whole.
The term is a little ridiculous, but I’ve come around on it a little bit. I’d say that, for the most part, people that deal with “real” object oriented stuff don’t do a lot of CSS. And people that do a lot of CSS, don’t have a lot of object oriented programming experience this definition would conflict with. Feel free to lance me for that typecasting. =)
You’re confusing the term “object oriented” with “object oriented programming”. Something can be object oriented without necessarily correlating directly to the OOP paradigm.
I suppose. Admittedly I don’t know a ton about all this from the technical/history side. But in a quite literal sense, it makes sense to me. We’re going to focus on the individual “objects” themselves. So if an “object” wants to be rounded, it needs a class name of rounded. If it wants to be 200px wide, maybe it needs a class name of “grid_3”. If should be taking on the properties of a block of news, it should have the “news” class.
It’s the idea of bringing the styling possibilities back into the HTML. So when building a new area of content, you have this big toolbox to work with focusing on the object’s properties. Rather than, perhaps, needing to write more CSS to accommodate this new content without specific hooks.
Object-oriented CSS is a misnomer, end of story.
Ideally, the way to deal with your ‘.rounded’ dilemma would be to have a CSS class called ’rounded’, and then in the CSS file map that class to the semantic elements that need it. So if the sidebar is rounded, you’d set sidebar to rounded in the CSS, which is where this sort of information lives.
Unfortunately you can’t do that in CSS, so we have to choose between polluting the HTML with style information, or repeating ourselves in the CSS.
Chris,
Thanks, btw
.outerHeight() returns height including padding and border, as well as margin if required.
Nice article. Very useful. Going to try it!
This is one of those things I have had on my list to play with. Nice to have a tutorial to start with! Thanks!
Bazinga! :)
Nice effect, Chris.
Awesome! Thanks Chirs! :)
How to make this Keyboard and screen reader user compatible?
All you would need to do is put the same anchor link that the plugin applies in the text inside the widget. In fact you could alter the plugin to look for that, remove it, and use in in the overlay. Depends on the scenario…
Please make it like so. and one more thing if JS is disabled then link is not accessibility also.
Awesome stuff, Chris!
Chris,
Great article, like always. You asked if we might have some ideas about the “7” in the height of the button. Maybe you could pass that to the function as an option. That way you could change it for each widget.
I would think they would all the be same, so passing it as a param I think would complicate things more than it’s worth.
Very Good Effect, Thanks ….
Anthropologie used to have this very effect on their site, but instead of a black background and text it was a white background and an image. I can’t recall if their middle box links had an a:hover event — and if they did it must have been subtle, because it took me kind of awhile to realize that clicking on the actual link did something different than just clicking on the image square itself. Target has something similar, I think, too. I had been curious as to how it was done — nice job.
I was just skimming the code, so forgive me if I’ve overlooked something, but the buttons that pop up are <a> tags, and their display type is left as inline, so instead of calculating the correct offset for the button in the middle, wouldn’t it be easier to just set the line-height of overlay to the same as it’s height, and let the browser do the vertical centering? The would get rid of the need for the hard-coded “7” wouldn’t it?
Yep, that would work, except in the rather edge case that the anchor link happens to be so long that it wrapped.
It’s great to see OOCSS in use. Both as a concept and as the actual building blocks at http://wiki.github.com/stubbornella/oocss/ OOCSS has been a true “magic bullet” for all of my CSS projects. It really shines when used together with the markup model defined by YUI 3 widgets (you don’t need YUI at all for this, it’s just a pattern): http://developer.yahoo.com/yui/3/widget/index.html#markup. Add a well-structured file and CSS becomes extremely reliable and predictable even in very large projects.
I’m not sure how you find the time to post new content, but it is much appreciated.
I’ve been defining my classes this way since I started, with “rounded” and the like. I think it’s far simpler because you can have standards for yourself across sites – helps me stay organized.
OOCSS = Cascading Style Sheets, nothing new at all.
It seems to be the new “ajax” (which is really the XMLHttpRequest object), but give it a slick name and watch it become the new big thing.
Friendly request – Chris dude, put a link to the demo at the top of your entries rather than near the bottom (or both). I know, its minor. Whoa is me, I can’t bare to roll my scroll wheel down a few inches:) I know. However, you are on my daily hit list and I check you out every other day or so but my hit list is a pretty long list. There is certainly not enough time in every day to get to the bottom of the list. So, if one of your write ups initially looks engaging, I typically want to peek at the demo first. That way if I’m short on time, I mark it, come back later and do a deep dive into the reading. Just a thought.
Good stuff otherwise. Thanks!
Regarding the 7: could you build/render the anchor off-screen, then get it’s height, and then move it to the final location via absolute positioning? (Haven’t tried it, just thinking off the top of my head.)
I’ve got a better idea. Append the link and the div, but with an extra class hidden where you add in { display: none; } or { visibility: hidden; } (i think either one would work in this case). Then your element exists. Next up you calculate the height of the link and reposition the top by minus the height. Then you do a quick fadeIn and you should be all set.
And please don’t give me a headache by yelling at me about forgetting the <code> tags. It’s early and my coffee machine broke down. I would’ve fixed the tags if i could’ve edited my comment.
This may be some wining from my end: Shouldn’t you consider grabbing the url from within the widget div? This of course as an extra service for when no url was given. I think that would be a great addition to what you mentioned about different circumstances and possibly wanting to add the url in the text for degrading (wouldn’t be too bad for your SEO as well).
In any case: Great job on your blog and keep up the good work. I’m also looking forward to your next screencast!
Crap. Could you please do something about my screw ups in the previous comments? Mostly appreciated (a)
Looks all very nice.
One suggestion though. Keep the border-color out of the widget and add/remove a css-class instead of applying a css border-color.
ie. addClass(“jquery-middleboxlinks-hover”);
Hi,
i like what i see, its a nice demo
on a side note: about the border radius you use in the demo, i know the demo works without, and i know you like using border-radius i see it in alot of your projects
but it still does not work in IE, and since i work for customers who ask for design x and thus should recieve design x, and not use another browser to see design x, i don’t like using borderradius (yet).
That’s a really cool effect, thank you for sharing!
Not that simple for me until these examples, its been tough task to have middle boxes and still maintain easy to work layout….good work