Adapt JS explained
This explains the origins of, and how to use, Adapt.js - (skip to How to use)
First Off
Let me say specifically that Adapt.js does not purport to be Responsive Web Design. The definition of RWD, as Ethan Marcotte initially described it, specifically involves using both: fluid widths and @media queries. He has further clarified this point through discussions on Twitter, posts on his blog and public presentations.
I just wanted to be clear that I never tagged Adapt.js as "responsive" — and preemptively (before launching Adapt.js) assured Ethan privately via email that I would not attempt to muddy the waters. Nevertheless, some have made that association on their own. I emphasize this distinction because I don't want Ethan's RWD efforts (or book) to go the way of HTML5, where marketers grab ahold of the term, and repurpose it to mean whatever is bright, shiny, and new.
Waxing Poetic
If anything, Adapt.js heralds from back alleys of the old-school. It depends not on CSS3, but on properties that can be reliably detected using JS - across multiple versions of multiple browsers, on multiple devices. Considering that @media queries require a polyfill for older browsers, namely everything up to (and including) IE8(Internet Explorer 8), JavaScript has to be involved either way. That may seem like backwards thinking, but remember that JavaScript predates the existence of CSS anyway.
Don't get me wrong, I certainly see the appeal of being able to manage everything via CSS. In fact, before learning JavaScript, I used to pride myself on really knowing CSS (before the days of CSS frameworks). After dabbling in JavaScript, I realized something. Now it's a point I've made repeatedly, with aspiring front-end developers and designers, about rounding out one's skill-set…
Mastering CSS is akin to building amazing things with Legos. Understanding JavaScript is like manufacturing Legos. Though related, they are simultaneously altogether different.
But I digress. When I first read Ethan's post, I whipped up a (failed) experiment to see if specifying media="…"
could be used to conditionally serve/block CSS files, based on browser width. If you're curious, that can be seen here…
host.sonspring.com/media_queries
I was disappointed to learn that as with media="print"
stylesheets, the entirety of the code is downloaded by the browser, on the off-chance it might be needed. Unlike the tech savvy web developers amongst us, the majority of the general public do not constantly resize their browsers looking for easter eggs.
Grey's
So instead, I decided to let the idea sit on the shelf for awhile. Until, one evening while watching Grey's Anatomy via Netflix with my wife, it hit me. If I am really concerned about testing for width, why not test for width, using an age-old technique? ("Old" is relative, when we're talking about the Internet.)
So I grabbed my laptop and whipped up what ended up being a less than 1KB (minified) snippet of JavaScript that would conditionally create…
<link rel="stylesheet" href="…" />
…based on browser width. Over the next few evenings, I whittled down to the essential code. A few weeks later, based on a few requests via Twitter and GitHub, I added the ability to specify a callback function that fires as the page loads/resizes. This allows your code to take action once Adapt.js does its work.
Note: As cheesy as TV medical dramas might be, I mention that the idea came whilst distracted, because sometimes our most inspirational moments hit us when we're not actively looking for them, but are engaged in passive thought.
How To Use
In all the following examples, feel free to drop the px
suffix. It is just there to emphasize that the calculations done by Adapt.js are pixel-based.
At the bare minimum, if all you want is to serve just the CSS needed on page load, but not worry about browser resize and whatnot, all you'd really need is this:
var ADAPT_CONFIG = {
path: 'assets/css/',
range: [
'0px to 760px = mobile.css',
'760px to 980px = 720.css',
'980px to 1280px = 960.css',
'1280px to 1600px = 1200.css',
'1600px to 1920px = 1560.css',
'1920px = fluid.css',
],
};
Bear in mind, you needn't use these naming conventions for your CSS files. "720.css" could just as easily be "peanut_butter.css" or any other name.
Now let's suppose you want to put all your mobile styles in your main stylesheet, and only add extra styles above a certain width threshold. The following code is basically saying "If the width is greater than 760px, add desktop.css." Adapt.js knows this, because it's the last (only) entry in the range array.
var ADAPT_CONFIG = {
path: 'assets/css/',
range: ['760px = desktop.css'],
};
If you want to adapt to browser resize and device tilt, add the dynamic
flag.
var ADAPT_CONFIG = {
path: 'assets/css/',
dynamic: true,
range: ['760px = desktop.css'],
};
Callback
For JS you want to fire when the page resizes, specify a callback function.
var ADAPT_CONFIG = {
path: 'assets/css/',
dynamic: true,
callback: myCallback,
range: [
'0px to 760px = mobile.css',
'760px to 980px = 720.css',
'980px to 1280px = 960.css',
'1280px to 1600px = 1200.css',
'1600px to 1920px = 1560.css',
'1920px = fluid.css',
],
};
If you don't want to swap CSS files, simply leave them out, and don't specify path
. That will still allow you take advantage of Adapt.js to trigger a callback function.
var ADAPT_CONFIG = {
dynamic: true,
callback: myCallback,
range: ['0 to 760', '760 to 980', '980 to 1280', '1280 to 1600', '1600 to 1920', '1920'],
};
HTML - Class Name
The callback is passed range
index and width — myCallback(i, width)
. Based on that info, you could do any number of things. One common use case, I'm assuming, would be to add a class name to the HTML tag. That would allow you to make slight tweaks, via descendant selectors in your master CSS file.
For the sake of discussion, I'm using the function name myCallback
. However, you could just as easily name your callback function myLittlePony
, captainAmerica
, or specify it as part of an application namespace like MY.app.adapt_callback
.
In the example below, i
is the index of the range array. Since arrays in JS (really, every language) are zero indexed, this means that the first one would be 0
, the second would be 1
, etc. Don't blame me, I didn't make the rules!
function myCallback(i, width) {
// Alias HTML tag.
var html = document.documentElement;
// Find all instances of range_NUMBER and kill 'em.
html.className = html.className.replace(/(\s+)?range_\d/g, '');
// Check for valid range.
if (i > -1) {
// Add class="range_NUMBER"
html.className += ' range_' + i;
}
// Note: Not making use of width here, but I'm sure
// you could think of an interesting way to use it.
}
In your master CSS file, you could do style overrides, like so…
.foobar {
/* Default styles here. */
}
html.range_0 .foobar {
/* Style overrides for: 0px to 760px */
}
html.range_1 .foobar {
/* Style overrides for: 760px to 980px */
}
/* etc. */
HTML - ID
If you prefer to use an id
selector instead, assuming you don't already have something like id="example_com"
or id="example_org"
on your HTML tag, you could define an extremely simple callback function. Like so…
function myCallback(i) {
// Replace HTML tag's ID.
document.documentElement.id = 'range_' + i;
}
That yields a stronger CSS selector to style against (id
trumps class
).
html#range_0 .foobar {
/* Style overrides for: 0px to 760px */
}
/* etc. */
That's just one example. Your callback could do whatever you want, allowing the file size of Adapt.js to remain lightweight. Just make sure to define your callback function before referencing the adapt.js
file, so that it will be recognized.
That's a Wrap
I think that about covers it. It is my sincere hope that Adapt.js will help alleviate some development woes. If you think I've left anything out, feel free to contact me and/or hit me up on Twitter. I will do my best to answer any questions.