telegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchat
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
14 KiB
464 lines
14 KiB
/*! |
|
* viewport-units-buggyfill v0.6.0 |
|
* @web: https://github.com/rodneyrehm/viewport-units-buggyfill/ |
|
* @author: Rodney Rehm - http://rodneyrehm.de/en/ |
|
*/ |
|
|
|
(function (root, factory) { |
|
'use strict'; |
|
if (typeof define === 'function' && define.amd) { |
|
// AMD. Register as an anonymous module. |
|
define([], factory); |
|
} else if (typeof exports === 'object') { |
|
// Node. Does not work with strict CommonJS, but |
|
// only CommonJS-like enviroments that support module.exports, |
|
// like Node. |
|
module.exports = factory(); |
|
} else { |
|
// Browser globals (root is window) |
|
root.viewportUnitsBuggyfill = factory(); |
|
} |
|
}(this, function () { |
|
'use strict'; |
|
/*global document, window, navigator, location, XMLHttpRequest, XDomainRequest, CustomEvent*/ |
|
|
|
var initialized = false; |
|
var options; |
|
var userAgent = window.navigator.userAgent; |
|
var viewportUnitExpression = /([+-]?[0-9.]+)(vh|vw|vmin|vmax)/g; |
|
var forEach = [].forEach; |
|
var dimensions; |
|
var declarations; |
|
var styleNode; |
|
var isBuggyIE = /MSIE [0-9]\./i.test(userAgent); |
|
var isOldIE = /MSIE [0-8]\./i.test(userAgent); |
|
var isOperaMini = userAgent.indexOf('Opera Mini') > -1; |
|
|
|
var isMobileSafari = /(iPhone|iPod|iPad).+AppleWebKit/i.test(userAgent) && (function() { |
|
// Regexp for iOS-version tested against the following userAgent strings: |
|
// Example WebView UserAgents: |
|
// * iOS Chrome on iOS8: "Mozilla/5.0 (iPad; CPU OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/39.0.2171.50 Mobile/12B410 Safari/600.1.4" |
|
// * iOS Facebook on iOS7: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D201 [FBAN/FBIOS;FBAV/12.1.0.24.20; FBBV/3214247; FBDV/iPhone6,1;FBMD/iPhone; FBSN/iPhone OS;FBSV/7.1.1; FBSS/2; FBCR/AT&T;FBID/phone;FBLC/en_US;FBOP/5]" |
|
// Example Safari UserAgents: |
|
// * Safari iOS8: "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4" |
|
// * Safari iOS7: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A4449d Safari/9537.53" |
|
var iOSversion = userAgent.match(/OS (\d)/); |
|
// viewport units work fine in mobile Safari and webView on iOS 8+ |
|
return iOSversion && iOSversion.length>1 && parseInt(iOSversion[1]) < 10; |
|
})(); |
|
|
|
var isBadStockAndroid = (function() { |
|
// Android stock browser test derived from |
|
// http://stackoverflow.com/questions/24926221/distinguish-android-chrome-from-stock-browser-stock-browsers-user-agent-contai |
|
var isAndroid = userAgent.indexOf(' Android ') > -1; |
|
if (!isAndroid) { |
|
return false; |
|
} |
|
|
|
var isStockAndroid = userAgent.indexOf('Version/') > -1; |
|
if (!isStockAndroid) { |
|
return false; |
|
} |
|
|
|
var versionNumber = parseFloat((userAgent.match('Android ([0-9.]+)') || [])[1]); |
|
// anything below 4.4 uses WebKit without *any* viewport support, |
|
// 4.4 has issues with viewport units within calc() |
|
return versionNumber <= 4.4; |
|
})(); |
|
|
|
// added check for IE10, IE11 and Edge < 20, since it *still* doesn't understand vmax |
|
// http://caniuse.com/#feat=viewport-units |
|
if (!isBuggyIE) { |
|
isBuggyIE = !!navigator.userAgent.match(/MSIE 10\.|Trident.*rv[ :]*1[01]\.| Edge\/1\d\./); |
|
} |
|
|
|
// Polyfill for creating CustomEvents on IE9/10/11 |
|
// from https://github.com/krambuhl/custom-event-polyfill |
|
try { |
|
new CustomEvent('test'); |
|
} catch(e) { |
|
var CustomEvent = function(event, params) { |
|
var evt; |
|
params = params || { |
|
bubbles: false, |
|
cancelable: false, |
|
detail: undefined |
|
}; |
|
|
|
evt = document.createEvent('CustomEvent'); |
|
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); |
|
return evt; |
|
}; |
|
CustomEvent.prototype = window.Event.prototype; |
|
window.CustomEvent = CustomEvent; // expose definition to window |
|
} |
|
|
|
function debounce(func, wait) { |
|
var timeout; |
|
return function() { |
|
var context = this; |
|
var args = arguments; |
|
var callback = function() { |
|
func.apply(context, args); |
|
}; |
|
|
|
clearTimeout(timeout); |
|
timeout = setTimeout(callback, wait); |
|
}; |
|
} |
|
|
|
// from http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t |
|
function inIframe() { |
|
try { |
|
return window.self !== window.top; |
|
} catch (e) { |
|
return true; |
|
} |
|
} |
|
|
|
function initialize(initOptions) { |
|
if (initialized) { |
|
return; |
|
} |
|
|
|
if (initOptions === true) { |
|
initOptions = { |
|
force: true |
|
}; |
|
} |
|
|
|
options = initOptions || {}; |
|
options.isMobileSafari = isMobileSafari; |
|
options.isBadStockAndroid = isBadStockAndroid; |
|
|
|
if (options.ignoreVmax && !options.force && !isOldIE) { |
|
// modern IE (10 and up) do not support vmin/vmax, |
|
// but chances are this unit is not even used, so |
|
// allow overwriting the "hacktivation" |
|
// https://github.com/rodneyrehm/viewport-units-buggyfill/issues/56 |
|
isBuggyIE = false; |
|
} |
|
|
|
if (isOldIE || (!options.force && !isMobileSafari && !isBuggyIE && !isBadStockAndroid && !isOperaMini && (!options.hacks || !options.hacks.required(options)))) { |
|
// this buggyfill only applies to mobile safari, IE9-10 and the Stock Android Browser. |
|
if (window.console && isOldIE) { |
|
console.info('viewport-units-buggyfill requires a proper CSSOM and basic viewport unit support, which are not available in IE8 and below'); |
|
} |
|
|
|
return { |
|
init: function () {} |
|
}; |
|
} |
|
|
|
// fire a custom event that buggyfill was initialize |
|
window.dispatchEvent(new CustomEvent('viewport-units-buggyfill-init')); |
|
|
|
options.hacks && options.hacks.initialize(options); |
|
|
|
initialized = true; |
|
styleNode = document.createElement('style'); |
|
styleNode.id = 'patched-viewport'; |
|
document.head.appendChild(styleNode); |
|
|
|
// Issue #6: Cross Origin Stylesheets are not accessible through CSSOM, |
|
// therefore download and inject them as <style> to circumvent SOP. |
|
importCrossOriginLinks(function() { |
|
var _refresh = debounce(refresh, options.refreshDebounceWait || 100); |
|
// doing a full refresh rather than updateStyles because an orientationchange |
|
// could activate different stylesheets |
|
window.addEventListener('orientationchange', _refresh, true); |
|
// orientationchange might have happened while in a different window |
|
window.addEventListener('pageshow', _refresh, true); |
|
|
|
if (options.force || isBuggyIE || inIframe()) { |
|
window.addEventListener('resize', _refresh, true); |
|
options._listeningToResize = true; |
|
} |
|
|
|
options.hacks && options.hacks.initializeEvents(options, refresh, _refresh); |
|
|
|
refresh(); |
|
}); |
|
} |
|
|
|
function updateStyles() { |
|
styleNode.textContent = getReplacedViewportUnits(); |
|
// move to the end in case inline <style>s were added dynamically |
|
styleNode.parentNode.appendChild(styleNode); |
|
// fire a custom event that styles were updated |
|
window.dispatchEvent(new CustomEvent('viewport-units-buggyfill-style')); |
|
} |
|
|
|
function refresh() { |
|
if (!initialized) { |
|
return; |
|
} |
|
|
|
findProperties(); |
|
|
|
// iOS Safari will report window.innerWidth and .innerHeight as 0 unless a timeout is used here. |
|
// TODO: figure out WHY innerWidth === 0 |
|
setTimeout(function() { |
|
updateStyles(); |
|
}, 1); |
|
} |
|
|
|
// http://stackoverflow.com/a/23613052 |
|
function processStylesheet(ss) { |
|
// cssRules respects same-origin policy, as per |
|
// https://code.google.com/p/chromium/issues/detail?id=49001#c10. |
|
try { |
|
if (!ss.cssRules) { return; } |
|
} catch(e) { |
|
if (e.name !== 'SecurityError') { throw e; } |
|
return; |
|
} |
|
// ss.cssRules is available, so proceed with desired operations. |
|
var rules = []; |
|
for (var i = 0; i < ss.cssRules.length; i++) { |
|
var rule = ss.cssRules[i]; |
|
rules.push(rule); |
|
} |
|
return rules; |
|
} |
|
|
|
function findProperties() { |
|
declarations = []; |
|
forEach.call(document.styleSheets, function(sheet) { |
|
var cssRules = processStylesheet(sheet); |
|
|
|
if (!cssRules || sheet.ownerNode.id === 'patched-viewport' || sheet.ownerNode.getAttribute('data-viewport-units-buggyfill') === 'ignore') { |
|
// skip entire sheet because no rules are present, it's supposed to be ignored or it's the target-element of the buggyfill |
|
return; |
|
} |
|
|
|
if (sheet.media && sheet.media.mediaText && window.matchMedia && !window.matchMedia(sheet.media.mediaText).matches) { |
|
// skip entire sheet because media attribute doesn't match |
|
return; |
|
} |
|
|
|
forEach.call(cssRules, findDeclarations); |
|
}); |
|
|
|
return declarations; |
|
} |
|
|
|
function findDeclarations(rule) { |
|
if (rule.type === 7) { |
|
var value; |
|
|
|
// there may be a case where accessing cssText throws an error. |
|
// I could not reproduce this issue, but the worst that can happen |
|
// this way is an animation not running properly. |
|
// not awesome, but probably better than a script error |
|
// see https://github.com/rodneyrehm/viewport-units-buggyfill/issues/21 |
|
try { |
|
value = rule.cssText; |
|
} catch(e) { |
|
return; |
|
} |
|
|
|
viewportUnitExpression.lastIndex = 0; |
|
if (viewportUnitExpression.test(value)) { |
|
// KeyframesRule does not have a CSS-PropertyName |
|
declarations.push([rule, null, value]); |
|
options.hacks && options.hacks.findDeclarations(declarations, rule, null, value); |
|
} |
|
|
|
return; |
|
} |
|
|
|
if (!rule.style) { |
|
if (!rule.cssRules) { |
|
return; |
|
} |
|
|
|
forEach.call(rule.cssRules, function(_rule) { |
|
findDeclarations(_rule); |
|
}); |
|
|
|
return; |
|
} |
|
|
|
forEach.call(rule.style, function(name) { |
|
var value = rule.style.getPropertyValue(name); |
|
// preserve those !important rules |
|
if (rule.style.getPropertyPriority(name)) { |
|
value += ' !important'; |
|
} |
|
|
|
viewportUnitExpression.lastIndex = 0; |
|
if (viewportUnitExpression.test(value)) { |
|
declarations.push([rule, name, value]); |
|
options.hacks && options.hacks.findDeclarations(declarations, rule, name, value); |
|
} |
|
}); |
|
} |
|
|
|
function getReplacedViewportUnits() { |
|
dimensions = getViewport(); |
|
|
|
var css = []; |
|
var buffer = []; |
|
var open; |
|
var close; |
|
|
|
declarations.forEach(function(item) { |
|
var _item = overwriteDeclaration.apply(null, item); |
|
var _open = _item.selector.length ? (_item.selector.join(' {\n') + ' {\n') : ''; |
|
var _close = new Array(_item.selector.length + 1).join('\n}'); |
|
|
|
if (!_open || _open !== open) { |
|
if (buffer.length) { |
|
css.push(open + buffer.join('\n') + close); |
|
buffer.length = 0; |
|
} |
|
|
|
if (_open) { |
|
open = _open; |
|
close = _close; |
|
buffer.push(_item.content); |
|
} else { |
|
css.push(_item.content); |
|
open = null; |
|
close = null; |
|
} |
|
|
|
return; |
|
} |
|
|
|
if (_open && !open) { |
|
open = _open; |
|
close = _close; |
|
} |
|
|
|
buffer.push(_item.content); |
|
}); |
|
|
|
if (buffer.length) { |
|
css.push(open + buffer.join('\n') + close); |
|
} |
|
|
|
// Opera Mini messes up on the content hack (it replaces the DOM node's innerHTML with the value). |
|
// This fixes it. We test for Opera Mini only since it is the most expensive CSS selector |
|
// see https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors |
|
if (isOperaMini) { |
|
css.push('* { content: normal !important; }'); |
|
} |
|
|
|
return css.join('\n\n'); |
|
} |
|
|
|
function overwriteDeclaration(rule, name, value) { |
|
var _value; |
|
var _selectors = []; |
|
|
|
_value = value.replace(viewportUnitExpression, replaceValues); |
|
|
|
if (options.hacks) { |
|
_value = options.hacks.overwriteDeclaration(rule, name, _value); |
|
} |
|
|
|
if (name) { |
|
// skipping KeyframesRule |
|
_selectors.push(rule.selectorText); |
|
_value = name + ': ' + _value + ';'; |
|
} |
|
|
|
var _rule = rule.parentRule; |
|
while (_rule) { |
|
_selectors.unshift('@media ' + _rule.media.mediaText); |
|
_rule = _rule.parentRule; |
|
} |
|
|
|
return { |
|
selector: _selectors, |
|
content: _value |
|
}; |
|
} |
|
|
|
function replaceValues(match, number, unit) { |
|
var _base = dimensions[unit]; |
|
var _number = parseFloat(number) / 100; |
|
return (_number * _base) + 'px'; |
|
} |
|
|
|
function getViewport() { |
|
var vh = window.innerHeight; |
|
var vw = window.innerWidth; |
|
|
|
return { |
|
vh: vh, |
|
vw: vw, |
|
vmax: Math.max(vw, vh), |
|
vmin: Math.min(vw, vh) |
|
}; |
|
} |
|
|
|
function importCrossOriginLinks(next) { |
|
var _waiting = 0; |
|
var decrease = function() { |
|
_waiting--; |
|
if (!_waiting) { |
|
next(); |
|
} |
|
}; |
|
|
|
forEach.call(document.styleSheets, function(sheet) { |
|
if (!sheet.href || origin(sheet.href) === origin(location.href) || sheet.ownerNode.getAttribute('data-viewport-units-buggyfill') === 'ignore') { |
|
// skip <style> and <link> from same origin or explicitly declared to ignore |
|
return; |
|
} |
|
|
|
_waiting++; |
|
convertLinkToStyle(sheet.ownerNode, decrease); |
|
}); |
|
|
|
if (!_waiting) { |
|
next(); |
|
} |
|
} |
|
|
|
function origin(url) { |
|
return url.slice(0, url.indexOf('/', url.indexOf('://') + 3)); |
|
} |
|
|
|
function convertLinkToStyle(link, next) { |
|
getCors(link.href, function() { |
|
var style = document.createElement('style'); |
|
style.media = link.media; |
|
style.setAttribute('data-href', link.href); |
|
style.textContent = this.responseText; |
|
link.parentNode.replaceChild(style, link); |
|
next(); |
|
}, next); |
|
} |
|
|
|
function getCors(url, success, error) { |
|
var xhr = new XMLHttpRequest(); |
|
if ('withCredentials' in xhr) { |
|
// XHR for Chrome/Firefox/Opera/Safari. |
|
xhr.open('GET', url, true); |
|
} else if (typeof XDomainRequest !== 'undefined') { |
|
// XDomainRequest for IE. |
|
xhr = new XDomainRequest(); |
|
xhr.open('GET', url); |
|
} else { |
|
throw new Error('cross-domain XHR not supported'); |
|
} |
|
|
|
xhr.onload = success; |
|
xhr.onerror = error; |
|
xhr.send(); |
|
return xhr; |
|
} |
|
|
|
return { |
|
version: '0.6.0', |
|
findProperties: findProperties, |
|
getCss: getReplacedViewportUnits, |
|
init: initialize, |
|
refresh: refresh |
|
}; |
|
|
|
})); |