REM-unit-polyfill.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. (function (window, undefined) {
  2. "use strict";
  3. // test for REM unit support
  4. var cssremunit = function () {
  5. var div = document.createElement("div");
  6. div.style.cssText = "font-size: 1rem;";
  7. return /rem/.test(div.style.fontSize);
  8. },
  9. // filter returned links for stylesheets
  10. isStyleSheet = function () {
  11. var styles = document.getElementsByTagName("link"),
  12. filteredLinks = [];
  13. for (var i = 0; i < styles.length; i++) {
  14. if (
  15. styles[i].rel.toLowerCase() === "stylesheet" &&
  16. styles[i].getAttribute("data-norem") === null
  17. ) {
  18. filteredLinks.push(styles[i].href);
  19. }
  20. }
  21. return filteredLinks;
  22. },
  23. processLinks = function () {
  24. //prepare to match each link
  25. for (var i = 0; i < links.length; i++) {
  26. xhr(links[i], storeCSS);
  27. }
  28. },
  29. storeCSS = function (response, link) {
  30. preCSS.push(response.responseText);
  31. CSSLinks.push(link);
  32. if (CSSLinks.length === links.length) {
  33. for (var j = 0; j < CSSLinks.length; j++) {
  34. matchCSS(preCSS[j], CSSLinks[j]);
  35. }
  36. if ((links = importLinks.slice(0)).length > 0) {
  37. //after finishing all current links, set links equal to the new imports found
  38. CSSLinks = [];
  39. preCSS = [];
  40. importLinks = [];
  41. processLinks();
  42. } else {
  43. buildCSS();
  44. }
  45. }
  46. },
  47. matchCSS = function (sheetCSS, link) {
  48. // collect all of the rules from the xhr response texts and match them to a pattern
  49. var clean = removeMediaQueries(sheetCSS).replace(/\/\*[\s\S]*?\*\//g, ""), // remove MediaQueries and comments
  50. pattern =
  51. /[\w\d\s\-\/\\\[\]:,.'"*()<>+~%#^$_=|@]+\{[\w\d\s\-\/\\%#:!;,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!;,.'"*()]*\}/g, //find selectors that use rem in one or more of their rules
  52. current = clean.match(pattern),
  53. remPattern = /\d*\.?\d+rem/g,
  54. remCurrent = clean.match(remPattern),
  55. sheetPathPattern = /(.*\/)/,
  56. sheetPath = sheetPathPattern.exec(link)[0], //relative path to css file specified in @import
  57. importPattern = /@import (?:url\()?['"]?([^'\)"]*)['"]?\)?[^;]*/gm, //matches all @import variations outlined at: https://developer.mozilla.org/en-US/docs/Web/CSS/@import
  58. importStatement;
  59. while ((importStatement = importPattern.exec(sheetCSS)) !== null) {
  60. if (importStatement[1].indexOf("/") === 0) {
  61. // check if the value of importStatement[1] is a root relative path, in which case it shouldn't be concatenated with sheetPath
  62. importLinks.push(importStatement[1]);
  63. } else {
  64. importLinks.push(sheetPath + importStatement[1]);
  65. }
  66. }
  67. if (current !== null && current.length !== 0) {
  68. found = found.concat(current); // save all of the blocks of rules with rem in a property
  69. foundProps = foundProps.concat(remCurrent); // save all of the properties with rem
  70. }
  71. },
  72. buildCSS = function () {
  73. // first build each individual rule from elements in the found array and then add it to the string of rules.
  74. var pattern =
  75. /[\w\d\s\-\/\\%#:,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!,.'"*()]*[;}]/g; // find properties with rem values in them
  76. for (var i = 0; i < found.length; i++) {
  77. rules = rules + found[i].substr(0, found[i].indexOf("{") + 1); // save the selector portion of each rule with a rem value
  78. var current = found[i].match(pattern);
  79. for (var j = 0; j < current.length; j++) {
  80. // build a new set of with only the selector and properties that have rem in the value
  81. rules = rules + current[j];
  82. if (j === current.length - 1 && rules[rules.length - 1] !== "}") {
  83. rules = rules + "\n}";
  84. }
  85. }
  86. }
  87. parseCSS();
  88. },
  89. parseCSS = function () {
  90. // replace each set of parentheses with evaluated content
  91. for (var i = 0; i < foundProps.length; i++) {
  92. css[i] =
  93. Math.round(
  94. parseFloat(
  95. foundProps[i].substr(0, foundProps[i].length - 3) * fontSize
  96. )
  97. ) + "px";
  98. }
  99. loadCSS();
  100. },
  101. loadCSS = function () {
  102. // replace and load the new rules
  103. for (var i = 0; i < css.length; i++) {
  104. // only run this loop as many times as css has entries
  105. if (css[i]) {
  106. rules = rules.replace(foundProps[i], css[i]); // replace old rules with our processed rules
  107. }
  108. }
  109. var remcss = document.createElement("style");
  110. remcss.setAttribute("type", "text/css");
  111. remcss.id = "remReplace";
  112. document.getElementsByTagName("head")[0].appendChild(remcss); // create the new element
  113. if (remcss.styleSheet) {
  114. remcss.styleSheet.cssText = rules; // IE8 will not support innerHTML on read-only elements, such as STYLE
  115. } else {
  116. remcss.appendChild(document.createTextNode(rules));
  117. }
  118. },
  119. xhr = function (url, callback) {
  120. // create new XMLHttpRequest object and run it
  121. try {
  122. //try to create a request object
  123. //arranging the two conditions this way is for IE7/8's benefit
  124. //so that it works with any combination of ActiveX or Native XHR settings,
  125. //as long as one or the other is enabled; but if both are enabled
  126. //it prefers ActiveX, which means it still works with local files
  127. //(Native XHR in IE7/8 is blocked and throws "access is denied",
  128. // but ActiveX is permitted if the user allows it [default is to prompt])
  129. var xhr = window.ActiveXObject
  130. ? new ActiveXObject("Microsoft.XMLHTTP") ||
  131. new ActiveXObject("Msxml2.XMLHTTP")
  132. : new XMLHttpRequest();
  133. xhr.open("GET", url, true);
  134. xhr.onreadystatechange = function () {
  135. if (xhr.readyState === 4) {
  136. callback(xhr, url);
  137. } // else { callback function on AJAX error }
  138. };
  139. xhr.send(null);
  140. } catch (e) {
  141. if (window.XDomainRequest) {
  142. var xdr = new XDomainRequest();
  143. xdr.open("get", url);
  144. xdr.onload = function () {
  145. callback(xdr, url);
  146. };
  147. xdr.onerror = function () {
  148. return false; // xdr load fail
  149. };
  150. xdr.send();
  151. }
  152. }
  153. },
  154. // Remove queries.
  155. removeMediaQueries = function (css) {
  156. // Test for Media Query support
  157. if (!window.matchMedia && !window.msMatchMedia) {
  158. // If the browser doesn't support media queries, we find all @media declarations in the CSS and remove them.
  159. // Note: Since @rules can't be nested in the CSS spec, we're safe to just check for the closest following "}}" to the "@media".
  160. css = css.replace(/@media[\s\S]*?\}\s*\}/g, "");
  161. }
  162. return css;
  163. };
  164. if (!cssremunit()) {
  165. // this checks if the rem value is supported
  166. var rules = "", // initialize the rules variable in this scope so it can be used later
  167. links = isStyleSheet(), // initialize the array holding the sheets urls for use later
  168. importLinks = [], //initialize the array holding the import sheet urls for use later
  169. found = [], // initialize the array holding the found rules for use later
  170. foundProps = [], // initialize the array holding the found properties for use later
  171. preCSS = [], // initialize array that holds css before being parsed
  172. CSSLinks = [], //initialize array holding css links returned from xhr
  173. css = [], // initialize the array holding the parsed rules for use later
  174. fontSize = "";
  175. // Notice: rem is a "root em" that means that in case when html element size was changed by css
  176. // or style we should not change document.documentElement.fontSize to 1em - only body size should be changed
  177. // to 1em for calculation
  178. fontSize = (function () {
  179. var doc = document,
  180. docElement = doc.documentElement,
  181. body = doc.body || doc.createElement("body"),
  182. isFakeBody = !doc.body,
  183. div = doc.createElement("div"),
  184. currentSize = body.style.fontSize,
  185. size;
  186. if (isFakeBody) {
  187. docElement.appendChild(body);
  188. }
  189. div.style.cssText =
  190. "width:1em; position:absolute; visibility:hidden; padding: 0;";
  191. body.style.fontSize = "1em";
  192. body.appendChild(div);
  193. size = div.offsetWidth;
  194. if (isFakeBody) {
  195. docElement.removeChild(body);
  196. } else {
  197. body.removeChild(div);
  198. body.style.fontSize = currentSize;
  199. }
  200. return size;
  201. })();
  202. processLinks();
  203. } // else { do nothing, you are awesome and have REM support }
  204. })(window);