1105 lines
28 KiB
JavaScript
1105 lines
28 KiB
JavaScript
/*
|
|
css3-mediaqueries.js - CSS Helper and CSS3 Media Queries Enabler
|
|
|
|
author: Wouter van der Graaf <wouter at dynora nl>
|
|
version: 1.0 (20110330)
|
|
license: MIT
|
|
website: http://code.google.com/p/css3-mediaqueries-js/
|
|
|
|
W3C spec: http://www.w3.org/TR/css3-mediaqueries/
|
|
|
|
Note: use of embedded <style> is not recommended when using media queries, because IE has no way of returning the raw literal css text from a <style> element.
|
|
*/
|
|
|
|
|
|
// true prototypal inheritance (http://javascript.crockford.com/prototypal.html)
|
|
if (typeof Object.create !== 'function') {
|
|
Object.create = function (o) {
|
|
function F() {}
|
|
F.prototype = o;
|
|
return new F();
|
|
};
|
|
}
|
|
|
|
|
|
// user agent sniffing shortcuts
|
|
var ua = {
|
|
toString: function () {
|
|
return navigator.userAgent;
|
|
},
|
|
test: function (s) {
|
|
return this.toString().toLowerCase().indexOf(s.toLowerCase()) > -1;
|
|
}
|
|
};
|
|
ua.version = (ua.toString().toLowerCase().match(/[\s\S]+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1];
|
|
ua.webkit = ua.test('webkit');
|
|
ua.gecko = ua.test('gecko') && !ua.webkit;
|
|
ua.opera = ua.test('opera');
|
|
ua.ie = ua.test('msie') && !ua.opera;
|
|
ua.ie6 = ua.ie && document.compatMode && typeof document.documentElement.style.maxHeight === 'undefined';
|
|
ua.ie7 = ua.ie && document.documentElement && typeof document.documentElement.style.maxHeight !== 'undefined' && typeof XDomainRequest === 'undefined';
|
|
ua.ie8 = ua.ie && typeof XDomainRequest !== 'undefined';
|
|
|
|
|
|
|
|
// initialize when DOM content is loaded
|
|
var domReady = function () {
|
|
var fns = [];
|
|
var init = function () {
|
|
if (!arguments.callee.done) { // run init functions once
|
|
arguments.callee.done = true;
|
|
for (var i = 0; i < fns.length; i++) {
|
|
fns[i]();
|
|
}
|
|
}
|
|
};
|
|
|
|
// listeners for different browsers
|
|
if (document.addEventListener) {
|
|
document.addEventListener('DOMContentLoaded', init, false);
|
|
}
|
|
if (ua.ie) {
|
|
(function () {
|
|
try {
|
|
// throws errors until after ondocumentready
|
|
document.documentElement.doScroll('left');
|
|
}
|
|
catch (e) {
|
|
setTimeout(arguments.callee, 50);
|
|
return;
|
|
}
|
|
// no errors, fire
|
|
init();
|
|
})();
|
|
// trying to always fire before onload
|
|
document.onreadystatechange = function () {
|
|
if (document.readyState === 'complete') {
|
|
document.onreadystatechange = null;
|
|
init();
|
|
}
|
|
};
|
|
}
|
|
if (ua.webkit && document.readyState) {
|
|
(function () {
|
|
if (document.readyState !== 'loading') {
|
|
init();
|
|
}
|
|
else {
|
|
setTimeout(arguments.callee, 10);
|
|
}
|
|
})();
|
|
}
|
|
window.onload = init; // fallback
|
|
|
|
return function (fn) { // add fn to init functions
|
|
if (typeof fn === 'function') {
|
|
fns[fns.length] = fn;
|
|
}
|
|
return fn;
|
|
};
|
|
}();
|
|
|
|
|
|
|
|
// helper library for parsing css to objects
|
|
var cssHelper = function () {
|
|
|
|
var regExp = {
|
|
BLOCKS: /[^\s{;][^{;]*\{(?:[^{}]*\{[^{}]*\}[^{}]*|[^{}]*)*\}/g,
|
|
BLOCKS_INSIDE: /[^\s{][^{]*\{[^{}]*\}/g,
|
|
DECLARATIONS: /[a-zA-Z\-]+[^;]*:[^;]+;/g,
|
|
RELATIVE_URLS: /url\(['"]?([^\/\)'"][^:\)'"]+)['"]?\)/g,
|
|
// strip whitespace and comments, @import is evil
|
|
REDUNDANT_COMPONENTS: /(?:\/\*([^*\\\\]|\*(?!\/))+\*\/|@import[^;]+;)/g,
|
|
REDUNDANT_WHITESPACE: /\s*(,|:|;|\{|\})\s*/g,
|
|
WHITESPACE_IN_PARENTHESES: /\(\s*(\S*)\s*\)/g,
|
|
MORE_WHITESPACE: /\s{2,}/g,
|
|
FINAL_SEMICOLONS: /;\}/g,
|
|
NOT_WHITESPACE: /\S+/g
|
|
};
|
|
|
|
var parsed, parsing = false;
|
|
|
|
var waiting = [];
|
|
var wait = function (fn) {
|
|
if (typeof fn === 'function') {
|
|
waiting[waiting.length] = fn;
|
|
}
|
|
};
|
|
var ready = function () {
|
|
for (var i = 0; i < waiting.length; i++) {
|
|
waiting[i](parsed);
|
|
}
|
|
};
|
|
var events = {};
|
|
var broadcast = function (n, v) {
|
|
if (events[n]) {
|
|
var listeners = events[n].listeners;
|
|
if (listeners) {
|
|
for (var i = 0; i < listeners.length; i++) {
|
|
listeners[i](v);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var requestText = function (url, fnSuccess, fnFailure) {
|
|
if (ua.ie && !window.XMLHttpRequest) {
|
|
window.XMLHttpRequest = function () {
|
|
return new ActiveXObject('Microsoft.XMLHTTP');
|
|
};
|
|
}
|
|
if (!XMLHttpRequest) {
|
|
return '';
|
|
}
|
|
var r = new XMLHttpRequest();
|
|
try {
|
|
r.open('get', url, true);
|
|
r.setRequestHeader('X_REQUESTED_WITH', 'XMLHttpRequest');
|
|
}
|
|
catch (e) {
|
|
fnFailure();
|
|
return;
|
|
}
|
|
var done = false;
|
|
setTimeout(function () {
|
|
done = true;
|
|
}, 5000);
|
|
document.documentElement.style.cursor = 'progress';
|
|
r.onreadystatechange = function () {
|
|
if (r.readyState === 4 && !done) {
|
|
if (!r.status && location.protocol === 'file:' ||
|
|
(r.status >= 200 && r.status < 300) ||
|
|
r.status === 304 ||
|
|
navigator.userAgent.indexOf('Safari') > -1 && typeof r.status === 'undefined') {
|
|
fnSuccess(r.responseText);
|
|
}
|
|
else {
|
|
fnFailure();
|
|
}
|
|
document.documentElement.style.cursor = '';
|
|
r = null; // avoid memory leaks
|
|
}
|
|
};
|
|
r.send('');
|
|
};
|
|
|
|
var sanitize = function (text) {
|
|
text = text.replace(regExp.REDUNDANT_COMPONENTS, '');
|
|
text = text.replace(regExp.REDUNDANT_WHITESPACE, '$1');
|
|
text = text.replace(regExp.WHITESPACE_IN_PARENTHESES, '($1)');
|
|
text = text.replace(regExp.MORE_WHITESPACE, ' ');
|
|
text = text.replace(regExp.FINAL_SEMICOLONS, '}'); // optional final semicolons
|
|
return text;
|
|
};
|
|
|
|
var objects = {
|
|
stylesheet: function (el) {
|
|
var o = {};
|
|
var amqs = [], mqls = [], rs = [], rsw = [];
|
|
var s = el.cssHelperText;
|
|
|
|
// add attribute media queries
|
|
var attr = el.getAttribute('media');
|
|
if (attr) {
|
|
var qts = attr.toLowerCase().split(',')
|
|
}
|
|
else {
|
|
var qts = ['all'] // imply 'all'
|
|
}
|
|
for (var i = 0; i < qts.length; i++) {
|
|
amqs[amqs.length] = objects.mediaQuery(qts[i], o);
|
|
}
|
|
|
|
// add media query lists and rules (top down order)
|
|
var blocks = s.match(regExp.BLOCKS); // @charset is not a block
|
|
if (blocks !== null) {
|
|
for (var i = 0; i < blocks.length; i++) {
|
|
if (blocks[i].substring(0, 7) === '@media ') { // media query (list)
|
|
var mql = objects.mediaQueryList(blocks[i], o);
|
|
rs = rs.concat(mql.getRules());
|
|
mqls[mqls.length] = mql;
|
|
}
|
|
else { // regular rule set, page context (@page) or font description (@font-face)
|
|
rs[rs.length] = rsw[rsw.length] = objects.rule(blocks[i], o, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
o.element = el;
|
|
o.getCssText = function () {
|
|
return s;
|
|
};
|
|
o.getAttrMediaQueries = function () {
|
|
return amqs;
|
|
};
|
|
o.getMediaQueryLists = function () {
|
|
return mqls;
|
|
};
|
|
o.getRules = function () {
|
|
return rs;
|
|
};
|
|
o.getRulesWithoutMQ = function () {
|
|
return rsw;
|
|
};
|
|
return o;
|
|
},
|
|
|
|
mediaQueryList: function (s, stsh) {
|
|
var o = {};
|
|
var idx = s.indexOf('{');
|
|
var lt = s.substring(0, idx);
|
|
s = s.substring(idx + 1, s.length - 1);
|
|
var mqs = [], rs = [];
|
|
|
|
// add media queries
|
|
var qts = lt.toLowerCase().substring(7).split(',');
|
|
for (var i = 0; i < qts.length; i++) { // parse each media query
|
|
mqs[mqs.length] = objects.mediaQuery(qts[i], o);
|
|
}
|
|
|
|
// add rule sets
|
|
var rts = s.match(regExp.BLOCKS_INSIDE);
|
|
if (rts !== null) {
|
|
for (i = 0; i < rts.length; i++) {
|
|
rs[rs.length] = objects.rule(rts[i], stsh, o);
|
|
}
|
|
}
|
|
|
|
o.type = 'mediaQueryList';
|
|
o.getMediaQueries = function () {
|
|
return mqs;
|
|
};
|
|
o.getRules = function () {
|
|
return rs;
|
|
};
|
|
o.getListText = function () {
|
|
return lt;
|
|
};
|
|
o.getCssText = function () {
|
|
return s;
|
|
};
|
|
return o;
|
|
},
|
|
|
|
mediaQuery: function (s, listOrSheet) {
|
|
s = s || '';
|
|
var mql, stsh;
|
|
if (listOrSheet.type === 'mediaQueryList') {
|
|
mql = listOrSheet;
|
|
}
|
|
else {
|
|
stsh = listOrSheet;
|
|
}
|
|
var not = false, type;
|
|
var expr = [];
|
|
var valid = true;
|
|
var tokens = s.match(regExp.NOT_WHITESPACE);
|
|
|
|
|
|
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
if (!type && (token === 'not' || token === 'only')) { // 'not' and 'only' keywords
|
|
// keyword 'only' does nothing, as if it was not present
|
|
if (token === 'not') {
|
|
not = true;
|
|
}
|
|
}
|
|
else if (!type) { // media type
|
|
type = token;
|
|
}
|
|
else if (token.charAt(0) === '(') { // media feature expression
|
|
var pair = token.substring(1, token.length - 1).split(':');
|
|
expr[expr.length] = {
|
|
mediaFeature: pair[0],
|
|
value: pair[1] || null
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
getQueryText: function () {
|
|
return s;
|
|
},
|
|
getAttrStyleSheet: function () {
|
|
return stsh || null;
|
|
},
|
|
getList: function () {
|
|
return mql || null;
|
|
},
|
|
getValid: function () {
|
|
return valid;
|
|
},
|
|
getNot: function () {
|
|
return not;
|
|
},
|
|
getMediaType: function () {
|
|
return type;
|
|
},
|
|
getExpressions: function () {
|
|
return expr;
|
|
}
|
|
};
|
|
},
|
|
|
|
rule: function (s, stsh, mql) {
|
|
var o = {};
|
|
var idx = s.indexOf('{');
|
|
var st = s.substring(0, idx);
|
|
var ss = st.split(',');
|
|
var ds = [];
|
|
var dts = s.substring(idx + 1, s.length - 1).split(';');
|
|
for (var i = 0; i < dts.length; i++) {
|
|
ds[ds.length] = objects.declaration(dts[i], o);
|
|
}
|
|
|
|
o.getStylesheet = function () {
|
|
return stsh || null;
|
|
};
|
|
o.getMediaQueryList = function () {
|
|
return mql || null;
|
|
};
|
|
o.getSelectors = function () {
|
|
return ss;
|
|
};
|
|
o.getSelectorText = function () {
|
|
return st;
|
|
};
|
|
o.getDeclarations = function () {
|
|
return ds;
|
|
};
|
|
o.getPropertyValue = function (n) {
|
|
for (var i = 0; i < ds.length; i++) {
|
|
if (ds[i].getProperty() === n) {
|
|
return ds[i].getValue();
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
return o;
|
|
},
|
|
|
|
declaration: function (s, r) {
|
|
var idx = s.indexOf(':');
|
|
var p = s.substring(0, idx);
|
|
var v = s.substring(idx + 1);
|
|
return {
|
|
getRule: function () {
|
|
return r || null;
|
|
},
|
|
getProperty: function () {
|
|
return p;
|
|
},
|
|
getValue: function () {
|
|
return v;
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
var parseText = function (el) {
|
|
if (typeof el.cssHelperText !== 'string') {
|
|
return;
|
|
}
|
|
var o = {
|
|
stylesheet: null,
|
|
mediaQueryLists: [],
|
|
rules: [],
|
|
selectors: {},
|
|
declarations: [],
|
|
properties: {}
|
|
};
|
|
|
|
// build stylesheet object
|
|
var stsh = o.stylesheet = objects.stylesheet(el);
|
|
|
|
// collect media query lists
|
|
var mqls = o.mediaQueryLists = stsh.getMediaQueryLists();
|
|
|
|
// collect all rules
|
|
var ors = o.rules = stsh.getRules();
|
|
|
|
// collect all selectors
|
|
var oss = o.selectors;
|
|
var collectSelectors = function (r) {
|
|
var ss = r.getSelectors();
|
|
for (var i = 0; i < ss.length; i++) {
|
|
var n = ss[i];
|
|
if (!oss[n]) {
|
|
oss[n] = [];
|
|
}
|
|
oss[n][oss[n].length] = r;
|
|
}
|
|
};
|
|
for (var i = 0; i < ors.length; i++) {
|
|
collectSelectors(ors[i]);
|
|
}
|
|
|
|
// collect all declarations
|
|
var ods = o.declarations;
|
|
for (i = 0; i < ors.length; i++) {
|
|
ods = o.declarations = ods.concat(ors[i].getDeclarations());
|
|
}
|
|
|
|
// collect all properties
|
|
var ops = o.properties;
|
|
for (i = 0; i < ods.length; i++) {
|
|
var n = ods[i].getProperty();
|
|
if (!ops[n]) {
|
|
ops[n] = [];
|
|
}
|
|
ops[n][ops[n].length] = ods[i];
|
|
}
|
|
|
|
el.cssHelperParsed = o;
|
|
parsed[parsed.length] = el;
|
|
return o;
|
|
};
|
|
|
|
var parseEmbedded = function (el, s) {
|
|
return;
|
|
// This function doesn't work because of a bug in IE, where innerHTML gives us parsed css instead of raw literal.
|
|
el.cssHelperText = sanitize(s || el.innerHTML);
|
|
return parseText(el);
|
|
};
|
|
|
|
var parse = function () {
|
|
parsing = true;
|
|
parsed = [];
|
|
var linked = [];
|
|
var finish = function () {
|
|
for (var i = 0; i < linked.length; i++) {
|
|
parseText(linked[i]);
|
|
}
|
|
var styles = document.getElementsByTagName('style');
|
|
for (i = 0; i < styles.length; i++) {
|
|
parseEmbedded(styles[i]);
|
|
}
|
|
parsing = false;
|
|
ready();
|
|
};
|
|
var links = document.getElementsByTagName('link');
|
|
for (var i = 0; i < links.length; i++) {
|
|
var link = links[i];
|
|
if (link.getAttribute('rel').indexOf('style') > -1 && link.href && link.href.length !== 0 && !link.disabled) {
|
|
linked[linked.length] = link;
|
|
}
|
|
}
|
|
if (linked.length > 0) {
|
|
var c = 0;
|
|
var checkForFinish = function () {
|
|
c++;
|
|
if (c === linked.length) { // parse in right order, so after last link is read
|
|
finish();
|
|
}
|
|
};
|
|
var processLink = function (link) {
|
|
var href = link.href;
|
|
requestText(href, function (text) {
|
|
// fix url's
|
|
text = sanitize(text).replace(regExp.RELATIVE_URLS, 'url(' + href.substring(0, href.lastIndexOf('/')) + '/$1)');
|
|
link.cssHelperText = text;
|
|
checkForFinish();
|
|
}, checkForFinish);
|
|
};
|
|
for (i = 0; i < linked.length; i++) {
|
|
processLink(linked[i]);
|
|
}
|
|
}
|
|
else {
|
|
finish();
|
|
}
|
|
};
|
|
|
|
var types = {
|
|
stylesheets: 'array',
|
|
mediaQueryLists: 'array',
|
|
rules: 'array',
|
|
selectors: 'object',
|
|
declarations: 'array',
|
|
properties: 'object'
|
|
};
|
|
|
|
var collections = {
|
|
stylesheets: null,
|
|
mediaQueryLists: null,
|
|
rules: null,
|
|
selectors: null,
|
|
declarations: null,
|
|
properties: null
|
|
};
|
|
|
|
var addToCollection = function (name, v) {
|
|
if (collections[name] !== null) {
|
|
if (types[name] === 'array') {
|
|
return (collections[name] = collections[name].concat(v));
|
|
}
|
|
else {
|
|
var c = collections[name];
|
|
for (var n in v) {
|
|
if (v.hasOwnProperty(n)) {
|
|
if (!c[n]) {
|
|
c[n] = v[n];
|
|
}
|
|
else {
|
|
c[n] = c[n].concat(v[n]);
|
|
}
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
}
|
|
};
|
|
|
|
var collect = function (name) {
|
|
collections[name] = (types[name] === 'array') ? [] : {};
|
|
for (var i = 0; i < parsed.length; i++) {
|
|
var pname = name === 'stylesheets' ? 'stylesheet' : name; // the exception
|
|
addToCollection(name, parsed[i].cssHelperParsed[pname]);
|
|
}
|
|
return collections[name];
|
|
};
|
|
|
|
// viewport size
|
|
var getViewportSize = function (d) {
|
|
if (typeof window.innerWidth != 'undefined') {
|
|
return window['inner' + d];
|
|
}
|
|
else if (typeof document.documentElement !== 'undefined'
|
|
&& typeof document.documentElement.clientWidth !== 'undefined'
|
|
&& document.documentElement.clientWidth != 0) {
|
|
return document.documentElement['client' + d];
|
|
}
|
|
};
|
|
|
|
// public static functions
|
|
return {
|
|
addStyle: function (s, mediaTypes, process) {
|
|
var el = document.createElement('style');
|
|
el.setAttribute('type', 'text/css');
|
|
if (mediaTypes && mediaTypes.length > 0) {
|
|
el.setAttribute('media', mediaTypes.join(','));
|
|
}
|
|
document.getElementsByTagName('head')[0].appendChild(el);
|
|
if (el.styleSheet) { // IE
|
|
el.styleSheet.cssText = s;
|
|
}
|
|
else {
|
|
el.appendChild(document.createTextNode(s));
|
|
}
|
|
el.addedWithCssHelper = true;
|
|
if (typeof process === 'undefined' || process === true) {
|
|
cssHelper.parsed(function (parsed) {
|
|
var o = parseEmbedded(el, s);
|
|
for (var n in o) {
|
|
if (o.hasOwnProperty(n)) {
|
|
addToCollection(n, o[n]);
|
|
}
|
|
}
|
|
broadcast('newStyleParsed', el);
|
|
});
|
|
}
|
|
else {
|
|
el.parsingDisallowed = true;
|
|
}
|
|
return el;
|
|
},
|
|
|
|
removeStyle: function (el) {
|
|
return el.parentNode.removeChild(el);
|
|
},
|
|
|
|
parsed: function (fn) {
|
|
if (parsing) {
|
|
wait(fn);
|
|
}
|
|
else {
|
|
if (typeof parsed !== 'undefined') {
|
|
if (typeof fn === 'function') {
|
|
fn(parsed);
|
|
}
|
|
}
|
|
else {
|
|
wait(fn);
|
|
parse();
|
|
}
|
|
}
|
|
},
|
|
|
|
stylesheets: function (fn) {
|
|
cssHelper.parsed(function (parsed) {
|
|
fn(collections.stylesheets || collect('stylesheets'));
|
|
});
|
|
},
|
|
|
|
mediaQueryLists: function (fn) {
|
|
cssHelper.parsed(function (parsed) {
|
|
fn(collections.mediaQueryLists || collect('mediaQueryLists'));
|
|
});
|
|
},
|
|
|
|
rules: function (fn) {
|
|
cssHelper.parsed(function (parsed) {
|
|
fn(collections.rules || collect('rules'));
|
|
});
|
|
},
|
|
|
|
selectors: function (fn) {
|
|
cssHelper.parsed(function (parsed) {
|
|
fn(collections.selectors || collect('selectors'));
|
|
});
|
|
},
|
|
|
|
declarations: function (fn) {
|
|
cssHelper.parsed(function (parsed) {
|
|
fn(collections.declarations || collect('declarations'));
|
|
});
|
|
},
|
|
|
|
properties: function (fn) {
|
|
cssHelper.parsed(function (parsed) {
|
|
fn(collections.properties || collect('properties'));
|
|
});
|
|
},
|
|
|
|
broadcast: broadcast,
|
|
|
|
addListener: function (n, fn) { // in case n is 'styleadd': added function is called everytime style is added and parsed
|
|
if (typeof fn === 'function') {
|
|
if (!events[n]) {
|
|
events[n] = {
|
|
listeners: []
|
|
};
|
|
}
|
|
events[n].listeners[events[n].listeners.length] = fn;
|
|
}
|
|
},
|
|
|
|
removeListener: function (n, fn) {
|
|
if (typeof fn === 'function' && events[n]) {
|
|
var ls = events[n].listeners;
|
|
for (var i = 0; i < ls.length; i++) {
|
|
if (ls[i] === fn) {
|
|
ls.splice(i, 1);
|
|
i -= 1;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
getViewportWidth: function () {
|
|
return getViewportSize('Width');
|
|
},
|
|
|
|
getViewportHeight: function () {
|
|
return getViewportSize('Height');
|
|
}
|
|
};
|
|
}();
|
|
|
|
|
|
|
|
// function to test and apply parsed media queries against browser capabilities
|
|
domReady(function enableCssMediaQueries() {
|
|
var meter;
|
|
|
|
var regExp = {
|
|
LENGTH_UNIT: /[0-9]+(em|ex|px|in|cm|mm|pt|pc)$/,
|
|
RESOLUTION_UNIT: /[0-9]+(dpi|dpcm)$/,
|
|
ASPECT_RATIO: /^[0-9]+\/[0-9]+$/,
|
|
ABSOLUTE_VALUE: /^[0-9]*(\.[0-9]+)*$/
|
|
};
|
|
|
|
var styles = [];
|
|
|
|
var nativeSupport = function () {
|
|
// check support for media queries
|
|
var id = 'css3-mediaqueries-test';
|
|
var el = document.createElement('div');
|
|
el.id = id;
|
|
var style = cssHelper.addStyle('@media all and (width) { #' + id +
|
|
' { width: 1px !important; } }', [], false); // false means don't parse this temp style
|
|
document.body.appendChild(el);
|
|
var ret = el.offsetWidth === 1;
|
|
style.parentNode.removeChild(style);
|
|
el.parentNode.removeChild(el);
|
|
nativeSupport = function () {
|
|
return ret;
|
|
};
|
|
return ret;
|
|
};
|
|
|
|
var createMeter = function () { // create measuring element
|
|
meter = document.createElement('div');
|
|
meter.style.cssText = 'position:absolute;top:-9999em;left:-9999em;' +
|
|
'margin:0;border:none;padding:0;width:1em;font-size:1em;'; // cssText is needed for IE, works for the others
|
|
document.body.appendChild(meter);
|
|
// meter must have browser default font size of 16px
|
|
if (meter.offsetWidth !== 16) {
|
|
meter.style.fontSize = 16 / meter.offsetWidth + 'em';
|
|
}
|
|
meter.style.width = '';
|
|
};
|
|
|
|
var measure = function (value) {
|
|
meter.style.width = value;
|
|
var amount = meter.offsetWidth;
|
|
meter.style.width = '';
|
|
return amount;
|
|
};
|
|
|
|
var testMediaFeature = function (feature, value) {
|
|
// non-testable features: monochrome|min-monochrome|max-monochrome|scan|grid
|
|
var l = feature.length;
|
|
var min = (feature.substring(0, 4) === 'min-');
|
|
var max = (!min && feature.substring(0, 4) === 'max-');
|
|
|
|
if (value !== null) { // determine value type and parse to usable amount
|
|
var valueType;
|
|
var amount;
|
|
if (regExp.LENGTH_UNIT.exec(value)) {
|
|
valueType = 'length';
|
|
amount = measure(value);
|
|
}
|
|
else if (regExp.RESOLUTION_UNIT.exec(value)) {
|
|
valueType = 'resolution';
|
|
amount = parseInt(value, 10);
|
|
var unit = value.substring((amount + '').length);
|
|
}
|
|
else if (regExp.ASPECT_RATIO.exec(value)) {
|
|
valueType = 'aspect-ratio';
|
|
amount = value.split('/');
|
|
}
|
|
else if (regExp.ABSOLUTE_VALUE) {
|
|
valueType = 'absolute';
|
|
amount = value;
|
|
}
|
|
else {
|
|
valueType = 'unknown';
|
|
}
|
|
}
|
|
|
|
var width, height;
|
|
if ('device-width' === feature.substring(l - 12, l)) { // screen width
|
|
width = screen.width;
|
|
if (value !== null) {
|
|
if (valueType === 'length') {
|
|
return ((min && width >= amount) || (max && width < amount) || (!min && !max && width === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test width without value
|
|
return width > 0;
|
|
}
|
|
}
|
|
else if ('device-height' === feature.substring(l - 13, l)) { // screen height
|
|
height = screen.height;
|
|
if (value !== null) {
|
|
if (valueType === 'length') {
|
|
return ((min && height >= amount) || (max && height < amount) || (!min && !max && height === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test height without value
|
|
return height > 0;
|
|
}
|
|
}
|
|
else if ('width' === feature.substring(l - 5, l)) { // viewport width
|
|
width = document.documentElement.clientWidth || document.body.clientWidth; // the latter for IE quirks mode
|
|
if (value !== null) {
|
|
if (valueType === 'length') {
|
|
return ((min && width >= amount) || (max && width < amount) || (!min && !max && width === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test width without value
|
|
return width > 0;
|
|
}
|
|
}
|
|
else if ('height' === feature.substring(l - 6, l)) { // viewport height
|
|
height = document.documentElement.clientHeight || document.body.clientHeight; // the latter for IE quirks mode
|
|
if (value !== null) {
|
|
if (valueType === 'length') {
|
|
return ((min && height >= amount) || (max && height < amount) || (!min && !max && height === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test height without value
|
|
return height > 0;
|
|
}
|
|
}
|
|
else if ('device-aspect-ratio' === feature.substring(l - 19, l)) { // screen aspect ratio
|
|
return valueType === 'aspect-ratio' && screen.width * amount[1] === screen.height * amount[0];
|
|
}
|
|
else if ('color-index' === feature.substring(l - 11, l)) { // number of colors
|
|
var colors = Math.pow(2, screen.colorDepth);
|
|
if (value !== null) {
|
|
if (valueType === 'absolute') {
|
|
return ((min && colors >= amount) || (max && colors < amount) || (!min && !max && colors === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test height without value
|
|
return colors > 0;
|
|
}
|
|
}
|
|
else if ('color' === feature.substring(l - 5, l)) { // bits per color component
|
|
var color = screen.colorDepth;
|
|
if (value !== null) {
|
|
if (valueType === 'absolute') {
|
|
return ((min && color >= amount) || (max && color < amount) || (!min && !max && color === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test height without value
|
|
return color > 0;
|
|
}
|
|
}
|
|
else if ('resolution' === feature.substring(l - 10, l)) {
|
|
var res;
|
|
if (unit === 'dpcm') {
|
|
res = measure('1cm');
|
|
}
|
|
else {
|
|
res = measure('1in');
|
|
}
|
|
if (value !== null) {
|
|
if (valueType === 'resolution') {
|
|
return ((min && res >= amount) || (max && res < amount) || (!min && !max && res === amount));
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else { // test height without value
|
|
return res > 0;
|
|
}
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
var testMediaQuery = function (mq) {
|
|
var test = mq.getValid();
|
|
var expressions = mq.getExpressions();
|
|
var l = expressions.length;
|
|
if (l > 0) {
|
|
for (var i = 0; i < l && test; i++) {
|
|
test = testMediaFeature(expressions[i].mediaFeature, expressions[i].value);
|
|
}
|
|
var not = mq.getNot();
|
|
return (test && !not || not && !test);
|
|
}
|
|
return test;
|
|
};
|
|
|
|
var testMediaQueryList = function (mql, ts) {
|
|
// ts is null or an array with any media type but 'all'.
|
|
var mqs = mql.getMediaQueries();
|
|
var t = {};
|
|
for (var i = 0; i < mqs.length; i++) {
|
|
var type = mqs[i].getMediaType();
|
|
if (mqs[i].getExpressions().length === 0) {
|
|
continue;
|
|
// TODO: Browser check! Assuming old browsers do apply the bare media types, even in a list with media queries.
|
|
}
|
|
var typeAllowed = true;
|
|
if (type !== 'all' && ts && ts.length > 0) {
|
|
typeAllowed = false;
|
|
for (var j = 0; j < ts.length; j++) {
|
|
if (ts[j] === type) {
|
|
typeAllowed = true;
|
|
}
|
|
}
|
|
}
|
|
if (typeAllowed && testMediaQuery(mqs[i])) {
|
|
t[type] = true;
|
|
}
|
|
}
|
|
var s = [], c = 0;
|
|
for (var n in t) {
|
|
if (t.hasOwnProperty(n)) {
|
|
if (c > 0) {
|
|
s[c++] = ',';
|
|
}
|
|
s[c++] = n;
|
|
}
|
|
}
|
|
if (s.length > 0) {
|
|
styles[styles.length] = cssHelper.addStyle('@media ' + s.join('') + '{' + mql.getCssText() + '}', ts, false);
|
|
}
|
|
};
|
|
|
|
var testMediaQueryLists = function (mqls, ts) {
|
|
for (var i = 0; i < mqls.length; i++) {
|
|
testMediaQueryList(mqls[i], ts);
|
|
}
|
|
};
|
|
|
|
var testStylesheet = function (stsh) {
|
|
var amqs = stsh.getAttrMediaQueries();
|
|
var allPassed = false;
|
|
var t = {};
|
|
for (var i = 0; i < amqs.length; i++) {
|
|
if (testMediaQuery(amqs[i])) {
|
|
t[amqs[i].getMediaType()] = amqs[i].getExpressions().length > 0;
|
|
}
|
|
}
|
|
var ts = [], tswe = [];
|
|
for (var n in t) {
|
|
if (t.hasOwnProperty(n)) {
|
|
ts[ts.length] = n;
|
|
if (t[n]) {
|
|
tswe[tswe.length] = n
|
|
}
|
|
if (n === 'all') {
|
|
allPassed = true;
|
|
}
|
|
}
|
|
}
|
|
if (tswe.length > 0) { // types with query expressions that passed the test
|
|
styles[styles.length] = cssHelper.addStyle(stsh.getCssText(), tswe, false);
|
|
}
|
|
var mqls = stsh.getMediaQueryLists();
|
|
if (allPassed) {
|
|
// If 'all' in media attribute passed the test, then test all @media types in linked CSS and create style with those types.
|
|
testMediaQueryLists(mqls);
|
|
}
|
|
else {
|
|
// Or else, test only media attribute types that passed the test and also 'all'.
|
|
// For positive '@media all', create style with attribute types that passed their test.
|
|
testMediaQueryLists(mqls, ts);
|
|
}
|
|
};
|
|
|
|
var testStylesheets = function (stshs) {
|
|
for (var i = 0; i < stshs.length; i++) {
|
|
testStylesheet(stshs[i]);
|
|
}
|
|
if (ua.ie) {
|
|
// force repaint in IE
|
|
document.documentElement.style.display = 'block';
|
|
setTimeout(function () {
|
|
document.documentElement.style.display = '';
|
|
}, 0);
|
|
// delay broadcast somewhat for IE
|
|
setTimeout(function () {
|
|
cssHelper.broadcast('cssMediaQueriesTested');
|
|
}, 100);
|
|
}
|
|
else {
|
|
cssHelper.broadcast('cssMediaQueriesTested');
|
|
}
|
|
};
|
|
|
|
var test = function () {
|
|
for (var i = 0; i < styles.length; i++) {
|
|
cssHelper.removeStyle(styles[i]);
|
|
}
|
|
styles = [];
|
|
cssHelper.stylesheets(testStylesheets);
|
|
};
|
|
|
|
var scrollbarWidth = 0;
|
|
var checkForResize = function () {
|
|
var cvpw = cssHelper.getViewportWidth();
|
|
var cvph = cssHelper.getViewportHeight();
|
|
|
|
// determine scrollbar width in IE, see resizeHandler
|
|
if (ua.ie) {
|
|
var el = document.createElement('div');
|
|
el.style.position = 'absolute';
|
|
el.style.top = '-9999em';
|
|
el.style.overflow = 'scroll';
|
|
document.body.appendChild(el);
|
|
scrollbarWidth = el.offsetWidth - el.clientWidth;
|
|
document.body.removeChild(el);
|
|
}
|
|
|
|
var timer;
|
|
var resizeHandler = function () {
|
|
var vpw = cssHelper.getViewportWidth();
|
|
var vph = cssHelper.getViewportHeight();
|
|
// check whether vp size has really changed, because IE also triggers resize event when body size changes
|
|
// 20px allowance to accomodate short appearance of scrollbars in IE in some cases
|
|
if (Math.abs(vpw - cvpw) > scrollbarWidth || Math.abs(vph - cvph) > scrollbarWidth) {
|
|
cvpw = vpw;
|
|
cvph = vph;
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function () {
|
|
if (!nativeSupport()) {
|
|
test();
|
|
}
|
|
else {
|
|
cssHelper.broadcast('cssMediaQueriesTested');
|
|
}
|
|
}, 500);
|
|
}
|
|
};
|
|
|
|
window.onresize = function () {
|
|
var x = window.onresize || function () {}; // save original
|
|
return function () {
|
|
x();
|
|
resizeHandler();
|
|
};
|
|
}();
|
|
};
|
|
|
|
// prevent jumping of layout by hiding everything before painting <body>
|
|
var docEl = document.documentElement;
|
|
docEl.style.marginLeft = '-32767px';
|
|
|
|
// make sure it comes back after a while
|
|
setTimeout(function () {
|
|
docEl.style.marginLeft = '';
|
|
}, 5000);
|
|
|
|
return function () {
|
|
if (!nativeSupport()) { // if browser doesn't support media queries
|
|
cssHelper.addListener('newStyleParsed', function (el) {
|
|
testStylesheet(el.cssHelperParsed.stylesheet);
|
|
});
|
|
// return visibility after media queries are tested
|
|
cssHelper.addListener('cssMediaQueriesTested', function () {
|
|
// force repaint in IE by changing width
|
|
if (ua.ie) {
|
|
docEl.style.width = '1px';
|
|
}
|
|
setTimeout(function () {
|
|
docEl.style.width = ''; // undo width
|
|
docEl.style.marginLeft = ''; // undo hide
|
|
}, 0);
|
|
// remove this listener to prevent following execution
|
|
cssHelper.removeListener('cssMediaQueriesTested', arguments.callee);
|
|
});
|
|
createMeter();
|
|
test();
|
|
}
|
|
else {
|
|
docEl.style.marginLeft = ''; // undo visibility hidden
|
|
}
|
|
checkForResize();
|
|
};
|
|
}());
|
|
|
|
|
|
// bonus: hotfix for IE6 SP1 (bug KB823727)
|
|
try {
|
|
document.execCommand('BackgroundImageCache', false, true);
|
|
} catch (e) {}
|