You are here



Seth Godin - Wed 19th Oct 2016 20:10
If you're sharing a cab to the airport with a stranger, what happens if he's two inches taller than you? Probably nothing. There's nothing to distract, or to cause discomfort. You make small talk. What if he's a little shorter...        Seth Godin
Categories: thinktime

JavaScript for Web Designers: DOM Scripting

a list apart - Wed 19th Oct 2016 01:10

A note from the editors: We’re pleased to share an excerpt from Chapter 5 of Mat Marquis' new book, JavaScript for Web Designers, available now from A Book Apart.

Before we do anything with a page, you and I need to have a talk about something very important: the Document Object Model. There are two purposes to the DOM: providing JavaScript with a map of all the elements on our page, and providing us with a set of methods for accessing those elements, their attributes, and their contents.

The “object” part of Document Object Model should make a lot more sense now than it did the first time the DOM came up, though: the DOM is a representation of a web page in the form of an object, made up of properties that represent each of the document’s child elements and subproperties representing each of those elements’ child elements, and so on. It’s objects all the way down.

window: The Global Context

Everything we do with JavaScript falls within the scope of a single object: window. The window object represents, predictably enough, the entire browser window. It contains the entire DOM, as well as—and this is the tricky part—the whole of JavaScript.

When we first talked about variable scope, we touched on the concept of there being “global” and “local” scopes, meaning that a variable could be made available either to every part of our scripts or to their enclosing function alone.

The window object is that global scope. All of the functions and methods built into JavaScript are built off of the window object. We don’t have to reference window constantly, of course, or you would’ve seen a lot of it before now—since window is the global scope, JavaScript checks window for any variables we haven’t defined ourselves. In fact, the console object that you’ve hopefully come to know and love is a method of the window object:

window.console.log function log() { [native code] }

It’s hard to visualize globally vs. locally scoped variables before knowing about window, but much easier after: when we introduce a variable to the global scope, we’re making it a property of window—and since we don’t explicitly have to reference window whenever we’re accessing one of its properties or methods, we can call that variable anywhere in our scripts by just using its identifier. When we access an identifier, what we’re really doing is this:

function ourFunction() { var localVar = "I’m local."; globalVar = "I’m global."; return "I’m global too!"; }; undefined window.ourFunction(); I’m global too! window.localVar; undefined window.globalVar; I’m global.

The DOM’s entire representation of the page is a property of window: specifically, window.document. Just entering window.document in your developer console will return all of the markup on the current page in one enormous string, which isn’t particularly useful—but everything on the page can be accessed as subproperties of window.document the exact same way. Remember that we don’t need to specify window in order to access its document property—window is the only game in town, after all.

document.head <head>...</head> document.body <body>...</body>

Those two properties are themselves objects that contain properties that are objects, and so on down the chain. (“Everything is an object, kinda.”)

Using the DOM

The objects in window.document make up JavaScript’s map of the document, but it isn’t terribly useful for us—at least, not when we’re trying to access DOM nodes the way we’d access any other object. Winding our way through the document object manually would be a huge headache for us, and that means our scripts would completely fall apart as soon as any markup changed.

But window.document isn’t just a representation of the page; it also provides us with a smarter API for accessing that information. For instance, if we want to find every p element on a page, we don’t have to write out a string of property keys—we use a helper method built into document that gathers them all into an array-like list for us. Open up any site you want—so long as it likely has a paragraph element or two in it—and try this out in your console:

document.getElementsByTagName( "p" ); [<p>...</p>, <p>...</p>, <p>...</p>, <p>...</p>]

Since we’re dealing with such familiar data types, we already have some idea how to work with them:

var paragraphs = document.getElementsByTagName( "p" ); undefined paragraphs.length 4 paragraphs[ 0 ]; <p>...</p>

But DOM methods don’t give us arrays, strictly speaking. Methods like getElementsByTagName return “node lists,” which behave a lot like arrays. Each item in a nodeList refers to an individual node in the DOM—like a p or a div—and will come with a number of DOM-specific methods built in. For example, the innerHTML method will return any markup a node contains—elements, text, and so on—as a string:

var paragraphs = document.getElementsByTagName( "p" ), lastIndex = paragraphs.length – 1, /* Use the length of the `paragraphs` node list minus 1 (because of zero-indexing) to get the last paragraph on the page */ lastParagraph = paragraphs[ lastIndex ]; lastParagraph.innerHTML; And that’s how I spent my summer vacation.

Fig 5.1: First drafts are always tough.

The same way these methods give us access to information on the rendered page, they allow us to alter that information, as well. For example, the innerHTML method does this the same way we’d change the value of any other object: a single equals sign, followed by the new value.

var paragraphs = document.getElementsByTagName( "p" ), firstParagraph = paragraphs[ 0 ]; firstParagraph.innerHTML = "Listen up, chumps:"; "Listen up, chumps:"

