2 * viewport-units-buggyfill v0.4.1
3 * @web: https://github.com/rodneyrehm/viewport-units-buggyfill/
4 * @author: Rodney Rehm - http://rodneyrehm.de/en/
7 (function (root, factory) {
9 if (typeof define === 'function' && define.amd) {
10 // AMD. Register as an anonymous module.
12 } else if (typeof exports === 'object') {
13 // Node. Does not work with strict CommonJS, but
14 // only CommonJS-like enviroments that support module.exports,
16 module.exports = factory();
18 // Browser globals (root is window)
19 root.viewportUnitsBuggyfill = factory();
23 /*global document, window, location, XMLHttpRequest, XDomainRequest*/
25 var initialized = false;
27 var isMobileSafari = /(iPhone|iPod|iPad).+AppleWebKit/i.test(window.navigator.userAgent);
28 var viewportUnitExpression = /([+-]?[0-9.]+)(vh|vw|vmin|vmax)/g;
29 var forEach = [].forEach;
33 var isOldInternetExplorer = false;
35 // Do not remove the following comment!
36 // It is a conditional comment used to
37 // identify old Internet Explorer versions
41 @if (@_jscript_version <= 10)
42 isOldInternetExplorer = true;
47 function debounce(func, wait) {
52 var callback = function() {
53 func.apply(context, args);
56 clearTimeout(timeout);
57 timeout = setTimeout(callback, wait);
61 // from http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t
64 return window.self !== window.top;
70 function initialize(initOptions) {
75 if (initOptions === true) {
81 options = initOptions || {};
82 options.isMobileSafari = isMobileSafari;
84 if (!options.force && !isMobileSafari && !isOldInternetExplorer && (!options.hacks || !options.hacks.required(options))) {
85 // this buggyfill only applies to mobile safari
89 options.hacks && options.hacks.initialize(options);
92 styleNode = document.createElement('style');
93 styleNode.id = 'patched-viewport';
94 document.head.appendChild(styleNode);
96 // Issue #6: Cross Origin Stylesheets are not accessible through CSSOM,
97 // therefore download and inject them as <style> to circumvent SOP.
98 importCrossOriginLinks(function() {
99 var _refresh = debounce(refresh, options.refreshDebounceWait || 100);
100 // doing a full refresh rather than updateStyles because an orientationchange
101 // could activate different stylesheets
102 window.addEventListener('orientationchange', _refresh, true);
103 // orientationchange might have happened while in a different window
104 window.addEventListener('pageshow', _refresh, true);
106 if (options.force || isOldInternetExplorer || inIframe()) {
107 window.addEventListener('resize', _refresh, true);
108 options._listeningToResize = true;
111 options.hacks && options.hacks.initializeEvents(options, refresh, _refresh);
117 function updateStyles() {
118 styleNode.textContent = getReplacedViewportUnits();
128 // iOS Safari will report window.innerWidth and .innerHeight as 0
129 // unless a timeout is used here.
130 // TODO: figure out WHY innerWidth === 0
131 setTimeout(function() {
136 function findProperties() {
138 forEach.call(document.styleSheets, function(sheet) {
139 if (sheet.ownerNode.id === 'patched-viewport' || !sheet.cssRules) {
140 // skip entire sheet because no rules ara present or it's the target-element of the buggyfill
144 if (sheet.media && sheet.media.mediaText && window.matchMedia && !window.matchMedia(sheet.media.mediaText).matches) {
145 // skip entire sheet because media attribute doesn't match
149 forEach.call(sheet.cssRules, findDeclarations);
155 function findDeclarations(rule) {
156 if (rule.type === 7) {
157 var value = rule.cssText;
158 viewportUnitExpression.lastIndex = 0;
159 if (viewportUnitExpression.test(value)) {
160 // KeyframesRule does not have a CSS-PropertyName
161 declarations.push([rule, null, value]);
162 options.hacks && options.hacks.findDeclarations(declarations, rule, null, value);
169 if (!rule.cssRules) {
173 forEach.call(rule.cssRules, function(_rule) {
174 findDeclarations(_rule);
180 forEach.call(rule.style, function(name) {
181 var value = rule.style.getPropertyValue(name);
182 viewportUnitExpression.lastIndex = 0;
183 if (viewportUnitExpression.test(value)) {
184 declarations.push([rule, name, value]);
185 options.hacks && options.hacks.findDeclarations(declarations, rule, name, value);
190 function getReplacedViewportUnits() {
191 dimensions = getViewport();
198 declarations.forEach(function(item) {
199 var _item = overwriteDeclaration.apply(null, item);
200 var _open = _item.selector.length ? (_item.selector.join(' {\n') + ' {\n') : '';
201 var _close = new Array(_item.selector.length + 1).join('\n}');
203 if (!_open || _open !== open) {
205 css.push(open + buffer.join('\n') + close);
212 buffer.push(_item.content);
214 css.push(_item.content);
222 if (_open && !open) {
227 buffer.push(_item.content);
231 css.push(open + buffer.join('\n') + close);
234 return css.join('\n\n');
237 function overwriteDeclaration(rule, name, value) {
238 var _value = value.replace(viewportUnitExpression, replaceValues);
242 _value = options.hacks.overwriteDeclaration(rule, name, _value);
246 // skipping KeyframesRule
247 _selectors.push(rule.selectorText);
248 _value = name + ': ' + _value + ';';
251 var _rule = rule.parentRule;
253 _selectors.unshift('@media ' + _rule.media.mediaText);
254 _rule = _rule.parentRule;
258 selector: _selectors,
263 function replaceValues(match, number, unit) {
264 var _base = dimensions[unit];
265 var _number = parseFloat(number) / 100;
266 return (_number * _base) + 'px';
269 function getViewport() {
270 var vh = window.innerHeight;
271 var vw = window.innerWidth;
276 vmax: Math.max(vw, vh),
277 vmin: Math.min(vw, vh)
281 function importCrossOriginLinks(next) {
283 var decrease = function() {
290 forEach.call(document.styleSheets, function(sheet) {
291 if (!sheet.href || origin(sheet.href) === origin(location.href)) {
292 // skip <style> and <link> from same origin
297 convertLinkToStyle(sheet.ownerNode, decrease);
305 function origin(url) {
306 return url.slice(0, url.indexOf('/', url.indexOf('://') + 3));
309 function convertLinkToStyle(link, next) {
310 getCors(link.href, function() {
311 var style = document.createElement('style');
312 style.media = link.media;
313 style.setAttribute('data-href', link.href);
314 style.textContent = this.responseText;
315 link.parentNode.replaceChild(style, link);
320 function getCors(url, success, error) {
321 var xhr = new XMLHttpRequest();
322 if ('withCredentials' in xhr) {
323 // XHR for Chrome/Firefox/Opera/Safari.
324 xhr.open('GET', url, true);
325 } else if (typeof XDomainRequest !== 'undefined') {
326 // XDomainRequest for IE.
327 xhr = new XDomainRequest();
328 xhr.open('GET', url);
330 throw new Error('cross-domain XHR not supported');
333 xhr.onload = success;
341 findProperties: findProperties,
342 getCss: getReplacedViewportUnits,