Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Created March 30, 2012 03:19
Show Gist options
  • Save jonathantneal/2246170 to your computer and use it in GitHub Desktop.
Save jonathantneal/2246170 to your computer and use it in GitHub Desktop.
Match Media
(function (window, screen, documentElement) {
/* ======================================================================
Setup
====================================================================== */
var hasEventListener = ('addEventListener' in window);
var hasOrientation = ('orientation' in window);
var hasGetComputedStyle = ('getComputedStyle' in window);
var regexpTrim = /^\s+|\s+$/g;
var regexpValue = /(\d+)(\w+)/;
var gap = ' ';
var landscape = 'landscape';
var portrait = 'portrait';
var fontSize = 16;
function documentElementHasClass(className) {
return (gap + documentElement.className + gap).indexOf(gap + className + gap) > -1;
}
function documentElementAddClass(className) {
if (!documentElementHasClass(className)) {
documentElement.className = (documentElement.className + gap + className).replace(regexpTrim, '');
}
}
function documentElementCutClass(className) {
if (documentElementHasClass(className)) {
documentElement.className = (gap + documentElement.className + gap).replace(gap + className + gap, gap).replace(regexpTrim, '');
}
}
function documentElementFontSize() {
return (hasGetComputedStyle ? getComputedStyle(documentElement, null) : documentElement.currentStyle).fontSize.toLowerCase().replace(regexpValue, parseDocumentElementFontSize) + 0;
}
function windowAddEvent(type, callback) {
window[hasEventListener ? 'addEventListener' : 'attachEvent']((hasEventListener ? '' : 'on') + type, callback);
}
function toCamelCase(string) {
return string.toLowerCase().replace(/-[a-z]/g, function (match) {
return match.substr(1).toUpperCase();
});
}
function parseDocumentElementFontSize(m, m1, m2) {
switch (m2) {
case 'em':
return m1 * 16;
case 'in':
return m1 * 96;
case 'pt':
return m1 * 96 / 72;
case '%':
return m1 / 100 * 16;
}
return m1;
}
function parseCSSValue(m, m1, m2) {
switch (m2) {
case 'em':
return m1 * matchMedia.current.fontSize;
case 'in':
return m1 * 96;
case 'pt':
return m1 * 96 / 72;
case '%':
return m1 / 100 * matchMedia.current.width;
}
return m1;
}
/* ======================================================================
MediaQueryList
====================================================================== */
function MediaQueryList(mediaQuery) {
var instance = this;
instance.__events__ = {};
instance.__classNames__ = {};
instance.validator = getMediaQueryValidator(mediaQuery);
instance.matches = instance.validator(matchMedia.current);
instance.query = mediaQuery;
matchMedia.instance.push(instance);
return instance;
}
MediaQueryList.prototype.addListener = function (fn) {
this.__events__[fn] = fn;
if (this.matches) {
fn.call(this, matchMedia.current);
}
};
MediaQueryList.prototype.removeListener = function (fn) {
delete this.__events__[fn];
};
MediaQueryList.prototype.addClass = function (className) {
this.__classNames__[className] = true;
if (this.matches) {
documentElementAddClass(className);
} else {
documentElementCutClass(className);
}
};
MediaQueryList.prototype.removeClass = function (className) {
delete this.__classNames__[className];
};
/* ======================================================================
matchMedia
====================================================================== */
function matchMedia(mediaQuery) {
return new MediaQueryList(mediaQuery);
}
matchMedia.current = {};
matchMedia.instance = [];
matchMedia.update = function () {
var
width = window.innerWidth || documentElement.clientWidth,
height = window.innerHeight || documentElement.clientHeight,
orientation = width > height ? landscape : portrait,
deviceWidth = screen.width,
deviceHeight = screen.height,
deviceOrientation = hasOrientation ? window.orientation % 180 ? landscape : portrait : deviceWidth > deviceHeight ? landscape : portrait;
matchMedia.current = {
all: true,
screen: true,
print: false,
width: width,
height: height,
aspectRatio: width / height,
orientation: orientation,
fontSize: documentElementFontSize(),
deviceWidth: deviceWidth,
deviceHeight: deviceHeight,
deviceAspectRatio: deviceWidth / deviceHeight,
deviceOrientation: deviceOrientation
};
var instanceIndex, callbackIndex, classNameIndex;
for (instanceIndex in matchMedia.instance) {
instance = matchMedia.instance[instanceIndex];
if (instance.validator(matchMedia.current) != instance.matches) {
instance.matches = !instance.matches;
for (callbackIndex in instance.__events__) {
instance.__events__[callbackIndex].call(instance, matchMedia.current);
}
for (className in instance.__classNames__) {
if (instance.matches) {
documentElementAddClass(className);
} else {
documentElementCutClass(className);
}
}
}
}
}
function getMediaQueryValidator(mediaQuery) {
var
callback = [],
entries = mediaQuery.toLowerCase().split(/\s*,\s*/),
entriesIndex, entry, entryIndex, mq;
for (entriesIndex in entries) {
callback[entriesIndex] = [];
entry = entries[entriesIndex].replace(/\(|\)/g, '').split(/\s+and\s+/);
for (entryIndex in entry) {
callback[entriesIndex].push(
entry[entryIndex]
.replace(/min-(.*?):/g, '$1>=')
.replace(/max-(.*?):/g, '$1<=')
.replace(/:/g, '==')
.replace(/^([\W\w]+?)\s*([<>=]+)\s*([\W\w]+?)$|[\W\w]+/, function (m, m1, m2, m3) {
return m1
? 'q.' + toCamelCase(m1) + m2 + m3.replace(regexpValue, parseCSSValue)
: '!!q.' + toCamelCase(m)
})
);
}
callback[entriesIndex] = '(' + callback[entriesIndex].join('&&') + ')';
}
return Function('q', 'return' + callback.join('||'));
}
matchMedia.update();
windowAddEvent('resize', matchMedia.update);
window.matchMedia = matchMedia;
})(window, screen, document.documentElement);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment