Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
/*
Adapt.js licensed under GPL and MIT.
Read more here: http://adapt.960.gs
*/
// Closure.
(function(w, d, config, undefined) {
// If no config, exit.
if (!config) {
return;
}
// Empty vars to use later.
var url, url_old, timer;
// Alias config values.
var path = config.path;
var range = config.range;
var range_len = range.length;
// Create empty link tag:
// <link rel="stylesheet" />
var css = d.createElement('link');
css.rel = 'stylesheet';
// Adapt to width.
function adapt() {
// This clearInterval is for IE.
// Really it belongs in react(),
// but doesn't do any harm here.
clearInterval(timer);
// Parse browser width.
var x = w.innerWidth || d.documentElement.clientWidth || d.body.clientWidth || 0;
// While loop vars.
var arr, arr_0, val_1, val_2, is_range, file;
// How many ranges?
var i = range_len;
var last = range_len - 1;
while (i--) {
// Turn string into array.
arr = range[i].split('=');
// Width is to the left of "=".
arr_0 = arr[0];
// File name is to the right of "=".
// Presuppoes a file with no spaces.
file = arr[1].replace(/\s/g, '');
// Assume min/max if "to" isn't present.
is_range = arr_0.match('to');
// If it's a range, split left/right sides of "to",
// and then convert each one into numerical values.
// If it's not a range, turn min/max into a number.
val_1 = is_range ? parseInt(arr_0.split('to')[0], 10) : parseInt(arr_0, 10);
val_2 = is_range ? parseInt(arr_0.split('to')[1], 10) : undefined;
// Check for: minimum, maxiumum, range.
if ((!val_2 && i === 0 && x <= val_1) || (!val_2 && range_len > 1 && i === last && x > val_1) || (x > val_1 && x <= val_2)) {
// Built full URL to CSS file.
url = path + file;
break;
} else {
// Blank if no conditions met.
url = '';
}
}
// Was it created yet?
if (url_old && url_old !== url) {
// If so, just set the URL.
css.href = url;
url_old = url;
} else {
// If not, set URL and append to DOM.
css.href = url;
url_old = url;
// Use faster document.head if possible.
(d.head || d.getElementsByTagName('head')[0]).appendChild(css);
}
}
// Fire off once.
adapt();
// Slight delay.
function react() {
// Clear interval as window resize fires,
// so that it only calls adapt() when the
// user has finished resizing the window.
clearInterval(timer);
timer = setInterval(adapt, 100);
}
// Do we want to watch for
// resize and device tilt?
if (config.dynamic) {
// Event listener for window resize,
// also triggered by phone rotation.
if (w.addEventListener) {
// Good browsers.
w.addEventListener('resize', react, false);
} else if (w.attachEvent) {
// Legacy IE support.
w.attachEvent('onresize', react);
} else {
// Old-school fallback.
w.onresize = react;
}
}
// Pass in window, document, config, undefined.
})(this, this.document, ADAPT_CONFIG);
@marioestrada

This comment has been minimized.

Show comment Hide comment
@marioestrada

marioestrada Apr 6, 2011

Instead of loading specific CSS files, I would go for adding a specific class to the <html> tag:

<html class="w_800">

Instead of loading specific CSS files, I would go for adding a specific class to the <html> tag:

<html class="w_800">
@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 6, 2011

You could also do that.

The benefit here is that you're not sending down any CSS that isn't absolutely needed. To attempt to load faster on mobile. With what you're proposing, you'd send down CSS for all possible scenarios, and then scope it via class-based descendent selectors.

Owner

nathansmith commented Apr 6, 2011

You could also do that.

The benefit here is that you're not sending down any CSS that isn't absolutely needed. To attempt to load faster on mobile. With what you're proposing, you'd send down CSS for all possible scenarios, and then scope it via class-based descendent selectors.

@tiffehr

This comment has been minimized.

Show comment Hide comment
@tiffehr

tiffehr Apr 6, 2011

I think this would work nicely. I'd like to see server-side CSS merging & compression lend a hand, were this a living site. Where the JS builds a call for a CSS assets, it could get back a single reply. Perhaps something like getMergedMediaFriendlyCSS.js?480,800,homepage where 400 & 800 would be media-query wrapped CSS and homepage could be page-specific rules? Of course, caching would be weird. But it could alleviate the need to watch for a resize event.

I suppose the decision-maker is how much CSS lives within each media-query block. If it's just a few lines, that's negligible. If it's a bunch of stuff targeting a specific device group—like major header/footer changes—that's where this makes sense.

tiffehr commented Apr 6, 2011

I think this would work nicely. I'd like to see server-side CSS merging & compression lend a hand, were this a living site. Where the JS builds a call for a CSS assets, it could get back a single reply. Perhaps something like getMergedMediaFriendlyCSS.js?480,800,homepage where 400 & 800 would be media-query wrapped CSS and homepage could be page-specific rules? Of course, caching would be weird. But it could alleviate the need to watch for a resize event.