JavaScript’s map of the DOM works both ways: document is updated whenever any markup changes, and our markup is updated whenever anything within document changes (Fig 5.1).

Likewise, the DOM API gives us a number of methods for creating, adding, and removing elements. They’re all more or less spelled out in plain English, so even though things can seem a little verbose, it isn’t too hard to break down.

DOM Scripting

Before we get started, let’s abandon our developer console for a bit. Ages ago now, we walked through setting up a bare-bones HTML template that pulls in a remote script, and we’re going to revisit that setup now. Between the knowledge you’ve gained about JavaScript so far and an introduction to the DOM, we’re done with just telling our console to parrot things back to us—it’s time to build something.

We’re going to add a “cut” to an index page full of text—a teaser paragraph followed by a link to reveal the full text. We’re not going to make the user navigate to another page, though. Instead, we’ll use JavaScript to show the full text on the same page.

Let’s start by setting up an HTML document that links out to an external stylesheet and external script file—nothing fancy. Both our stylesheet and script files are empty with .css and .js extensions, for now—I like to keep my CSS in a /css subdirectory and my JavaScript in a /js subdirectory, but do whatever makes you most comfortable.

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="css/style.css"> </head> <body> <script src="js/script.js"></script> </body> </html>

We’re going to populate that page with several paragraphs of text. Any ol’ text you can find laying around will do, including—with apologies to the content strategists in the audience—a little old-fashioned lorem ipsum. We’re just mocking up a quick article page, like a blog post.

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="css/style.css"> </head> <body> <h1>JavaScript for Web Designers</h1> <p>In all fairness, I should start this book with an apology—not to you, reader, though I don’t doubt that I’ll owe you at least one by the time we get to the end. I owe JavaScript a number of apologies for the things I’ve said to it during the early years of my career, some of which were strong enough to etch glass.</p> <p>This is my not-so-subtle way of saying that JavaScript can be a tricky thing to learn.</p> [ … ] <script src="js/script.js"></script> </body> </html>

Feel free to open up the stylesheet and play with the typography, but don’t get too distracted. We’ll need to write a little CSS later, but for now: we’ve got scripting to do.

We can break this script down into a few discrete tasks: we need to add a Read More link to the first paragraph, we need to hide all the p elements apart from the first one, and we need to reveal those hidden elements when the user interacts with the Read More link.

We’ll start by adding that Read More link to the end of the first paragraph. Open up your still-empty script.js file and enter the following:

var newLink = document.createElement( "a" );

First, we’re intializing the variable newLink, which uses document.createElement( "a" ) to—just like it says on the tin—create a new a element. This element doesn’t really exist anywhere yet—to get it to appear on the page we’ll need to add it manually. First, though, <a></a> without any attributes or contents isn’t very useful. Before adding it to the page, let’s populate it with whatever information it needs.

We could do this after adding the link to the DOM, of course, but there’s no sense in making multiple updates to the element on the page instead of one update that adds the final result—doing all the work on that element before dropping it into the page helps keep our code predictable.

Making a single trip to the DOM whenever possible is also better for performance—but performance micro-optimization is easy to obsess over. As you’ve seen, JavaScript frequently offers us multiple ways to do the same thing, and one of those methods may technically outperform the other. This invariably leads to “excessively clever” code—convoluted loops that require in-person explanations to make any sense at all, just for the sake of shaving off precious picoseconds of load time. I’ve done it; I still catch myself doing it; but you should try not to. So while making as few round-trips to the DOM as possible is a good habit to be in for the sake of performance, the main reason is that it keeps our code readable and predictable. By only making trips to the DOM when we really need to, we avoid repeating ourselves and we make our interaction points with the DOM more obvious for future maintainers of our scripts.

So. Back to our empty, attribute-less <a></a> floating in the JavaScript ether, totally independent of our document.

Now we can use two other DOM interfaces to make that link more useful: setAttribute to give it attributes, and innerHTML to populate it with text. These have a slightly different syntax. We can just assign a string using innerHTML, the way we’d assign a value to any other object. setAttribute, on the other hand, expects two arguments: the attribute and the value we want for that attribute, in that order. Since we don’t actually plan to have this link go anywhere, we’ll just set a hash as the href—a link to the page you’re already on.

var newLink = document.createElement( "a" ); newLink.setAttribute( "href", "#" ); newLink.innerHTML = "Read more";

You’ll notice we’re using these interfaces on our stored reference to the element instead of on document itself. All the DOM’s nodes have access to methods like the ones we’re using here—we only use document.getElementsByTagName( "p" ) because we want to get all the paragraph elements in the document. If we only wanted to get all the paragraph elements inside a certain div, we could do the same thing with a reference to that div—something like ourSpecificDiv.getElementsByTagName( "p" );. And since we’ll want to set the href attribute and the inner HTML of the link we’ve created, we reference these properties using newLink.setAttribute and newLink.innerHTML.

