157 lines
5.0 KiB
JavaScript
157 lines
5.0 KiB
JavaScript
|
/**
|
||
|
* Polyfill the behavior of window.matchMedia.
|
||
|
*
|
||
|
* @see http://dev.w3.org/csswg/cssom-view/#widl-Window-matchMedia-MediaQueryList-DOMString-query
|
||
|
*
|
||
|
* Test whether a CSS media type or media query applies. Register listeners
|
||
|
* to MediaQueryList objects.
|
||
|
*
|
||
|
* Adapted from https://github.com/paulirish/matchMedia.js with the addition
|
||
|
* of addListener and removeListener. The polyfill referenced above uses
|
||
|
* polling to trigger registered listeners on matchMedia tests.
|
||
|
* This polyfill triggers tests on window resize and orientationchange.
|
||
|
*/
|
||
|
|
||
|
window.matchMedia = window.matchMedia || (function (doc, window) {
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
var docElem = doc.documentElement;
|
||
|
var refNode = docElem.firstElementChild || docElem.firstChild;
|
||
|
// fakeBody required for <FF4 when executed in <head>.
|
||
|
var fakeBody = doc.createElement("body");
|
||
|
var div = doc.createElement("div");
|
||
|
|
||
|
div.id = "mq-test-1";
|
||
|
div.style.cssText = "position:absolute;top:-100em";
|
||
|
fakeBody.style.background = "none";
|
||
|
fakeBody.appendChild(div);
|
||
|
|
||
|
/**
|
||
|
* A replacement for the native MediaQueryList object.
|
||
|
*
|
||
|
* @param {String} q
|
||
|
* A media query e.g. "screen" or "screen and (min-width: 28em)".
|
||
|
*/
|
||
|
function MediaQueryList (q) {
|
||
|
this.media = q;
|
||
|
this.matches = false;
|
||
|
this.check.call(this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Polyfill the addListener and removeListener methods.
|
||
|
*/
|
||
|
MediaQueryList.prototype = {
|
||
|
listeners: [],
|
||
|
|
||
|
/**
|
||
|
* Perform the media query application check.
|
||
|
*/
|
||
|
check: function () {
|
||
|
var isApplied;
|
||
|
div.innerHTML = "­<style media=\"" + this.media + "\"> #mq-test-1 {width: 42px;}</style>";
|
||
|
docElem.insertBefore(fakeBody, refNode);
|
||
|
isApplied = div.offsetWidth === 42;
|
||
|
docElem.removeChild(fakeBody);
|
||
|
this.matches = isApplied;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Polyfill the addListener method of the MediaQueryList object.
|
||
|
*
|
||
|
* @param {Function} callback
|
||
|
* The callback to be invoked when the media query is applicable.
|
||
|
*
|
||
|
* @return {Object MediaQueryList}
|
||
|
* A MediaQueryList object that indicates whether the registered media
|
||
|
* query applies. The matches property is true when the media query
|
||
|
* applies and false when not. The original media query is referenced in
|
||
|
* the media property.
|
||
|
*/
|
||
|
addListener: function (callback) {
|
||
|
var handler = (function (mql, debounced) {
|
||
|
return function () {
|
||
|
mql.check();
|
||
|
debounced.call(mql, mql);
|
||
|
};
|
||
|
}(this, debounce(callback, 250)));
|
||
|
this.listeners.push({
|
||
|
'callback': callback,
|
||
|
'handler': handler
|
||
|
});
|
||
|
|
||
|
// Associate the handler to the resize and orientationchange events.
|
||
|
if ('addEventListener' in window) {
|
||
|
window.addEventListener('resize', handler);
|
||
|
window.addEventListener('orientationchange', handler);
|
||
|
}
|
||
|
else if ('attachEvent' in window) {
|
||
|
window.attachEvent('onresize', handler);
|
||
|
window.attachEvent('onorientationchange', handler);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Polyfill the removeListener method of the MediaQueryList object.
|
||
|
*
|
||
|
* @param {Function} callback
|
||
|
* The callback to be removed from the set of listeners.
|
||
|
*/
|
||
|
removeListener: function (callback) {
|
||
|
for (var i = 0, listeners = this.listeners; i < listeners.length; i++) {
|
||
|
if (listeners[i].callback === callback) {
|
||
|
// Disassociate the handler to the resize and orientationchange events.
|
||
|
if ('removeEventListener' in window) {
|
||
|
window.removeEventListener('resize', listeners[i].handler);
|
||
|
window.removeEventListener('orientationchange', listeners[i].handler);
|
||
|
}
|
||
|
else if ('detachEvent' in window) {
|
||
|
window.detachEvent('onresize', listeners[i].handler);
|
||
|
window.detachEvent('onorientationchange', listeners[i].handler);
|
||
|
}
|
||
|
listeners.splice(i, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Limits the invocations of a function in a given time frame.
|
||
|
*
|
||
|
* @param {Function} callback
|
||
|
* The function to be invoked.
|
||
|
*
|
||
|
* @param {Number} wait
|
||
|
* The time period within which the callback function should only be
|
||
|
* invoked once. For example if the wait period is 250ms, then the callback
|
||
|
* will only be called at most 4 times per second.
|
||
|
*/
|
||
|
function debounce (callback, wait) {
|
||
|
var timeout, result;
|
||
|
return function () {
|
||
|
var context = this;
|
||
|
var args = arguments;
|
||
|
var later = function () {
|
||
|
timeout = null;
|
||
|
result = callback.apply(context, args);
|
||
|
};
|
||
|
window.clearTimeout(timeout);
|
||
|
timeout = window.setTimeout(later, wait);
|
||
|
return result;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a MediaQueryList.
|
||
|
*
|
||
|
* @param {String} q
|
||
|
* A media query e.g. "screen" or "screen and (min-width: 28em)". The media
|
||
|
* query is checked for applicability before the object is returned.
|
||
|
*/
|
||
|
return function (q) {
|
||
|
// Build a new MediaQueryList object with the result of the check.
|
||
|
return new MediaQueryList(q);
|
||
|
};
|
||
|
}(document, window));
|