| /** |
| * Copyright Marc J. Schmidt. See the LICENSE file at the top-level |
| * directory of this distribution and at |
| * https://github.com/marcj/css-element-queries/blob/master/LICENSE. |
| */ |
| ; |
| (function() { |
| /** |
| * |
| * @type {Function} |
| * @constructor |
| */ |
| var ElementQueries = this.ElementQueries = function() { |
| |
| this.withTracking = false; |
| var elements = []; |
| |
| /** |
| * |
| * @param element |
| * @returns {Number} |
| */ |
| function getEmSize(element) { |
| if (!element) { |
| element = document.documentElement; |
| } |
| var fontSize = getComputedStyle(element, 'fontSize'); |
| return parseFloat(fontSize) || 16; |
| } |
| |
| /** |
| * |
| * @copyright https://github.com/Mr0grog/element-query/blob/master/LICENSE |
| * |
| * @param {HTMLElement} element |
| * @param {*} value |
| * @returns {*} |
| */ |
| function convertToPx(element, value) { |
| var units = value.replace(/[0-9]*/, ''); |
| value = parseFloat(value); |
| switch (units) { |
| case "px": |
| return value; |
| case "em": |
| return value * getEmSize(element); |
| case "rem": |
| return value * getEmSize(); |
| // Viewport units! |
| // According to http://quirksmode.org/mobile/tableViewport.html |
| // documentElement.clientWidth/Height gets us the most reliable info |
| case "vw": |
| return value * document.documentElement.clientWidth / 100; |
| case "vh": |
| return value * document.documentElement.clientHeight / 100; |
| case "vmin": |
| case "vmax": |
| var vw = document.documentElement.clientWidth / 100; |
| var vh = document.documentElement.clientHeight / 100; |
| var chooser = Math[units === "vmin" ? "min" : "max"]; |
| return value * chooser(vw, vh); |
| default: |
| return value; |
| // for now, not supporting physical units (since they are just a set number of px) |
| // or ex/ch (getting accurate measurements is hard) |
| } |
| } |
| |
| /** |
| * |
| * @param {HTMLElement} element |
| * @constructor |
| */ |
| function SetupInformation(element) { |
| this.element = element; |
| this.options = {}; |
| var key, option, width = 0, height = 0, value, actualValue, attrValues, attrValue, attrName; |
| |
| /** |
| * @param {Object} option {mode: 'min|max', property: 'width|height', value: '123px'} |
| */ |
| this.addOption = function(option) { |
| var idx = [option.mode, option.property, option.value].join(','); |
| this.options[idx] = option; |
| }; |
| |
| var attributes = ['min-width', 'min-height', 'max-width', 'max-height']; |
| |
| /** |
| * Extracts the computed width/height and sets to min/max- attribute. |
| */ |
| this.call = function() { |
| // extract current dimensions |
| width = this.element.offsetWidth; |
| height = this.element.offsetHeight; |
| |
| attrValues = {}; |
| |
| for (key in this.options) { |
| if (!this.options.hasOwnProperty(key)){ |
| continue; |
| } |
| option = this.options[key]; |
| |
| value = convertToPx(this.element, option.value); |
| |
| actualValue = option.property == 'width' ? width : height; |
| attrName = option.mode + '-' + option.property; |
| attrValue = ''; |
| |
| if (option.mode == 'min' && actualValue >= value) { |
| attrValue += option.value; |
| } |
| |
| if (option.mode == 'max' && actualValue <= value) { |
| attrValue += option.value; |
| } |
| |
| if (!attrValues[attrName]) attrValues[attrName] = ''; |
| if (attrValue && -1 === (' '+attrValues[attrName]+' ').indexOf(' ' + attrValue + ' ')) { |
| attrValues[attrName] += ' ' + attrValue; |
| } |
| } |
| |
| for (var k in attributes) { |
| if (attrValues[attributes[k]]) { |
| this.element.setAttribute(attributes[k], attrValues[attributes[k]].substr(1)); |
| } else { |
| this.element.removeAttribute(attributes[k]); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * @param {HTMLElement} element |
| * @param {Object} options |
| */ |
| function setupElement(element, options) { |
| if (element.elementQueriesSetupInformation) { |
| element.elementQueriesSetupInformation.addOption(options); |
| } else { |
| element.elementQueriesSetupInformation = new SetupInformation(element); |
| element.elementQueriesSetupInformation.addOption(options); |
| element.elementQueriesSensor = new ResizeSensor(element, function() { |
| element.elementQueriesSetupInformation.call(); |
| }); |
| } |
| element.elementQueriesSetupInformation.call(); |
| |
| if (this.withTracking) { |
| elements.push(element); |
| } |
| } |
| |
| /** |
| * @param {String} selector |
| * @param {String} mode min|max |
| * @param {String} property width|height |
| * @param {String} value |
| */ |
| function queueQuery(selector, mode, property, value) { |
| var query; |
| if (document.querySelectorAll) query = document.querySelectorAll.bind(document); |
| if (!query && 'undefined' !== typeof $$) query = $$; |
| if (!query && 'undefined' !== typeof jQuery) query = jQuery; |
| |
| if (!query) { |
| throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.'; |
| } |
| |
| var elements = query(selector); |
| for (var i = 0, j = elements.length; i < j; i++) { |
| setupElement(elements[i], { |
| mode: mode, |
| property: property, |
| value: value |
| }); |
| } |
| } |
| |
| var regex = /,?([^,\n]*)\[[\s\t]*(min|max)-(width|height)[\s\t]*[~$\^]?=[\s\t]*"([^"]*)"[\s\t]*]([^\n\s\{]*)/mgi; |
| |
| /** |
| * @param {String} css |
| */ |
| function extractQuery(css) { |
| var match; |
| css = css.replace(/'/g, '"'); |
| while (null !== (match = regex.exec(css))) { |
| if (5 < match.length) { |
| queueQuery(match[1] || match[5], match[2], match[3], match[4]); |
| } |
| } |
| } |
| |
| /** |
| * @param {CssRule[]|String} rules |
| */ |
| function readRules(rules) { |
| var selector = ''; |
| if (!rules) { |
| return; |
| } |
| if ('string' === typeof rules) { |
| rules = rules.toLowerCase(); |
| if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf('max-width')) { |
| extractQuery(rules); |
| } |
| } else { |
| for (var i = 0, j = rules.length; i < j; i++) { |
| if (1 === rules[i].type) { |
| selector = rules[i].selectorText || rules[i].cssText; |
| if (-1 !== selector.indexOf('min-height') || -1 !== selector.indexOf('max-height')) { |
| extractQuery(selector); |
| }else if(-1 !== selector.indexOf('min-width') || -1 !== selector.indexOf('max-width')) { |
| extractQuery(selector); |
| } |
| } else if (4 === rules[i].type) { |
| readRules(rules[i].cssRules || rules[i].rules); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Searches all css rules and setups the event listener to all elements with element query rules.. |
| * |
| * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements |
| * (no garbage collection possible if you don not call .detach() first) |
| */ |
| this.init = function(withTracking) { |
| this.withTracking = withTracking; |
| for (var i = 0, j = document.styleSheets.length; i < j; i++) { |
| try { |
| readRules(document.styleSheets[i].cssText || document.styleSheets[i].cssRules || document.styleSheets[i].rules); |
| } catch(e) { |
| if (e.name !== 'SecurityError') { |
| throw e; |
| } |
| } |
| } |
| }; |
| |
| /** |
| * |
| * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements |
| * (no garbage collection possible if you don not call .detach() first) |
| */ |
| this.update = function(withTracking) { |
| this.withTracking = withTracking; |
| this.init(); |
| }; |
| |
| this.detach = function() { |
| if (!this.withTracking) { |
| throw 'withTracking is not enabled. We can not detach elements since we don not store it.' + |
| 'Use ElementQueries.withTracking = true; before domready.'; |
| } |
| |
| var element; |
| while (element = elements.pop()) { |
| ElementQueries.detach(element); |
| } |
| |
| elements = []; |
| }; |
| }; |
| |
| /** |
| * |
| * @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements |
| * (no garbage collection possible if you don not call .detach() first) |
| */ |
| ElementQueries.update = function(withTracking) { |
| ElementQueries.instance.update(withTracking); |
| }; |
| |
| /** |
| * Removes all sensor and elementquery information from the element. |
| * |
| * @param {HTMLElement} element |
| */ |
| ElementQueries.detach = function(element) { |
| if (element.elementQueriesSetupInformation) { |
| element.elementQueriesSensor.detach(); |
| delete element.elementQueriesSetupInformation; |
| delete element.elementQueriesSensor; |
| console.log('detached'); |
| } else { |
| console.log('detached already', element); |
| } |
| }; |
| |
| ElementQueries.withTracking = false; |
| |
| ElementQueries.init = function() { |
| if (!ElementQueries.instance) { |
| ElementQueries.instance = new ElementQueries(); |
| } |
| |
| ElementQueries.instance.init(ElementQueries.withTracking); |
| }; |
| |
| var domLoaded = function (callback) { |
| /* Internet Explorer */ |
| /*@cc_on |
| @if (@_win32 || @_win64) |
| document.write('<script id="ieScriptLoad" defer src="//:"><\/script>'); |
| document.getElementById('ieScriptLoad').onreadystatechange = function() { |
| if (this.readyState == 'complete') { |
| callback(); |
| } |
| }; |
| @end @*/ |
| /* Mozilla, Chrome, Opera */ |
| if (document.addEventListener) { |
| document.addEventListener('DOMContentLoaded', callback, false); |
| } |
| /* Safari, iCab, Konqueror */ |
| if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) { |
| var DOMLoadTimer = setInterval(function () { |
| if (/loaded|complete/i.test(document.readyState)) { |
| callback(); |
| clearInterval(DOMLoadTimer); |
| } |
| }, 10); |
| } |
| /* Other web browsers */ |
| window.onload = callback; |
| }; |
| |
| if (window.addEventListener) { |
| window.addEventListener('load', ElementQueries.init, false); |
| } else { |
| window.attachEvent('onload', ElementQueries.init); |
| } |
| domLoaded(ElementQueries.init); |
| |
| })(); |