Next: we want this link to come at the end of our first paragraph, so our script will need a way to reference that first paragraph. We already know that document.getElementsByTagName( "p" ) gives us a node list of all the paragraphs in the page. Since node lists behave like arrays, we can reference the first item in the node list one by using the index 0.

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.innerHTML = "Read more";

For the sake of keeping our code readable, it’s a good idea to initialize our variables up at the top of a script—even if only by initializing them as undefined (by giving them an identifier but no value)—if we plan to assign them a value later on. This way we know all the identifiers in play.

So now we have everything we need in order to append a link to the end of the first paragraph: the element that we want to append (newLink) and the element we want to append it to (firstParagraph).

One of the built-in methods on all DOM nodes is appendChild, which—as the name implies—allows us to append a child element to that DOM node. We’ll call that appendChild method on our saved reference to the first paragraph in the document, passing it newLink as an argument.

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.innerHTML = "Read more"; firstParagraph.appendChild( newLink );

Now—finally—we have something we can point at when we reload the page. If everything has gone according to plan, you’ll now have a Read More link at the end of the first paragraph on the page. If everything hasn’t gone according to plan—because of a misplaced semicolon or mismatched parentheses, for example—your developer console will give you a heads-up that something has gone wrong, so be sure to keep it open.

Pretty close, but a little janky-looking—our link is crashing into the paragraph above it, since that link is display: inline by default (Fig 5.2).

Fig 5.2: Well, it’s a start.

We have a couple of options for dealing with this: I won’t get into all the various syntaxes here, but the DOM also gives us access to styling information about elements—though, in its most basic form, it will only allow us to read and change styling information associated with a style attribute. Just to get a feel for how that works, let’s change the link to display: inline-block and add a few pixels of margin to the left side, so it isn’t colliding with our text. Just like setting attributes, we’ll do this before we add the link to the page:

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.innerHTML = "Read more"; = "inline-block"; = "10px"; firstParagraph.appendChild( newLink );

Well, adding those lines worked, but not without a couple of catches. First, let’s talk about that syntax (Fig 5.3).

Fig 5.3: Now we’re talking.

Remember that identifiers can’t contain hyphens, and since everything is an object (sort of), the DOM references styles in object format as well. Any CSS property that contains a hyphen instead gets camel-cased: margin-left becomes marginLeft, border-radius-top-left becomes borderRadiusTopLeft, and so on. Since the value we set for those properties is a string, however, hyphens are just fine. A little awkward and one more thing to remember, but this is manageable enough—certainly no reason to avoid styling in JavaScript, if the situation makes it absolutely necessary.

A better reason to avoid styling in JavaScript is to maintain a separation of behavior and presentation. JavaScript is our “behavioral” layer the way CSS is our “presentational” layer, and seldom the twain should meet. Changing styles on a page shouldn’t mean rooting through line after line of functions and variables, the same way we wouldn’t want to bury styles in our markup. The people who might end up maintaining the styles for the site may not be completely comfortable editing JavaScript—and since changing styles in JavaScript means we’re indirectly adding styles via style attributes, whatever we write in a script is going to override the contents of a stylesheet by default.

We can maintain that separation of concerns by instead using setAttribute again to give our link a class. So, let’s scratch out those two styling lines and add one setting a class in their place.

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.setAttribute( "class", "more-link" ); newLink.innerHTML = "Read more"; firstParagraph.appendChild( newLink );

Now we can style .more-link in our stylesheets as usual:

.more-link { display: inline-block; margin-left: 10px; }

Much better (Fig 5.4). It’s worth keeping in mind for the future that using setAttribute this way on a node in the DOM would mean overwriting any classes already on the element, but that’s not a concern where we’re putting this element together from scratch.

Fig 5.4: No visible changes, but this change keeps our styling decisions in our CSS and our behavioral decisions in JavaScript.

Now we’re ready to move on to the second item on our to-do list: hiding all the other paragraphs.

Since we’ve made changes to code we know already worked, be sure to reload the page to make sure everything is still working as expected. We don’t want to introduce a bug here and continue on writing code, or we’ll eventually get stuck digging back through all the changes we made. If everything has gone according to plan, the page should look the same when we reload it now.

Now we have a list of all the paragraphs on the page, and we need to act on each of them. We need a loop—and since we’re iterating over an array-like node list, we need a for loop. Just to make sure we have our loop in order, we’ll log each paragraph to the console before we go any further:

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.setAttribute( "class", "more-link" ); newLink.innerHTML = "Read more"; for( var i = 0; i < allParagraphs.length; i++ ) { console.log( allParagraphs[ i ] ); } firstParagraph.appendChild( newLink );

Your Read More link should still be kicking around in the first paragraph as usual, and your console should be rich with filler text (Fig 5.5).

Fig 5.5: Looks like our loop is doing what we expect.

