media.match.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*! MediaMatch v.2.0.3 - Testing css media queries in Javascript. Authors & copyright (c) 2013: WebLinc, David Knight. */
  2. window.matchMedia || (window.matchMedia = function (win) {
  3. 'use strict';
  4. // Internal globals
  5. var _doc = win.document,
  6. _viewport = _doc.documentElement,
  7. _queries = [],
  8. _queryID = 0,
  9. _type = '',
  10. _features = {},
  11. // only screen
  12. // only screen and
  13. // not screen
  14. // not screen and
  15. // screen
  16. // screen and
  17. _typeExpr = /\s*(only|not)?\s*(screen|print|[a-z\-]+)\s*(and)?\s*/i,
  18. // (-vendor-min-width: 300px)
  19. // (min-width: 300px)
  20. // (width: 300px)
  21. // (width)
  22. // (orientation: portrait|landscape)
  23. _mediaExpr = /^\s*\(\s*(-[a-z]+-)?(min-|max-)?([a-z\-]+)\s*(:?\s*([0-9]+(\.[0-9]+)?|portrait|landscape)(px|em|dppx|dpcm|rem|%|in|cm|mm|ex|pt|pc|\/([0-9]+(\.[0-9]+)?))?)?\s*\)\s*$/,
  24. _timer = 0,
  25. // Helper methods
  26. /*
  27. _matches
  28. */
  29. _matches = function (media) {
  30. // screen and (min-width: 400px), screen and (max-width: 500px)
  31. var mql = (media.indexOf(',') !== -1 && media.split(',')) || [media],
  32. mqIndex = mql.length - 1,
  33. mqLength = mqIndex,
  34. mq = null,
  35. // not screen, screen
  36. negateType = null,
  37. negateTypeFound = '',
  38. negateTypeIndex = 0,
  39. negate = false,
  40. type = '',
  41. // (min-width: 400px), (min-width)
  42. exprListStr = '',
  43. exprList = null,
  44. exprIndex = 0,
  45. exprLength = 0,
  46. expr = null,
  47. prefix = '',
  48. length = '',
  49. unit = '',
  50. value = '',
  51. feature = '',
  52. match = false;
  53. if (media === '') {
  54. return true;
  55. }
  56. do {
  57. mq = mql[mqLength - mqIndex];
  58. negate = false;
  59. negateType = mq.match(_typeExpr);
  60. if (negateType) {
  61. negateTypeFound = negateType[0];
  62. negateTypeIndex = negateType.index;
  63. }
  64. if (!negateType || ((mq.substring(0, negateTypeIndex).indexOf('(') === -1) && (negateTypeIndex || (!negateType[3] && negateTypeFound !== negateType.input)))) {
  65. match = false;
  66. continue;
  67. }
  68. exprListStr = mq;
  69. negate = negateType[1] === 'not';
  70. if (!negateTypeIndex) {
  71. type = negateType[2];
  72. exprListStr = mq.substring(negateTypeFound.length);
  73. }
  74. // Test media type
  75. // Test type against this device or if 'all' or empty ''
  76. match = type === _type || type === 'all' || type === '';
  77. exprList = (exprListStr.indexOf(' and ') !== -1 && exprListStr.split(' and ')) || [exprListStr];
  78. exprIndex = exprList.length - 1;
  79. exprLength = exprIndex;
  80. if (match && exprIndex >= 0 && exprListStr !== '') {
  81. do {
  82. expr = exprList[exprIndex].match(_mediaExpr);
  83. if (!expr || !_features[expr[3]]) {
  84. match = false;
  85. break;
  86. }
  87. prefix = expr[2];
  88. length = expr[5];
  89. value = length;
  90. unit = expr[7];
  91. feature = _features[expr[3]];
  92. // Convert unit types
  93. if (unit) {
  94. if (unit === 'px') {
  95. // If unit is px
  96. value = Number(length);
  97. } else if (unit === 'em' || unit === 'rem') {
  98. // Convert relative length unit to pixels
  99. // Assumed base font size is 16px
  100. value = 16 * length;
  101. } else if (expr[8]) {
  102. // Convert aspect ratio to decimal
  103. value = (length / expr[8]).toFixed(2);
  104. } else if (unit === 'dppx') {
  105. // Convert resolution dppx unit to pixels
  106. value = length * 96;
  107. } else if (unit === 'dpcm') {
  108. // Convert resolution dpcm unit to pixels
  109. value = length * 0.3937;
  110. } else {
  111. // default
  112. value = Number(length);
  113. }
  114. }
  115. // Test for prefix min or max
  116. // Test value against feature
  117. if (prefix === 'min-' && value) {
  118. match = feature >= value;
  119. } else if (prefix === 'max-' && value) {
  120. match = feature <= value;
  121. } else if (value) {
  122. match = feature === value;
  123. } else {
  124. match = !!feature;
  125. }
  126. // If 'match' is false, break loop
  127. // Continue main loop through query list
  128. if (!match) {
  129. break;
  130. }
  131. } while (exprIndex--);
  132. }
  133. // If match is true, break loop
  134. // Once matched, no need to check other queries
  135. if (match) {
  136. break;
  137. }
  138. } while (mqIndex--);
  139. return negate ? !match : match;
  140. },
  141. /*
  142. _setFeature
  143. */
  144. _setFeature = function () {
  145. // Sets properties of '_features' that change on resize and/or orientation.
  146. var w = win.innerWidth || _viewport.clientWidth,
  147. h = win.innerHeight || _viewport.clientHeight,
  148. dw = win.screen.width,
  149. dh = win.screen.height,
  150. c = win.screen.colorDepth,
  151. x = win.devicePixelRatio;
  152. _features.width = w;
  153. _features.height = h;
  154. _features['aspect-ratio'] = (w / h).toFixed(2);
  155. _features['device-width'] = dw;
  156. _features['device-height'] = dh;
  157. _features['device-aspect-ratio'] = (dw / dh).toFixed(2);
  158. _features.color = c;
  159. _features['color-index'] = Math.pow(2, c);
  160. _features.orientation = (h >= w ? 'portrait' : 'landscape');
  161. _features.resolution = (x && x * 96) || win.screen.deviceXDPI || 96;
  162. _features['device-pixel-ratio'] = x || 1;
  163. },
  164. /*
  165. _watch
  166. */
  167. _watch = function () {
  168. clearTimeout(_timer);
  169. _timer = setTimeout(function () {
  170. var query = null,
  171. qIndex = _queryID - 1,
  172. qLength = qIndex,
  173. match = false;
  174. if (qIndex >= 0) {
  175. _setFeature();
  176. do {
  177. query = _queries[qLength - qIndex];
  178. if (query) {
  179. match = _matches(query.mql.media);
  180. if ((match && !query.mql.matches) || (!match && query.mql.matches)) {
  181. query.mql.matches = match;
  182. if (query.listeners) {
  183. for (var i = 0, il = query.listeners.length; i < il; i++) {
  184. if (query.listeners[i]) {
  185. query.listeners[i].call(win, query.mql);
  186. }
  187. }
  188. }
  189. }
  190. }
  191. } while(qIndex--);
  192. }
  193. }, 10);
  194. },
  195. /*
  196. _init
  197. */
  198. _init = function () {
  199. var head = _doc.getElementsByTagName('head')[0],
  200. style = _doc.createElement('style'),
  201. info = null,
  202. typeList = ['screen', 'print', 'speech', 'projection', 'handheld', 'tv', 'braille', 'embossed', 'tty'],
  203. typeIndex = 0,
  204. typeLength = typeList.length,
  205. cssText = '#mediamatchjs { position: relative; z-index: 0; }',
  206. eventPrefix = '',
  207. addEvent = win.addEventListener || (eventPrefix = 'on') && win.attachEvent;
  208. style.type = 'text/css';
  209. style.id = 'mediamatchjs';
  210. head.appendChild(style);
  211. // Must be placed after style is inserted into the DOM for IE
  212. info = (win.getComputedStyle && win.getComputedStyle(style)) || style.currentStyle;
  213. // Create media blocks to test for media type
  214. for ( ; typeIndex < typeLength; typeIndex++) {
  215. cssText += '@media ' + typeList[typeIndex] + ' { #mediamatchjs { position: relative; z-index: ' + typeIndex + ' } }';
  216. }
  217. // Add rules to style element
  218. if (style.styleSheet) {
  219. style.styleSheet.cssText = cssText;
  220. } else {
  221. style.textContent = cssText;
  222. }
  223. // Get media type
  224. _type = typeList[(info.zIndex * 1) || 0];
  225. head.removeChild(style);
  226. _setFeature();
  227. // Set up listeners
  228. addEvent(eventPrefix + 'resize', _watch, false);
  229. addEvent(eventPrefix + 'orientationchange', _watch, false);
  230. };
  231. _init();
  232. /*
  233. A list of parsed media queries, ex. screen and (max-width: 400px), screen and (max-width: 800px)
  234. */
  235. return function (media) {
  236. var id = _queryID,
  237. mql = {
  238. matches : false,
  239. media : media,
  240. addListener : function addListener(listener) {
  241. _queries[id].listeners || (_queries[id].listeners = []);
  242. listener && _queries[id].listeners.push(listener);
  243. },
  244. removeListener : function removeListener(listener) {
  245. var query = _queries[id],
  246. i = 0,
  247. il = 0;
  248. if (!query) {
  249. return;
  250. }
  251. il = query.listeners.length;
  252. for ( ; i < il; i++) {
  253. if (query.listeners[i] === listener) {
  254. query.listeners.splice(i, 1);
  255. }
  256. }
  257. }
  258. };
  259. if (media === '') {
  260. mql.matches = true;
  261. return mql;
  262. }
  263. mql.matches = _matches(media);
  264. _queryID = _queries.push({
  265. mql : mql,
  266. listeners : null
  267. });
  268. return mql;
  269. };
  270. }(window));