CSS selectors are handy things. They make coding CSS
easier, sure, but they can also help keep your markup clean. For
example, here’s a chunk of code that doesn’t use selectors well:
<ul class="products">
<li class="product">Item 1</li>
<li class="product">Item 2</li>
<li class="product">Item 3</li>
</ul>
This textbook class-itis leads to messy CSS:
ul.products {
/* Properties here */
}
li.product {
/* More properties here */
}
Because each of those list-items
has a common parent, a descendant selector can simplify the markup:
<ul class="products">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
Descendent selectors keep the CSS cleaner:
ul.products {
/* Properties here */
}
ul.products li {
/* More properties here */
}
This second version works just as well and the markup isn’t nearly as cluttered. And, when it comes to maintaining the markup, there’s one fewer class to worry about. (As a side note, I also indented that second rule—the ul.products li
one—as I’ve found that indenting CSS can improve its readability in just the same way that indenting markup can.)
Maintainable code helps the original author, but it’s even more important if you’re going to hand off your code to a client or a content management system: the less intricate the markup, the fewer chances there are for the nuances of your code to become lost in the shuffle.
Of course, descendant selectors are just the start—there are plenty more selectors out there. Some of the most powerful are the “pseudo-classes” within CSS. Pseudo-classes act like classes applied to an element except that they take effect based on an element’s status or its position within the document structure. For instance, you’ve probably used :visited
before. The browser associates that with links if they’re visited—there’s no visited
class that needs to be applied manually: it just happens. Such is the way with pseudo-classes.
Other pseudo-classes include :first-child
, :last-child
and :only-child
. As you might guess, these refer to elements and the relationship with their parent. While they may seem esoteric at first, they too can help alleviate extraneous markup. Take, for example, a common horizontal navigation list such as the one depicted in the comp below:
Comp of a navigation menu with items for Products, Support, About, and Contact Us.
Depending on who you ask, either an unordered or ordered list would probably be the most appropriate element for the above navigation bar. I’ll go with an unordered list:
<ul id="primary-navigation">
<li><a href="/products/">Products</a></li>
<li><a href="/support/">Support</a></li>
<li><a href="/about/">About</a></li>
<li><a href="/contact-us/">Contact Us</a></li>
</ul>
For the sake of brevity, I won’t go through each and every property and value involved in the CSS, but I do want to talk a bit about those dividers. I chose not to place the “|” characters in the markup because, while they might have given the desired appearance, they would have been unhelpful to users who may be using a screen reader (“Products Vertical Bar Support Vertical Bar About Vertical Bar…”). Instead, the dividers can be applied through background images:
ul#primary-navigation li a {
background-image: url(primary-navigation-divider.png);
background-position: 0em 0.1em;
background-repeat: no-repeat;
/* [other link properties] */
}
That gets us most of the way there. The problem is that the code ends up placing a divider-image to the left of each navigation item, but the first item (“Products”) isn’t meant to have a divider on its left side since it’s the first item. Let’s bring in a pseudo-class and take care of that, eh?
ul#primary-navigation li a {
background-image: url(primary-navigation-divider.png);
background-position: 0em 0.1em;
background-repeat: no-repeat;
/* [other link properties] */
}
ul#primary-navigation li:first-child a {
background-image: none;
}
That code is valid and it works, with no extra markup getting in the way. It just doesn’t work in IE 6. While pseudo-classes are gaining support among browsers, they’re not really IE’s strong point, though in fairness to the IE team, IE 7 does offer some improvements over IE 6.
So, what now? Well, you could add a class to the markup (such as first-child
on that first list-item
); that would be one way around it. While that would work, it would also be one more thing to keep track of if the navigation items ever needed to change. What if there were a way to have your pseudo-classes and eat them too?
Enter Offspring#section2
It occurred to me that classes resembling pseudo-classes could be added automagically through DOM Scripting. For instance, what if the first child of every element got the class first-child
? And, to round things out, what about only-child
and last-child
as well?
The next few steps along the path weren’t solely mine. My friend and fellow front-end developer Chris Griego lent a hand toward helping to shape the logic of how this could work, and he and I pair-programmed the first proofs-of-concept for the project.
The basic sequence is that the code runs once the page has loaded and, starting from the body node, it recursively walks through the DOM and applies the appropriate classes to each element. The code is entirely self-starting; a page just needs a script
element pointing to your local copy of offspring.js
and it’s ready to go.
What Offspring brings to the table#section3
Here’s a rundown of the classes that Offspring adds:
first-child
- This mimics
:first-child
and is applied to the first child element of a given parent. (All the other classes are based on pseudo-classes from CSS3.) last-child
- This pseudo-class refers to the last child-element of a given parent. Keep in mind that if the situation warrants it, an element might end up being both the first and last child of its parent.
nth-child-#
- Unlike the other classes here, this one is numbered (starting at one) and connotes an element’s position among its siblings. While the CSS syntax is along the lines of
:nth-child(number)
, the class that’s applied in this case would benth-child-number
(such asnth-child-2
). The spec allows for much more intricate notations of:nth-child()
; Offspring takes on a subset of what:nth-child()
can do. nth-child-odd
- The syntax of the
:nth-child()
pseudo-class can also account for odd and even children. In the context of Offspring, that’s represented bynth-child-odd
andnth-child-even
classes, respectively. nth-child-even
- Thus is the flip-side of
nth-child-odd
. Keep in mind that these two classes (nth-child-odd
andnth-child-even
) are applied on a basis of the first item in a sequence being odd (since it’s number one). Where this may get tricky is if you have an array of DOM elements, such as an array of table rows. While you may have something liketableRows[2]
, that will end up withnth-child-odd
since that’s really the third row. only-child
- This class is applied to elements who have no element siblings. (In case you’re wondering, an element can have comments next to it and still be considered an only child.)
All together now#section4
As an example of how it can be used, let’s revisit the earlier CSS snippet that dealt with a navigation list. While IE 6 doesn’t play well with :first-child
, it has no problems with ordinary classes. Taking Offspring into account, the code could be written like this instead:
ul#primary-navigation li a {
background-image: url(primary-navigation-divider.png);
background-position: 0em 0.1em;
background-repeat: no-repeat;
/* [other link properties] */
}
/* (notice the "." rather than the ":"
before "first-child" below) */
ul#primary-navigation li.first-child a {
background-image: none;
}
See this navigation list in action.
“So, is this just for IE 6?”#section5
You might be asking yourself if Offspring will be redundant in the context of non-IE6 browsers. Well, not exactly. Sure, IE 6 may have the most to gain, but other browsers can benefit as well. With some assistance from CSS3.info’s automated CSS Selectors test suite, I prodded several browsers to see if they natively supported the CSS pseudo-classes that Offspring emulates. Here’s how that played out:
:first-child
- Unimplemented in IE 6
:last-child
- Unimplemented in IE 6, IE 7, and Opera 9.x
:nth-child(#)
- Unimplemented in IE 6, IE 7, Safari 2.x, Firefox 2.x, Firefox 3 beta, and Opera 9.x
:nth-child(odd)
- Unimplemented in IE 6, IE 7, Safari 2.x, Firefox 2.x, Firefox 3 beta, and Opera 9.x
:nth-child(even)
- Unimplemented in IE 6, IE 7, Safari 2.x, Firefox 2.x, Firefox 3 beta, and Opera 9.x
:only-child
- Unimplemented in IE 6, IE 7, and Opera 9.x
This summary is intended as an illustration and, for the sake of clarity, doesn’t get into the nuances of browsers that may have partial support for some of these pseudo-classes. Just keep in mind that even if a browser doesn’t make the “unimplemented” list for one of the pseudo-classes, its support for that pseudo-class isn’t necessarily bulletproof.
There’s an optional “light” mode#section6
In its simplest form, Offspring just needs to a script tag pointing to your local copy of offspring.js:
<script type="text/javascript" src="offspring.js"></script>
For most people, the default configuration (invoked with a regular script
element, as above) is probably fine. I’ve also come to realize that some people may strive for a little extra speed in exchange for a reduced set of functionality. So, I’ve also put in place an optional “light” mode for Offspring which, when triggered, omits a few classes. Here’s what each mode has to offer:
- Full mode
first-child
,last-child
,only-child
,nth-child-odd
,nth-child-even
, andnth-child-##
- Light mode
first-child
,last-child
, andonly-child
“How fast is it?”#section7
By its nature, Offspring touches each node in the DOM (well, each element node, but that still ends up being most of the nodes). “So,” you may be wondering, “how fast is it on pages with a lot of nodes?” Well, to test that, I benchmarked the code against some of the more popular sites on the web.
Using Alexa’s list of the Top US Sites, I benchmarked the top 10 sites in each of Firefox 2.x, Firefox 3 beta, IE 6, IE 7, Safari 3.x, and Opera 9.x, timing each browser 5 times and recording the median of those times. The full results are on the benchmarking page, but I’ve included a brief summary below.
The fastest overall browser in the tests was Safari 3.x, offering sub-100ms times on every site (in both “full” and “light” modes). On the other hand, the slowest overall browser (IE 6) was still able to get by. IE 6 came in with times of not more than about 500 ms on 7/10 sites and, in “light” mode, cleared the 500 ms bar on 9/10 sites. The lone hold-out, in case you’re curious, was Amazon, in which IE 6 nudged through the 1000ms barrier in both “full” and “light” modes. The culprit, if I had to guess, may be due to Amazon’s table-based layout; by comparison, another site with a fairly intricate layout, Yahoo, took IE 6 took less than half the time to tackle.
So, what does this mean? Well, it probably depends on the complexity of the layout you’re dealing with. And, as the differences in times between Amazon and Yahoo demonstrate, it’s semantic rather than visual complexity that matters. While Offspring can be quite versatile, if you happen to be dealing with a table-based layout or a site with a bajillion elements, Offspring may not be your best option.
A Quick Refresher on Object Literals#section8
For its default configuration, Offspring needs no configuration—if you just reference the file normally, you’ll get the default set of options. On the other hand, if you’d like to put “light” mode (or one of the other options) to use, that can be done through a configuration variable, offspringConfiguration, which makes use of JavaScript’s object literal notation. If by chance you’re already familiar with object literal notation, feel free to skip ahead to the configuration section.
For those that may not be familiar with object literal notation, I’ll offer a quick refresher. In its basic form, an object literal is simply made of a set of name:value pairs. Fortunately, defining an object is fairly similar to defining other variables. In the case of a regular variable in which you had needed to define numberOfApples as “3”, you might say var numberOfApples = 3;
. So far, so good? Object literal notation is just the same, right up through the “=” part.
In this case, the name:value pairs come after the equals sign, each separated by a comma, and then the whole thing is enclosed in a set of curly-braces (“{” and ”}”). Continuing our hypothetical fruit-based example, an object literal could be used to keep track of a shopping list:
var storeInventory = {
numberOfApples: 2,
numberOfOranges: 5,
numberOfPears: 0,
storeToGoTo: "S-Mart",
carHasGas: true
};
There’s one last thing I’ll mention about object literals: though commas are required between each item, it’s important that you don’t include a comma after the very last item. So, in this case, numberOfPears
and storeToGoTo
each has trailing commas, but carHasGas
doesn’t have one. Similarly, if by chance you had no longer needed to keep track of the carHasGas
variable and were to delete it, you’d also need to delete the comma after storeToGoTo
since that would now be the last item in the set:
var storeInventory = {
numberOfApples: 2,
numberOfOranges: 5,
numberOfPears: 0,
storeToGoTo: "S-Mart"
};
Configuration#section9
Offspring’s options (such as “light” mode or others) can be defined through a variable, offspringConfiguration. You can define as many or as few options as you’d like; for the options that you don’t explicitly define, the defaults are used. Offspring offers three options:
runningMode
-
full
—Offspring applies all of its classes (as listed earlier) [default]light
—Offspring only appliesfirst-child
,last-child
, andonly-child
, omittingnth-child-odd
,nth-child-even
, andnth-child-##
.
autoStart
-
true
—Offspring runs automatically as soon as the DOM is ready [default]false
—Offspring must be started manually. This can be done by callingOffspring.start();
shouldRemoveOldOffspringClassesFirst
-
true
—Offspring first removes any old Offspring classes before applying the new ones. (This might be of use if Offspring is to be called on a page that has already been processed, such as if a table has been sorted or content has been loaded via Ajax.)false
—Offspring applies its classes without first removing old Offspring classes that might be there. Unless you’re doing fancy DOM updates, this is probably the better option in most cases. [default]
Supposing that you had wanted to use Offspring in “light” mode (leaving the other options at their defaults), the code for that would look something like this:
<script type="text/javascript">
// <![CDATA[
var offspringConfiguration = {
runningMode: "light"
};
// ]]>
</script>
<script type="text/javascript" src="offspring.js"></script>
Or, if you had wanted to use Offspring in “light” mode but prevent it from applying its classes automatically, you might use some code such as this:
<script type="text/javascript">
// <![CDATA[
var offspringConfiguration = {
runningMode: "light",
autoStart: false
};
// ]]>
</script>
<script type="text/javascript" src="offspring.js"></script>
Applying Offspring to a specific section of the DOM#section10
Most people probably don’t need to worry about this. Put another way, unless your ears perked up at the prospect of “Applying Offspring to a specific section of the DOM,” you can probably skip over this section.
In very specialized situations, you may find it convenient to be able to apply Offspring to only a specific section of the DOM. For instance, you may have added additional nodes to the page via an Ajax call, or rearranged some of the existing nodes (perhaps by sorting a table). Or, if you’ve inherited the maintenance of a page that has far too many nodes for its own good, I suppose you could also use the techniques here to manually apply Offspring to a subset of that page.
Let’s take the example of a page which includes sortable tables. Up until the point at which the user sorts the table, you’d probably be following a fairly normal course of events (that is, you’d have offspring.js
referenced with a script
element and so on). Once the user sorts the table, though, you might need to reapply Offspring to the table in order to normalize the odd/even classes on the table rows.
Offspring’s heavy-lifting function—which is normally triggered automatically behind the scenes—is traverseChildren()
and there’s no reason it can’t be called manually. It accepts a single parameter, the starting node, and recursively calls itself on all of that node’s children nodes. As you might guess, what normally happens on page-load is that traverseChildren()
is applied to the body
element (which of course affects the entire page).
Getting back to the example about sortable tables, let’s suppose that the page has loaded and Offspring has run normally—perhaps the head
of your document even has some
configuration options:
<script type="text/javascript">
// <![CDATA[
var offspringConfiguration = {
runningMode: "light"
};
// ]]>
</script>
<script type="text/javascript" src="offspring.js"></script>
When it comes time to manually call traverseChildren()
on the table, you probably also want to set shouldRemoveOldOffspringClassesFirst
to “true.” (In its default configuration and as a performance
optimization, Offspring doesn’t check to see if an element might already have Offspring-related classes before it applies new ones.) Here’re the steps we’ll go through to reapply Offspring to that just-sorted table:
- Set up our configuration variable
- Re-initialize Offspring
- Run
traverseChildren()
on the table
Here’s how that code might look:
// Set up our configuration variable
var offspringConfiguration = {
runningMode: "light",
shouldRemoveOldOffspringClassesFirst: true
};
/* Re-initialize Offspring (this tells it to read
the values from the configuration variable) */
Offspring.init();
/* Run traverseChildren() on the table
(this assumes that the "tableNode" is assigned
to the table) */
Offspring.traverseChildren(tableNode);
Of course, those first two steps (“Set up our configuration variable” and “Re-initialize Offspring”) are only necessary since we’re changing Offspring’s configuration. Supposing that you had to deal with several tables that had been resorted, you’d only need to deal with the configuration bits once—after that, you’d only need to run Offspring.traverseChildren()
on the affected tables.
Code optimizations#section11
If you’re not particularly interested in the internals of how the code works, no worries—feel free to skip to the next section. On the other hand, if you’re interested in some of the behind-the-scenes goings-on, well, here we go.
In its original draft, the code worked probably about how you would have expected—starting at the body
element, it recursively walked the DOM tree, adding classes to each node as needed. That did the job, but I was left wondering if there might be a more efficient way to go about it. After profiling the code (with the help of Firebug), I realized that much of the processing time involved string manipulation.
Under the original algorithm, each element was checked to see if it qualified for a given class (such as first-child
or nth-child-odd
); if so, that class would be added. Then that sequence was repeated on that element (check the element, add the class if needed) for each of the remaining classes. As you might guess, adding each class involved many tiny string concatenations. While the time involved in a single string concatenation is virtually immeasurable, I noticed that they had the tendency to accumulate into a small delay when dealing with large pages.
To eliminate many of the string concatenations, I refactored the code (and, in the process, ended up jettisoning much of the original code). I realized that I could pre-calculate a given element’s classes as long as I knew its position among its siblings and whether it was the last child of its parent.
The code creates a cache of these class names by generating two arrays ahead of time, one for regular child elements and one for child elements that are also the last in a set. If you were to peer into the contents of those arrays, here’s what the first few items in each array would look like (Line wraps marked » —Ed.):
// 1st element (and not the last element)
regularHashTable[0] = "first-child nth-child-odd »
nth-child-1";
// 2nd element (and not the last element)
regularHashTable[1] = "nth-child-even nth-child-2";
// 3rd element (and not the last element)
regularHashTable[2] = "nth-child-odd nth-child-3"; // more array elements for regularHashTable…/* 1st element (and also the last element) which,
by definition, also means that it's an only child */
lastChildHashTable[0] = "first-child only-child »
nth-child-odd nth-child-1 last-child"; // 2nd element (and also the last element)
lastChildHashTable[1] = "first-child nth-child-even »
nth-child-2 last-child"; // 3rd element (and also the last element)
lastChildHashTable[2] = "first-child nth-child-odd »
nth-child-3 last-child"; // more array elements for lastChildHashTable…
I’ll walk through an example of how the cache can “know” what classes apply
to a given element. Let’s say we’re dealing with the second element in a set and that we know it’s not the last element:
- It’s not the first child element, so it doesn’t get that one.
- Because it’s the second item, we know it’s not an only child.
- As the second item, it gets both
nth-child-even
andnth-child-2
- Since we also know that it’s not the last child element, it doesn’t get that class
Caveats#section12
I’m pleased with Offspring, but I hope that this tool (a hammer, if you will) doesn’t cause every piece of code to look like a nail. I think there’re many things Offspring is good for, but it may not be perfect for everything. For starters, not all your users may have JavaScript. Granted, that’s probably a pretty small number, but it might not be
zero. As a rule of thumb, I’d recommend treating Offspring-generated classes as a component of progressive enhancement.
For example, let’s go back to that earlier scenario about the horizontal navigation and its dividers. With Offspring in place, you could use a selector such as ul#primary-navigation li.first-child a
(so far, so good). Then, let’s assume that someone who doesn’t have JavaScript visits the page—then what? Well, he or she would end up seeing an extra navigation-divider on that first item.
Is that a bad thing? Well, that’s up to you. Depending on how that affects the design as a whole, that may be a design variation that you’re willing to accept for the handful or so users who may not have JavaScript. That decision aside, I would caution against using Offspring for major building blocks of your layout. For instance, suppose that you had a two-column layout made up of two containers (who also had a common parent element). In theory, you could set the first container to float left based on its first-child
class while setting the other child to float right based on its last-child
class. While that would work for users who had JavaScript, it would offer a marked difference in presentation for any users without JavaScript.
In cases such as those where you’re dealing with the core skeletal structure of the page, you may be better of going with traditional id
s and classes. Besides, in that previous example about the page with two columns, you’re probably not dealing with much superfluous markup in the first place:
<div id="primary-and-secondary-content">
<div id="primary-content">
<!-- primary content here -- >
</div>
<div id="secondary-content">
<!-- secondary content here -- >
</div>
</div> <-- end of primary-and-secondary-content -->
There is one other thing I feel I should point out. In the spirit of forward-thinking toward maintenance, you may be tempted to create comma-delimited selectors featuring both the Offspring-created class and its corresponding pseudo-class, such as this:
/* This is an example of /what not to do/ */
ul#primary-navigation li.only-child a,
ul#primary-navigation li:only-child a {
/* Properties here */
}
You may be thinking that such a technique would allow you to cater to both extra-capable and less-capable browsers, and imagining that the Offspring-based portion of your selectors could be removed once your project no longer supports the browser(s) that don’t play well with these psudeo-classes. In fairness, that probably would be a decent idea but for one thing: the spec states that user-agents must ignore an entire rule (the selector plus its declaration block) if any portion of a comma-delimited selector isn’t parsable by the browser.
So, in the above do-not-do-this example, if a given browser is following the spec but yet has absolutely no inkling about :only-child
, it might very well ignore the entire rule (whether or not it may be able to understand one of the other comma-delimited sections within the selector). Just to put some specifics to this, I tested several browsers to see how they fared against invalid comma-delimited selectors.
The full results of my testing are on that page, but the short version is that IE 6 & 7 sometimes passed and sometimes failed, while most other modern browsers passed. Keeping in mind that “passing” means that they correctly ignored rules in which part of their (comma-delimited) selectors were invalid, it would appear that this concern is more than theoretical. Accordingly, I wouldn’t recommend combining an Offspring-created class and its corresponding pseudo-class within a single comma-delimited selector.
Another attempt#section13
So, what then? What about writing two CSS rules in those cases, one for the Offspring-created class and a separate version for the corresponding pseudo-class? If for no other reason than my desire for maintainable code, I’m not sure about that idea, either. Consider this example:
/* This example may or may not represent a good idea, either */
ul#primary-navigation li.only-child a {
/* Properties here
More Properties
... */
}
ul#primary-navigation li:only-child a {
/* Properties here
More Properties
... */
}
As you may have already guessed, if any of the properties for that anchor need to be changed, the author would need to remember to make those changes in both places. Now, if it were the case that the anchor’s declaration only had a single property:value pair, this duplicate rules idea might be worth a second look.
A third way#section14
There is also a third option. If you were to write rules targeting just Offspring’s classes, you would no longer have duplicate rules and the previously mentioned maintenance concerns would no longer be an issue. Taking this approach with the previous example, you’d have something like this:
ul#primary-navigation li.only-child a {
/* Properties here */
}
Naturally, this doesn’t offer the option of, at some point in the future, stripping out the Offspring-created classes and leaving behind their corresponding pseudo-classes (since this approach doesn’t make use of the corresponding pseudo-classes).
Of the three options—combining Offspring-created classes and pseudo-classes into a single comma-delimited selector, writing duplicate rules for the Offspring-created and pseudo-classes, and writing rules focusing on the Offspring-created classes—the first one is clearly not worth considering. That leaves two, and the final choice is up to you. From your own perspective and for your own projects, is it more important to remove a potential maintenance hazard, or to leave open the possibility of later replacing the Offspring-created classes with their corresponding CSS pseudo-classes?
“Oh, and are there any similar libraries?”#section15
I enjoy DOM scripting, but I’m by no means the only DOM scripting guy out there. I’d like to offer a tip of my hat to some other libraries which have similar goals to Offspring. Back in the 2004/2005 timeframe, Dean Edwards came up with a library that he christened IE7; though the name may seem ambiguous today in the context of “the real IE 7,” Dean’s library came out well ahead of Microsoft’s browser of the same name. What the library set out to do, and largely achieved, was to rejigger IE’s CSS support in favor of standards compliance through JavaScript. It was (and still is) an impressive set of code. At the same time, its strength—an overhaul of IE’s CSS interpreter—came at the price of speed.
Dean’s library worked behind the scenes by parsing both the DOM and the referenced CSS files; then, it dynamically created class-based CSS rules and added those rules to the DOM. Unfortunately, IE’s JavaScript processing is not the fastest out there. In fact, it may have the slowest JavaScript interpreter among the major browsers. And, in my personal experiments with “IE7” around that timeframe, I found that it worked spectacularly but, unfortunately, a little slowly for what I needed. It seems that IE7 (the library) was so exacting that IE (the browser) really had its hands full with all the fixes being made to itself. I think that may have been one reason why I aimed for a relatively lean approach with Offspring. Sure, it requires a little more developer interaction than “IE7,” but I also wanted to ensure that the code would run quickly across many browsers (including IE).
Last but not least, one of the peer reviewers for this article thought it would be a good idea to mention the Sons of Suckerfish / Suckerfish Shoal
family of code [which made its debut in the pages of ALA —Ed.]. I don’t think I had run across that site before and, now that I have, I couldn’t agree more. In case you haven’t seen the site, here’s the basic idea: The Sons of Suckerfish libraries use JavaScript to add support for CSS2’s link-related pseudo-classes (such as :target
, :active
, and :hover
) to browsers that might not otherwise support those natively. And, to their credit, the authors, Patrick Griffiths and Dan Webb, take pride in their lightweight (or, as they say, “slimline”) approach to JavaScript. Nice job, guys—looks like a bang-up set of code.
Going forward#section16
Offspring does what I want it to do—add pseudo-class-like classes to elements to leverage some of the benefits of pseudo-classes while we wait for browsers to catch up and support them outright. All the same, I’m open to suggestions on further optimization or other improvements and, to help foster that environment, I’ve set up a project for Offspring at Google Code where you can always find the latest files along with discussions around the code’s future directions.
my i know what u used to mesure the time taken?
Could many of the concepts discussed be implemented using “jQuery”:http://jquery.com/ as well?
There’s a missing closing
tag immediately above the heading "All together now", which makes the last half of the article pretty hard to read.
In a more legitimate comment, I'd also be interested to see Offspring ported to something like jQuery or (my personal preference) Mootools.
@thirukumaran: To measure the time taken, I created a one-off version of the code in which I checked the Date() object before and after processing the page; by subtracting one from the other, I had the elapsed time. (I then made use of OpenOffice Calc to help keep track of all the numbers and calculate the median values.)
@Jacob: Yeah, it may be possible to implement some of the functionality with other JavaScript libraries (such as jQuery). All the same, a more specialized library such as this one may be able to offer a lighter set of code than a general-purpose JavaScript library. (And, as it’s tuned toward a specific task, this approach may offer performance advantages as well.)
(Just to be sure, I have nothing against jQuery or other JavaScript libraries—they can be quite handy for any number of tasks.)
Fantastic work. Well done. Just one note — it is my understanding and experience that Safari _does not_ implement @last-child@.
@ “Jacob Boomgaarden”:http://www.alistapart.com/comments/keepelementskidsinlinewithoffspring?page=1#2 : Yes, jQuery could certainly do this. However, many developers don’t want and many projects don’t need the overhead of that type of library. Offspring seems to be about doing one thing and doing it well.
Ben is right about Safari not supporting the last-child selector.
@”Ben Spaulding”:http://www.alistapart.com/comments/keepelementskidsinlinewithoffspring?page=1#5 : Good catch. Indeed, Safari “hasn’t implemented”:http://www.quirksmode.org/css/firstchild.html support for last-child.
On mid-to-large projects, where using a complete library is needed, such a script would be useless.
And using jQuery for example, would yield much more flexible results, as its CSS selectors are very complete.
I am, by no means, trying to flame here, but i really don’t see the point of a js library that does trivial things.
Wouldn’t it be better to simply use pure js to accomplish this?
I mean, if you only need the first-child of the navigation, its much faster to do:
var nav-items = document.getElementById(‘nav’).getElementsByTagName(‘li’);
nav-items[0].class = ‘first-child’;
Might be worth having another look at Dean Edwards’ ie7 (and now ie8) script. He’s done some re-writing, and I believe it performs a bit better now.
Of course, Offspring and ie7/8 solve different problems, albeit with some overlap.
Unfortunately Opera also does not support the CSS 3 selector :last-child so it only works in Mozilla based browers.
sometimes for purely presentation-based pages i import jquery just to get around ie6’s useless CSS implementation and to avoid using CSS hacks – so i commend you for fighting the fight. jquery is probably an overkill if you don’t need help with extra behaviours, so i guess in this case offspring would be useful – but it’s nice to know with jquery that you have extended options such as ajax, events and DOM manipulation, available when and if you need them.
The first-child selector is the only CSS-selector I have really missed for IE6, when creating horizontal menus.
A simple fix would be to use negative margin-left for IE6 served with the star-html CSS-hack.
* html ul#primary-navigation li {
margin-left:-2px;
}
Here’s a slightly outdated table of CSS 3 selector compatibility I made on CSS3.info a while ago:
http://www.css3.info/modules/selector-compat/
This article has reminded me that I need to update it!
What’s up with that huge gap of white space after the first line of the first markup snippet? That doesn’t look very good. Time to call the CSS doctor.
I admit I read it in diagonal (I will read better later… 😉 but this lib doesn’t support :hover on none a elements correct? That would be a nice addition for drop downs etc… Or did I miss it?
Thanks!
Great article.
I was wondering one thing though. How much longer will it be until everyone jumps off this screen-reader bandwagon. There are other platforms that limit displays. Some PDAs and most cellphones for example have poor CSS capabilities.
Can we be a bit more inclusive? Accessibility isn’t just about blind people.
Opera 9.5 beta has full support for CSS selectors “check it out”:http://www.css3.info/fully-selected/
Sounds like it has got some positives and negatives.
Nicely done, it’s a simple library which performs one task well with minimal overhead. Where some of the other commenters have suggested incorporating this functionality into larger libraries, I for one value the clean and minimal nature of Offspring; for projects where I need this functionality I can now get it cleanly without a load of other stuff being thrown in as well.
I don’t think people suggest porting to other libraries because they think that Offspring needs speed improvements. I at least would love to see a Mootools port because I use Mootools for other things, and having the functionality that Offspring provides would be a nice bonus.
If all you need is CSS pseudo-classes, then clearly Offspring as a small, stream-lined, standalone library is superior. However, if you’re using a Javascript library for other programming already then integrating Offspring into it makes a lot of sense.
This looks fantastic and something that I’d love to play around with sometime. That said, I can’t imagine using it seriously any time soon. I don’t think the benefits outweigh the headaches that it’ll undoubtedly somehow cause.
In your section about the horizontal navigation you mention, “It just doesn’t work in IE 6.” Isn’t that a deal breaker? When client sites have 30% or more of their audience using IE 6, is it reasonable to create something that doesn’t work for those folks? Then you toss-in the Safari issues – more of a deal breaker?
@Eddie: I don’t think you read the article. The whole point was to make advanced selectors work in IE6, and other browsers. IE6 was a deal breaker (for CSS selectors), now its not. And “The fastest overall browser in the tests was Safari 3.x”, so I’m not sure what Safari issues you are talking about.
In the section of the code in the optimization section, lastChildHashTable[1] and lastChildHashTable[2] both apply the _first-child_ class. Shouldn’t _first-child_ only be applied for lastChildHashTable[0], or am I missing something here?
Alex: Wonderful job on this library. I like the fact that it stands alone without support from any underlying framework. I’m not a framework hater by any means, in fact I have a few favorite frameworks of my own, but it’s nice to see a script that can stand on its own for those projects where a complete framework is overkill.
I also like how thorough the library is. Those nth-child selectors will come in very handy. Up to this point, I’ve been using PHP on the back-end to add a special alt class to odd elements.
Once again, well done on this library and keep up the good work. Oh, and try to ignore the nay-sayers…they’re just jealous. 😉
Does anyone know any reason what-so-ever why Microsoft would choose to implement first-child and not last-child for IE7? I’m perplexed.
This comes at a good time for me. Trying to improve the CSS for the WordPress backend and very annoyed that they don’t provide enough CSS hoooks in their tables.
With nth-child support I can get around that problem without having to edit the core templates, and I didn’t want to use a full library such as jQuery to create the same effect due to speed issues. WordPress’ backend can be pretty slow already.
Great article, and all the comments in the .js file are very helpful too!
However, something kept bothering me. I’m not a webdeveloper, but I wonder if JavaScript is the language to do this. In my view JavaScript is designed to do behavioural tasks in a browser. XSLT is the language that’s really good at finding the position of an element in the DOM and at creating mark-up.
So I did an experiment and wrote a small client-side XSLT (60 lines) that renders the same result (I think) as this JavaScript library in full mode.
It doesn’t run flawless yet in Internet Explorer but I believe this is the way it should be done (in a perfect world 🙂
I’ve written about my solution: http://banji.nl/experiments
I’d be interested to get some feedback and hear about other experiences with client-side XSLT.
A couple of people asked in the comments why Internet Explorer (and Safari) support first-child but not last-child. This is probably because first-child was part of the CSS2.1 recommendation and last is CSS3.
While I appreciate libraries like this, I will only use them in contained environments (where you know people will have javascript. If not, I prefer to find solid css fixed.
As for the css discussed in this article, I do have a few things to say:
* using descendant selectors is great, it can be pretty dangerous since the > combinator is not supported in IE6 either. This means that an ul li statement will hit on *all* li elements. This is annoying when nesting lists.
* you are right that the pipe symbol does not belong in the html source, but if you’re using a pipe anyway it’s better to
have it inserted through css (generated content). Again, this is not supported by IE (yet) but it’s really the way to go, as this generated content can be styled to your liking. Much easier than fixing background images over and over again. If you’re really hardcore, you can have it inserted through the css content and use conditional comments for IE. Though that is overkill alright 🙂
here’s a weird fact:
IE6 & IE7 implement the CSS3 selector “text-overflow” but Firefox and Safari don’t (haven’t cared to check it in Opera).
http://www.css3.info/preview/text-overflow/
Also, this seems like an awful lot of JavaScript for not much benefit you can have clean markup and utilize the selectors without all the extra javaScirpt. I’ll give it a try though.
my2cents
Of the three forward thinking methods, I like the third one the best, (just use the offspring classes). The reason I like this one is because if I do eventually want to remove offspring I can just do a find/replace on those classes.
I’m currently working as stylesheet guy on Slantly.com – I developed a method for making pipe-separated horizontal lists with no requirement of :first-child (so no IE problem, either) => http://www.dillerdesign.com/css/cookbook/links_pipes_list_elements.html
My motive for leaving my previous comment was that use of :first-child was the first-real world example in the article.
Interesting script, Alex. I’m playing around with it right now on some dev sites at work 🙂
I just wanted to let you know that there is a small error in the “Applying Offspring to a specific section of the DOM” section. Where you have “Offspring.init();” and “Offspring.traverseChildren(tableNode);”, those first “O”s should be lowercase.
It took me a few minutes to figure out why I was getting “Offspring is not defined” errors, but a lowercase “o” sets everything right.
Has anyone done a proper testing of Dean Edwards IE7 beta 3 script and if so, give some feedback.
I would very much like to use it, however heard a couple of annoying things such as frozen screens and very slow loading times.
Help would be appreciated. Thanks.
I’m not qualified to speak on the script aspects of the article as I’m only just dipping my toe into javascript but the section of the article that suggests using an image to separate the items of a horizontal list bothers me a little. I like the reason for using it but if the viewer is using a large font size rather than a screen reader; the list-item markers won’t increase in size as the font increases. Would it not be an idea, especially as horizontal lists are so useful, to lobby for either a vertical line or for customised markers to be included as a list-item marker.
The only part of offspring that I haven’t liked is the need to use class notation in your stylesheets instead of pseudo-classes, which means when browser support catches up with CSS3 (sometime around the turn of the next century, I’m sure) you’d have to go through and fix all your stylesheets. So I’ve devised a little script that you can run with offspring called pseudonut. It basically lets you use the css3 pseudo-classes in your stylesheet, and it converts them into the class notation for use with offspring. When CSS3-compatible browsers become commonplace, all you’d have to do is drop the scripts and you’re ready to go.
You can check it out at http://www.cssquirrel.com/2008/03/02/solving-impatience-with-pseudonut/
For me this is perfect – and I’m loading offspring and the class notion styles via a conditional statement in the index file, so there is no extra overhead for any other browser than IE6, and no need to upgrade the CSS later