Now we have to hide those paragraphs with display: none, and we have a couple of options: we could use a class the way we did before, but it wouldn’t be a terrible idea to use styles in JavaScript for this. We’re controlling all the hiding and showing from our script, and there’s no chance we’ll want that behavior to be overridden by something in a stylesheet. In this case, it makes sense to use the DOM’s built-in methods for applying styles:

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.setAttribute( "class", "more-link" ); newLink.innerHTML = "Read more"; for( var i = 0; i < allParagraphs.length; i++ ) { allParagraphs[ i ].style.display = "none"; } firstParagraph.appendChild( newLink );

If we reload the page now, everything is gone: our JavaScript loops through the entire list of paragraphs and hides them all. We need to make an exception for the first paragraph, and that means conditional logic—an if statement, and the i variable gives us an easy value to check against:

var newLink = document.createElement( "a" ); var allParagraphs = document.getElementsByTagName( "p" ); var firstParagraph = allParagraphs[ 0 ]; newLink.setAttribute( "href", "#" ); newLink.setAttribute( "class", "more-link" ); newLink.innerHTML = "Read more"; for( var i = 0; i < allParagraphs.length; i++ ) { if( i === 0 ) { continue; } allParagraphs[ i ].style.display = "none"; } firstParagraph.appendChild( newLink );

If this is the first time through of the loop, the continue keyword skips the rest of the current iteration and then—unlike if we’d used break—the loop continues on to the next iteration.

If you reload the page now, we’ll have a single paragraph with a Read More link at the end, but all the others will be hidden. Things are looking good so far—and if things aren’t looking quite so good for you, double-check your console to make sure nothing is amiss.

Now that you’ve got a solid grounding in the DOM, let’s really dig in and see where to take it from here.

Want to read more?

The rest of this chapter (even more than you just read!) goes even deeper—and that’s only one chapter out of Mat’s hands-on, help-you-with-your-current-project guide. Check out the rest of JavaScript for Web Designers at A Book Apart.

Categories: thinktime

You're invited to an all-day Q&A in New York in December

Seth Godin - Tue 18th Oct 2016 21:10
We've been planning this one for months... On Saturday, December 10, I'll be running an all-day session in New York. You can find all the details and tickets by visiting this site. I want to connect you to other people...        Seth Godin
Categories: thinktime

Ketchup and the third-party problem

Seth Godin - Mon 17th Oct 2016 19:10
Sir Kensington's Ketchup is better ketchup. Most adults who try it agree that it's more delicious, a better choice. Alas, Heinz has a host of significant advantages, including dominant shelf space, a Proustian relationship with our childhood and unlimited money...        Seth Godin
Categories: thinktime


Seth Godin - Sun 16th Oct 2016 19:10
The things that break all at once aren’t really a problem. You note that they’ve broken, and then you fix them. The challenge is corrosion. Things that slowly fade, that eventually become a hassle--it takes effort and judgment to decide...        Seth Godin
Categories: thinktime

Your discomfort zone

Seth Godin - Sat 15th Oct 2016 19:10
Most of us need an external stimulus to do our best work. It helps to have an alarm clock if you want to get out of bed before dawn. A presentation. A deadline. A live performance. The threat of foreclosure,...        Seth Godin
Categories: thinktime

Cutting through the clutter

Seth Godin - Fri 14th Oct 2016 19:10
You're trying to get through all the noise and the distraction and the clutter with your message. Here's the thing: You are the noise and the distraction and the clutter. Just because it's important to you doesn't mean it's important...        Seth Godin
Categories: thinktime

What would happen...

Seth Godin - Thu 13th Oct 2016 20:10
if we chose to: Get better at setting and honoring deadlines Help one more person, each day Sit in the front row Ask a hard question every time we go to a meeting Give more and take less Learn to...        Seth Godin
Categories: thinktime

Cable news

Seth Godin - Wed 12th Oct 2016 19:10
What if the fear and malaise and anger isn't merely being reported by cable news... What if it's being caused by cable news? What if ubiquitous video accompanied by frightening and freaked out talking heads is actually, finally, changing our...        Seth Godin
Categories: thinktime

Using CSS Mod Queries with Range Selectors

a list apart - Wed 12th Oct 2016 01:10

Recently, I was asked to build a simple list that would display in a grid—one that could start with a single element and grow throughout the day, yet alway be tidy regardless of the length. So, as you do sometimes when you’re busy with one thing and asked if you can do something completely different, I tried to think of any reason why it couldn’t be done, came up blank, and distractedly said, “Yes.”

At the time, I was working on a London-based news organization’s website. We’d spent the previous year migrating their CMS to the Adobe AEM platform while simultaneously implementing a responsive UI—both big improvements. Since that phase was complete, we were starting to focus on finessing the UI and building new features. The development project was divided into a number of small semiautonomous teams. My team was focusing on hub pages, and I was leading the UI effort.

