A List Apart

Menu

Keeping Your Elements’ Kids in Line with Offspring

Issue № 252

Keeping Your Elements’ Kids in Line with Offspring

by Published in Browsers, CSS, HTML · 39 Comments

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

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

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 be nth-child-number (such as nth-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 by nth-child-odd and nth-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 and nth-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 like tableRows[2], that will end up with nth-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

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?”

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

In its simplest form, Offspring just needs to a script tag pointing to your local copy of offspring.js:

[removed][removed]

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, and nth-child-##
Light mode
first-child, last-child, and only-child

“How fast is it?”

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

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

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 applies first-child, last-child, and only-child, omitting nth-child-odd, nth-child-even, and nth-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 calling Offspring.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:

[removed]
  // <![CDATA[
  var offspringConfiguration = {
    runningMode: "light" 
  };
  // ]]>
[removed]
[removed][removed]

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:

[removed]
  // <![CDATA[
  var offspringConfiguration = {
    runningMode: "light",
    autoStart: false
  };
  // ]]>
[removed]
[removed][removed]

Applying Offspring to a specific section of the DOM

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:

[removed]
  // <![CDATA[
  var offspringConfiguration = {
    runningMode: "light" 
  };
  // ]]>
[removed]
[removed][removed]

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:

  1. Set up our configuration variable
  2. Re-initialize Offspring
  3. Run traverseChildren() on the table
  4.   

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

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 and nth-child-2
  • Since we also know that it’s not the last child element, it doesn’t get that class

Caveats

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 ids 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

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

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?”

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

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.

About the Author

39 Reader Comments

Load Comments