I suppose the decision-maker is how much CSS lives within each media-query block. If it's just a few lines, that's negligible. If it's a bunch of stuff targeting a specific device group—like major header/footer changes—that's where this makes sense.

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 6, 2011

Tiff: Yeah, that'd be cool. I was thinking the site-wide styles: typography, colors, branding, etc. could live in a minified / concatenated file, and then the conditionally loaded CSS would be a minified grid / layout only.

Owner

nathansmith commented Apr 6, 2011

Tiff: Yeah, that'd be cool. I was thinking the site-wide styles: typography, colors, branding, etc. could live in a minified / concatenated file, and then the conditionally loaded CSS would be a minified grid / layout only.

@tiffehr

This comment has been minimized.

Show comment Hide comment
@tiffehr

tiffehr Apr 6, 2011

For the recently redesigned Today.com, we had a similar mix of sitewide & grid-based CSS though ultimately some business decisions came down that gutted our best media-query-based ideas (ads, of course). What we ended up with is a backup media-query solution, where modern browsers get the media-query-wrapped ruleset but <=IE8 gets an extra copy via conditional comments. Same rules twice, which sucks given the number of rules. (<=IE8 also gets an IE-hack CSS file that's large anyway, so the pain was already present.)

I wish media-queries had an optional pass-through for browsers that don't understand them.

tiffehr commented Apr 6, 2011

For the recently redesigned Today.com, we had a similar mix of sitewide & grid-based CSS though ultimately some business decisions came down that gutted our best media-query-based ideas (ads, of course). What we ended up with is a backup media-query solution, where modern browsers get the media-query-wrapped ruleset but <=IE8 gets an extra copy via conditional comments. Same rules twice, which sucks given the number of rules. (<=IE8 also gets an IE-hack CSS file that's large anyway, so the pain was already present.)

I wish media-queries had an optional pass-through for browsers that don't understand them.

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 6, 2011

Tiff: Me too. I was surprised to find that all the CSS files on this page get downloaded, even by modern browsers...

http://j.mp/g4trwR

Owner

nathansmith commented Apr 6, 2011

Tiff: Me too. I was surprised to find that all the CSS files on this page get downloaded, even by modern browsers...

http://j.mp/g4trwR

@tiffehr

This comment has been minimized.

Show comment Hide comment
@tiffehr

tiffehr Apr 6, 2011

Here's another thought: What about inserting the media attribute conditions on the link tags via JS? Doesn't solve the downloaded-resource issue but it would add fall-back behavior for older browsers?

tiffehr commented Apr 6, 2011

Here's another thought: What about inserting the media attribute conditions on the link tags via JS? Doesn't solve the downloaded-resource issue but it would add fall-back behavior for older browsers?

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 7, 2011

Tiff: I'm not sure I follow. Could you make a gist so I can better understand what you mean?

Owner

nathansmith commented Apr 7, 2011

Tiff: I'm not sure I follow. Could you make a gist so I can better understand what you mean?

@tiffehr

This comment has been minimized.

Show comment Hide comment
@tiffehr

tiffehr Apr 7, 2011

Okay, built and tested my version: https://gist.github.com/907079

Or http://www.beermestrength.com/media_query_test.html (try it in a modern browser & IE8, for comparison)

The goal was to add media-query logic after older browsers download the CSS assets. This dosen't make a lot of sense for your Grid example, but could if the media-query logic was an additive effect.

tiffehr commented Apr 7, 2011

Okay, built and tested my version: https://gist.github.com/907079

Or http://www.beermestrength.com/media_query_test.html (try it in a modern browser & IE8, for comparison)

The goal was to add media-query logic after older browsers download the CSS assets. This dosen't make a lot of sense for your Grid example, but could if the media-query logic was an additive effect.

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 9, 2011

Tiff: I was thinking of something like this...

<script
  src="assets/js/adapt.js"
  data-path="assets/css/"
  data-min="480=480.css"
  data-range="480-960=800.css"
  data-range="960-1280=960.css"
  data-range="1280-1600=1280.css"
  data-max="1600=1280.css"
></script>

Then the JS could parse all the values off its own <script> tag.

I see where you were going with media queries, but wanted to keep this JS as an alternative to using them :)

Owner

nathansmith commented Apr 9, 2011

Tiff: I was thinking of something like this...

<script
  src="assets/js/adapt.js"
  data-path="assets/css/"
  data-min="480=480.css"
  data-range="480-960=800.css"
  data-range="960-1280=960.css"
  data-range="1280-1600=1280.css"
  data-max="1600=1280.css"
></script>

Then the JS could parse all the values off its own <script> tag.

I see where you were going with media queries, but wanted to keep this JS as an alternative to using them :)

@tiffehr

This comment has been minimized.

Show comment Hide comment
@tiffehr

tiffehr Apr 9, 2011