Each hub page is essentially a list of lists, simply there to help readers find content that interests them. As you can imagine, a news website is almost exclusively made of content lists! A page full of generic vertical lists would be unhelpful and unappealing; we wanted readers to enjoy browsing the content related to their sphere of interest. Sections needed to be distinct and the lists had to be both individually distinguishable and sit harmoniously together. In short, the visual display was critical to the usability and effectiveness of the entire page.

That “simple list” I said I’d build would be high profile, sitting in its own panel near the top of a hub page and serving to highlight a specific point of interest. Starting with one item and growing throughout the day as related articles were published, the list needed to be a rectangular grid rather than a single column, and never have “leftover” items in the last row. And no matter how many child elements it contained at any given moment, it had to stay tidy and neat because it would display above the fold. Each item would be more or less square, with the first item set at 100% width, the second two at 50%, and all subsequent items 33% and arranged in rows of three. My simple list suddenly wasn’t so simple.

Not everyone wants a generic grid or stack of identical items—there’s something nice about selective prominence, grouped elements, and graceful line endings. These styles can be hardcoded if you know the list will always be an exact length, but it becomes more of a challenge when the length can change. How could I keep that last row tidy when there were fewer than three items?

Our intended layout would break visually as more items were added to the list.

When it came to actually building the thing, I realized that knowing the length of the list wasn’t very helpful. Having loved Heydon Pickering’s excellent article on quantity queries for CSS, I assumed I could find out the length of the list using QQs, then style it accordingly and all would be fine.

But since my list could be any length, I’d need an infinite number of QQs to meet the requirements! I couldn’t have a QQ for every eventuality. Plus, there were rumors a “Load More” button might be added down the road, letting users dynamically inject another 10 or so items. I needed a different solution.

After a minor meltdown, I asked myself, What would Lea Verou do? Well, not panicking would be a good start. Also, it would help to simplify and identify the underlying requirements. Since the list would fundamentally comprise rows of three, I needed to know the remainder from mod 3.

The “mod” query

Being able to select and style elements by the number of siblings is great, but there’s more to this than mere length. In this case, it would be much better to know if my list is divisible by a certain number rather than how long it is.

Unfortunately, there isn’t a native mod query in CSS, but we can create one by combining two selectors: :nth-child(3n) (aka the “modulo” selector) and the :first-child selector.

The following query selects everything if the list is divisible by three:

li:nth-last-child(3n):first-child, li:nth-last-child(3n):first-child ~ li { … selects everything in a list divisible by three … } Only those rows divisible by three are selected. See the Pen Using CSS Mod Queries with Range Selectors: Fig 2 by Patrick (@clanceyp) on CodePen. Cat image via Paper Bird Publishing.

Let’s talk through that code. (I use li for “list item” in the examples.)

The css selector:

li:nth-last-child(3n):first-child ~ li

Select all following siblings:

... ~ li

The first child (first li in the list, in this case):

...:first-child ...

Every third item starting from the end of the list:


That combination basically means if the first child is 3n from the end, select all of its siblings.

The query selects all siblings of the first item, but doesn’t include the first item itself, so we need to add a selector for it separately.

li:nth-last-child(3n):first-child, li:nth-last-child(3n):first-child ~ li { … styles for list items in a list divisible by 3 … }

Check out the demo and give it a try!

What about remainders?

With my mod query, I can select all the items in a list if the list is divisible by three, but I’ll need to apply different styles if there are remainders. (In the case of remainder 1, I’ll just need to count back in the CSS from the second-to-last element, instead of the last. This can be achieved by simply adding +1 to the query.)

li:nth-last-child(3n+1):first-child, li:nth-last-child(3n+1):first-child ~ li { … styles for elements in list length, mod 3 remainder = 1 … }

Ditto for remainder 2—I just add +2 to the query.

li:nth-last-child(3n+2):first-child, li:nth-last-child(3n+2):first-child ~ li { … styles for elements in list length, mod 3 remainder = 2 … } Creating a range selector

Now I have a way to determine if the list length is divisible by any given number, with or without remainders, but I still need to select a range. As with mod query, there isn’t a native CSS range selector, but we can create one by combining two selectors: :nth-child(n) (i.e., “everything above”) and :nth-child(-n) (i.e., “everything below”).

This allows us to select items 3 to 5, inclusive:

li:nth-child(n+3):nth-child(-n+5){ ... styles for items 3 to 5 inclusive ... } We’ve selected a range: cats 3, 4, and 5.

True, that could just as easily be achieved with simple :nth-child(n) syntax and targeting the item positions directly—li:nth-child(3), li:nth-child(4), li:nth-child(5){ ... }—but defining a start and end to a range is obviously much more versatile. Let’s quickly unpack the selector to see what it’s doing.

Selects all the items up to and including the fifth item:

li:nth-child(n+3):nth-child(-n+5){ … }

Selects all the items from the third item onwards:

li:nth-child(n+3):nth-child(-n+5){ … }

Combining the two—li:nth-child(n+3):nth-child(-n+5)—creates a range selector.

If we look at an example, we might have a product grid where the list items contain an image, title, and description. Let’s say the product image speaks for itself, so in the first row we promote the image and hide all the text. With the second and third row, we display the title and image as a thumbnail, while in subsequent rows we hide the image and show the title and description on a single line.

A product grid of our cats. We have standalone graphics in the top row, small graphics plus product titles in the second and third rows, and then we lose the graphics and only show text for all rows after that. See the Pen Using CSS Mod Queries with Range Selectors: Fig 4 by Patrick (@clanceyp) on CodePen.

By using the range selector, we can select the first three, the fourth through ninth, and the 10th onwards. This allows us to change the ranges at different breakpoints in the CSS so we can keep our product grid nice and responsive.

Notes on SCSS mixins

Since I was using a CSS preprocessor, I simplified my code by using preprocessor functions; these are SCSS mixins for creating range selectors and mod queries.

// range selector mixin @mixin select-range($start, $end){ &:nth-child(n+#{$start}):nth-child(-n+#{$end}){ @content; } } // mod query mixin @mixin mod-list($mod, $remainder){ &:nth-last-child(#{$mod}n+#{$remainder}):first-child, &:nth-last-child(#{$mod}n+#{$remainder}):first-child ~ li { @content; } }

Then in my code I could nest the mixins.

li { @include mod-list(3, 0){ @include select-range(3, 5){ // styles for items 3 to 5 in a list mod 3 remainder = 0 } } }

Which is, if nothing else, much easier to read!

Putting it all together

So now that I have a little arsenal of tools to help me deal with mods, ranges, and ranges within mods, I can break away from standard-implementation fixed length or fixed-layout lists. Creative use of mod queries and range selectors lets me apply styles to change the layout of elements.

Getting back to the original requirement—getting my list to behave—it became clear that if I styled the list assuming it was a multiple of three, then there would only be two other use cases to support:

  • Mod 3, remainder 1
  • Mod 3, remainder 2

If there was one remaining item, I’d make the second row take three items (instead of the default two), but if the remainder was 2, I could make the third row take two items (with the fourth and fifth items at 50%).

In the end, I didn’t need numerous queries at all, and the ones I did need were actually quite simple.

There was one special case: What if the list only contained two elements?

That was solved with a query to select the second item when it’s also the last child.

li:nth-child(2):last-child { ... styles for the last item if it’s also the second item ... }

The queries ultimately weren’t as hard as I’d expected; I just needed to combine the mod and range selectors.

li:nth-last-child(3n):first-child /* mod query */ ~ li:nth-child(n+3):nth-child(-n+5){ /* range selector */ ... styles for 3rd to 5th elements, in a list divisible by 3 ... }

Altogether, my CSS looked something like this in the end:

/* default settings for list (when its mod 3 remainder 0) list items are 33% wide except; the first item is 100% the second and third are 50% */ li { width: 33.33%; } li:first-child { width: 100%; } /* range selector for 2nd and 3rd */ li:nth-child(n+2):nth-child(-n+3){ width: 50%; } /* overrides */ /* mod query override, check for mod 3 remainder = 1 */ li:nth-last-child(3n+1):first-child ~ li:nth-child(n+2):nth-child(-n+3) { width: 33.33%; /* override default 50% width for 2nd and 3rd items */ } /* mod query override, check for mod 3 remainder = 2 */ li:nth-last-child(3n+2):first-child ~ li:nth-child(n+4):nth-child(-n+5) { width: 50%; /* override default 33% width for 4th and 5th items */ } /* special case, list contains only two items */ li:nth-child(2):last-child { margin-left: 25%; } Experience for yourself (and a note on browser support)

The mod queries and range selectors used in this article rely on the CSS3 selectors, so they will work in all modern browsers that support CSS3, including Internet Explorer 9 and above (but remember, IE will expect a valid doctype).

I created a small mod query generator that you can use to experiment with mod queries.

When I first came across QQs, I thought they were great and interesting but largely theoretical, without many practical real-world use cases. However, with mobile usage outstripping desktop, and responsive design now the norm, the need to display lists, target parts of lists depending on the length/mod, and display lists differently at different breakpoints has become much more common. This really brings the practical application of QQs into focus, and I’m finding more than ever that they are an essential part of the UI developer’s toolkit.

Additional resources
Categories: thinktime

Now is never (but here comes tomorrow)

Seth Godin - Tue 11th Oct 2016 20:10
Everything you're working on is an investment in tomorrow. While we can choose to enjoy the process, the end result is always at the end of an arc, always the result of many steps, of earning trust, of building a...        Seth Godin
Categories: thinktime


Seth Godin - Mon 10th Oct 2016 20:10
When creating a layout, designers put low-resolution, imperfect, non-final images, all marked "for position only." They exist to help the client understand the gestalt of the piece and to give feedback. They're temporary, parts of a whole ready to be...        Seth Godin
Categories: thinktime

Visualize the leaks

Seth Godin - Sun 09th Oct 2016 20:10
It's almost impossible to walk past a spewing faucet without stopping and trying to turn it off. We can't bear to see the waste. But our organizations leak all the time. The talented people who don't stick with the job...        Seth Godin
Categories: thinktime

"Here, I made this"

Seth Godin - Sat 08th Oct 2016 20:10
"I" as in me, you, us, the person who's on the line. This is the work of a human. The audience can make a direct connection between you and the thing you're offering. "Made" because it took effort, originality and...        Seth Godin
Categories: thinktime

The chance of a lifetime

Seth Godin - Fri 07th Oct 2016 19:10
That would be today. And every day, if you're up for it. The things that change our lives (and the lives of others) are rarely the long-scheduled events, the much-practiced speeches or the annual gala. No, it's almost certain that...        Seth Godin
Categories: thinktime

Do what you're good at, or...

Seth Godin - Thu 06th Oct 2016 20:10
get really good at what you do. You have nearly unlimited strategic choices and options about your career and what your organization does. Which means you can focus on doing things you are truly good at. Or, if a particular...        Seth Godin
Categories: thinktime

Breakage vs. references

Seth Godin - Wed 05th Oct 2016 20:10
Years ago, I asked fabled direct marketer Joe Sugarman about the money-back guarantee he offered on the stuff he sold through magazine ads. He said 10% of the people who bought asked for their money back... and if any product...        Seth Godin
Categories: thinktime

A Redesign with CSS Shapes

a list apart - Wed 05th Oct 2016 01:10

Here at An Event Apart (an A List Apart sibling) we recently refreshed the design of our “Why Should You Attend?” page, which had retained an older version of our site design and needed to be brought into alignment with the rest of the site. Along the way, we decided to enhance the page with some cutting-edge design techniques: non-rectangular float shapes and feature queries.

To be clear, we didn’t set out to create a Cutting Edge Technical Example™; rather, our designer (Mike Pick of Monkey Do) gave us a design, and we realized that his vision happened to align nicely with new CSS features that are coming into mainstream support. We were pleased enough with the results and the techniques that we decided to share them with the community.

Styling bubbles

Here are some excerpts from an earlier stage of the designs (Fig. 1). (The end-stage designs weren’t created as comps, so I can’t show their final form, but these are pretty close.)

Fig 1: Late-stage design comps showing “desktop” and “mobile” views.

What interested me was the use of the circular images, which at one point we called “portholes,” but I came to think of as “bubbles.” As I prepared to implement the design in code, I thought back to the talk Jen Simmons has been giving throughout the year at An Event Apart. Specifically, I thought about CSS Shapes and how I might be able to use them to let text flow along the circles’ edges—something like Fig. 2.

Fig 2: Flowing around a circular shape.

This layout technique used to be sort of possible by using crude float hacks like Ragged Float and Sliced Sandbags, but now we have float shapes! We can define a circle—or even a polygon—that describes how text should flow past a floated element.

“Wait a minute,” you may be saying, “I haven’t heard about widespread support for Shapes!” Indeed, you have not. They’re currently supported only in the WebKit/Blink family—Chrome, Safari, and Opera. But that’s no problem: in other browsers, the text will flow past the boxy floats the same way it always has. The same way it does in the design comps, in fact.

The basic CSS looks something like this:

img.bubble.left { float: left; margin: 0 40px 0 0 ; shape-outside: circle(150px at 130px 130px); } img.bubble.right { float: right; margin: 0 0 0 40px; shape-outside: circle(150px at 170px 130px); }

Each of those bubble images, by the way, is intrinsically 260px wide by 260px tall. In wide views like desktops, they’re left to that size; at smaller widths, they’re scaled to 30% of the viewport’s width.

Shape placement

To understand the shape setup, look at the left-side bubbles. They’re 260×260, with an extra 40 pixels of right margin. That means the margin box (that is, the box described by the outer edge of the margins) is 300 pixels wide by 260 pixels tall, with the actual image filling the left side of that box.

This is why the circular shape is centered at the point 130px 130px—it’s the midpoint of the image in question. So the circle is now centered on the image, and has a radius of 150px. That means it extends 20 pixels beyond the visible outer edge of the circle, as shown here (Fig. 3).

Fig 3: The 150px radius of the shape covers the entire visible part of the image, plus an extra 20px

In order to center the circles on the right-side bubbles, the center point has to be shifted to 170px 130px—traversing the 40-pixel left margin, and half the width of the image, to once again land on the center. The result is illustrated here, with annotations to show how each of the circles’ centerpoints are placed (Fig. 4).

Fig 4: Two of the circular shapes, as highlighted by Chrome’s Inspector and annotated in Keynote (!)

It’s worth examining that screenshot closely. For each image, the light blue box shows the element itself—the img element. The light orange is the basic margin area, 40 pixels wide in each case. The purple circle shows the shape-outside circle. Notice how the text flows into the orange area to come right up against the purple circle. That’s the effect of shape-outside. Areas of the margin outside that shape, and even areas of the element’s content outside the shape, are available for normal-flow content to flow into.

The other thing to notice is the purple circle extending outside the margin area.  This is misleading: any shape defined by shape-outside is clipped at the edge of the element’s margin box. So if I were to increase the circle’s radius to, say, 400 pixels, it would cover half the page in Chrome’s inspector view, but the actual layout of text would be around the margin edges of the floated image—as if there were no shape at all. I’d really like to see Chrome show this by fading the parts of the shape that extend past the margin box. (Firefox and Edge should of course follow suit!)

Being responsive

At this point, things seem great; the text flows past circular float shapes in Chrome/Safari/Opera, and past the standard boxy margin boxes in Firefox/Edge/etc. That’s fine as long as the page never gets so narrow as to let text wrap between bubbles—but, of course, it will, as we see in this screenshot (Fig. 5).

Fig 5: The perils of floats on smaller displays

For the right-floating images, it’s not so bad—but for the left floaters, things aren’t as nice. This particular situation is passably tolerable, but in a situation where just one or two words wrap under the bubble, it will look awful.

An obvious first step is to set some margins on the paragraphs so that they don’t wrap under the accompanying bubbles. For example:

.complex-content div:nth-child(even):not(:last-child) p { margin-right: 20%; } .complex-content div:nth-child(odd):not(:last-child) p { margin-left: 20%; }

The point here being, for all even-numbered child divs (that aren’t the last child) in a complex-content context, add a 20% right margin; for the odd-numbered divs, a similar left margin.

That’s pretty good in Chrome (Fig. 6) (with the circular float shapes) because the text wraps along the bubble and then pushes off at a sensible point. But in Firefox, which still has the boxy floats, it creates a displeasing stairstep effect (Fig. 7).

Fig 6: Chrome (with float shapes) Fig 7: Firefox (without float shapes)

On the flip side, increasing the margin to the point that the text all lines up in Firefox (33% margins) would mean that the float shape in Chrome would be mostly pointless, since the text would never flow down along the bottom half of the circles.

Querying feature support

This is where @supports came into play. By using @supports to run a feature query, I could set the margins for all browsers to the 33% needed when shapes aren’t supported, and then reduce it for browsers that do understand shapes. It goes something like this:

.complex-content div:nth-child(even):not(:last-child) p { margin-right: 33%; } .complex-content div:nth-child(odd):not(:last-child) p { margin-left: 33%; } @supports (shape-outside: circle()) { .complex-content div:nth-child(even):not(:last-child) p { margin-right: 20%; } .complex-content div:nth-child(odd):not(:last-child) p { margin-left: 20%; } }

With that, everything is fine in the two worlds (Fig. 8 and Fig. 9). There are still a few things that could be tweaked, but overall, the effect is pleasing in browsers that support float shapes, and also those that don’t. The two experiences are shown in the following videos. (They don’t autoplay, so click at your leisure.)

ALA CSS Bubble Example Captured in Chrome; higher resolution available (mp4, 3.3MB) ALA CSS Bubble Example in Firefox Captured in Firefox; higher resolution available (mp4, 2.9MB)

Thanks to feature queries, as browsers like Firefox and MS Edge add support for float shapes, they’ll seamlessly get the experience that currently belongs only to Chrome and its bretheren.  There’s no browser detection to adjust later, no hacks to clear out. There’s only silent progressive enhancement baked right into the CSS itself.  It’s pretty much “style and forget.”

While an arguably minor enhancement, I really enjoyed the process of working with shapes and making them progressively and responsively enhanced. It’s a nice little illustration of how we can use advanced features of CSS right now, without the usual wait for widespread support. This is a general pattern that will see a lot more use as we start to make use of shapes, flexbox, grid, and more cutting-edge layout tools, and I’m glad to be able to offer this case study.

Further reading

If you’d like to know more about float shapes and feature queries, I can do little better than to recommend the following articles.

Categories: thinktime

Indomitable is a mirage

Seth Godin - Tue 04th Oct 2016 19:10
One seductive brand position is the posture of being indomitable. Unable to be subdued, incapable of loss, the irresistible force and the immovable object, all in one. The public enjoys rooting for this macho ideal. Superman in real life, but...        Seth Godin
Categories: thinktime

Enough ethics?

Seth Godin - Mon 03rd Oct 2016 19:10
Most companies seek to be more profitable. They seek to increase their Key Performance Indicators. More referrals, more satisfaction, more loyalty. They seek to increase their market share, their dividends, their stock price. But ethics? In fact, most companies strive...        Seth Godin
Categories: thinktime


Subscribe to kattekrab aggregator