-
-
Save nathansmith/905220 to your computer and use it in GitHub Desktop.
/* | |
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); |
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.
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.
Tiff: Me too. I was surprised to find that all the CSS files on this page get downloaded, even by modern browsers...
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?
Tiff: I'm not sure I follow. Could you make a gist so I can better understand what you mean?
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.
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 :)
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.
Tiff:
Good call. Less attribute retrieval, and can immediately be turned into a JS object. Think on this further, I will. #Yoda
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"
.
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.
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.
Boom, updated! :)
I thought I would note that I made this its own project:
I'll leave this gist here for posterity, but won't be updating it any more.
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
where400
&800
would be media-query wrapped CSS andhomepage
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.