Gotcha. I usually hesitate to have anything watching document resize/scroll events. Hence trying out my little model, since media-queries serve that purpose. But that has little to do with your solution, more me exploring whether browsers observe JS-inserted media attributes. Which they do, so that's cool (and I have some clean-up to do).

I like the implications of your data-* attributes on that script tag. I wonder if something like this would work, given those args:

<script src="assets/js-adapt.js" data-path="assets/css/" data-range="{"480":"480.css","480-960":"800.css","960-1280":"960.css","1280-1600":"1280.css","1600":"1280.css"}"></script> or another JS object structure to collect the ranges.

tiffehr commented Apr 9, 2011

Gotcha. I usually hesitate to have anything watching document resize/scroll events. Hence trying out my little model, since media-queries serve that purpose. But that has little to do with your solution, more me exploring whether browsers observe JS-inserted media attributes. Which they do, so that's cool (and I have some clean-up to do).

I like the implications of your data-* attributes on that script tag. I wonder if something like this would work, given those args:

<script src="assets/js-adapt.js" data-path="assets/css/" data-range="{"480":"480.css","480-960":"800.css","960-1280":"960.css","1280-1600":"1280.css","1600":"1280.css"}"></script> or another JS object structure to collect the ranges.

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 9, 2011

Tiff:

Good call. Less attribute retrieval, and can immediately be turned into a JS object. Think on this further, I will. #Yoda

Owner

nathansmith commented Apr 9, 2011

Tiff:

Good call. Less attribute retrieval, and can immediately be turned into a JS object. Think on this further, I will. #Yoda

@mathiasbynens

This comment has been minimized.

Show comment Hide comment
@mathiasbynens

mathiasbynens Apr 11, 2011

May I suggest using document.head when it’s available? It’s as simple as changing line 43 into:

(d.head || d.getElementsByTagName('head')[0]).appendChild(css);

Typo: the example in the comment on line 9 should read id="ADAPT_CSS" instead of id="adaptive_css".

May I suggest using document.head when it’s available? It’s as simple as changing line 43 into:

(d.head || d.getElementsByTagName('head')[0]).appendChild(css);

Typo: the example in the comment on line 9 should read id="ADAPT_CSS" instead of id="adaptive_css".

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 11, 2011

@mathiasbynens

Changing line 43, as you suggested, would cause it not to work in browsers that understand document.head. A browser that understands document.head would never evaluate past the "or" conditional "||".

d.head || d.getElementsByTagName('head')[0].appendChild(css);

Instead, it would need to be...

d.head ? d.head.appendChild(css) : d.getElementsByTagName('head')[0].appendChild(css);

But that's extra code, when I'm only ever referencing <head> once. I agree that making a shortcut to it, as that article suggests, is a good idea if you're going to be using that convention repeatedly throughout your code. But for this one-liner, I don't think it really matters, because I would still have to add the lengthy selector for old browsers. By doing it only via getElementsByTagName, it works everywhere, and is less code overall.

Owner

nathansmith commented Apr 11, 2011

@mathiasbynens

Changing line 43, as you suggested, would cause it not to work in browsers that understand document.head. A browser that understands document.head would never evaluate past the "or" conditional "||".

d.head || d.getElementsByTagName('head')[0].appendChild(css);

Instead, it would need to be...

d.head ? d.head.appendChild(css) : d.getElementsByTagName('head')[0].appendChild(css);

But that's extra code, when I'm only ever referencing <head> once. I agree that making a shortcut to it, as that article suggests, is a good idea if you're going to be using that convention repeatedly throughout your code. But for this one-liner, I don't think it really matters, because I would still have to add the lengthy selector for old browsers. By doing it only via getElementsByTagName, it works everywhere, and is less code overall.

@mathiasbynens

This comment has been minimized.

Show comment Hide comment
@mathiasbynens

mathiasbynens Apr 11, 2011

Sorry, I made a mistake in my last comment. I’ve edited it now, but here is the proper solution again:

(d.head || d.getElementsByTagName('head')[0]).appendChild(css);

And yeah, it’s like 10 more bytes, but IMHO the speed gain in modern browsers is worth it.

Sorry, I made a mistake in my last comment. I’ve edited it now, but here is the proper solution again:

(d.head || d.getElementsByTagName('head')[0]).appendChild(css);

And yeah, it’s like 10 more bytes, but IMHO the speed gain in modern browsers is worth it.

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 11, 2011

@mathiasbynens

Boom, updated! :)

Owner

nathansmith commented Apr 11, 2011

@mathiasbynens

Boom, updated! :)

@nathansmith

This comment has been minimized.

Show comment Hide comment
@nathansmith

nathansmith Apr 17, 2011

I thought I would note that I made this its own project:

http://adapt.960.gs

I'll leave this gist here for posterity, but won't be updating it any more.

Owner

nathansmith commented Apr 17, 2011

I thought I would note that I made this its own project:

http://adapt.960.gs

I'll leave this gist here for posterity, but won't be updating it any more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment