├── .gitattributes ├── LICENSE ├── mq.genie.min.js ├── .gitignore ├── README.md └── mq.genie.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Matt Stow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /mq.genie.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * mqGenie v0.5.0 3 | * 4 | * Adjusts CSS media queries in browsers that include the scrollbar's width in the viewport width so they fire at the intended size 5 | * 6 | * Returns the mqGenie object containing .adjusted, .width & fontSize for use in re-calculating media queries in JavaScript with mqAdjust(string) 7 | * 8 | * Copyright (c) 2014 Matt Stow 9 | * 10 | * http://mattstow.com 11 | * 12 | * Licensed under the MIT license 13 | */ 14 | (function(d,b){if(!b.addEventListener){d.mqGenie={adjustMediaQuery:function(i){return i}};return}function e(k,l){var o=k.cssRules?k.cssRules:k.media,n,p=[],j=0,m=o.length;for(j;j0,fontSize:parseFloat(d.getComputedStyle(r).getPropertyValue("font-size")),width:l,adjustMediaQuery:function(j){if(!mqGenie.adjusted){return j}var i=j.replace(/\d+px/gi,function(w){return parseInt(w,10)+mqGenie.width+"px"});i=i.replace(/\d.+?em/gi,function(w){return((parseFloat(w)*mqGenie.fontSize)+mqGenie.width)/mqGenie.fontSize+"em"});return i}};if(s.adjusted){if("WebkitAppearance" in r.style){var k=/Chrome\/(\d*?\.\d*?\.\d*?\.\d*?)\s/g,q=navigator.userAgent.match(k),u;if(q){q=q[0].replace(k,"$1");u=q.split(".");u[0]=parseInt(u[0]);u[2]=parseInt(u[2]);u[3]=parseInt(u[3]);if(u[0]<=29){if(u[0]===29&&u[2]<1548&&u[3]<57){s.adjusted=false}else{if(u[0]<29){s.adjusted=false}}}}else{s.adjusted=false}if(!s.adjusted){return s}}var t=h(),m=t.length,p=0,n,v;for(p;p` element. 17 | * It then compares `window.innerWidth` to `document.documentElement.clientWidth`. If they're different, this value equals the width of the browser's scrollbar. 18 | * Then it loops through your stylesheets' media queries and increases all of the `min-width` and `max-width` ones by the width of the scrollbar so that they fire at the correct size. It also converts em-based ones, using the HTML's computed `font-size`. 19 | 20 | It returns a JavaScript object called `mqGenie`, which contains the following properties: 21 | 22 | * `adjusted` (boolean - whether your media queries were adusted) 23 | * `fontSize` (the computed HTML font-size) 24 | * `width` (the width adjusted by) 25 | * `adjustMediaQuery(media-query)` (function) allows you to re-calculate media queries that are written in JavaScript. Simply pass `adjustMediaQuery` the media query string and it will return one that's adjusted appropriately. 26 | 27 | #### Do we need it? 28 | 29 | Consider mqGenie as **progressive enhancement**. 30 | 31 | Ideally, your responsive projects will be built in a flexible way, such that a 15-20px difference in media queries shouldn't matter too much. 32 | 33 | However, there are definitely times where things can go awry. Fixed-width ads and other modules may rely on more precise measurements, and - while I don't condone targeting device widths specifically - writing a 768px media query and not having it triggered on an a portrait iPad, for example, is a little disconcerting. 34 | 35 | --- 36 | 37 | #### Usage: 38 | 39 | 1. Include the tiny (~1.1KB minified and gzipped) mq.genie.js in the `` of your document 40 | 41 | 2. If you develop in Safari, Chrome/Blink prior to 29.0.1547.57 or Firefox's scrollbar-less RWD View, write your media queries as you always have. mqGenie will adjust them for every other browser as required. 42 | 43 | 3. If you use another browser (or have scrollbars enabled on Mac), subtract `mqGenie.width` from the browser's reported viewport width. You can use my [Viewport Genie bookmarklet](https://github.com/stowball/Viewport-Genie) to tell you the "actual" viewport size. 44 | 45 | 4. If you have media queries triggering events in JavaScript, such as with [enquire.js](http://wicky.nillia.ms/enquire.js/), use `adjustMediaQuery(mq-string)` as opposed to `mq-string` 46 | 47 | --- 48 | 49 | Copyright (c) 2014 Matt Stow 50 | Licensed under the MIT license (see LICENSE for details) 51 | Minified version created with UglifyJS (http://jscompress.com/) 52 | -------------------------------------------------------------------------------- /mq.genie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * mqGenie v0.5.0 3 | * 4 | * Adjusts CSS media queries in browsers that include the scrollbar's width in the viewport width so they fire at the intended size 5 | * 6 | * Returns the mqGenie object containing .adjusted, .width & fontSize for use in re-calculating media queries in JavaScript with mqAdjust(string) 7 | * 8 | * Copyright (c) 2014 Matt Stow 9 | * 10 | * http://mattstow.com 11 | * 12 | * Licensed under the MIT license 13 | */ 14 | ;(function(window, document) { 15 | if (!document.addEventListener) { 16 | window.mqGenie = { 17 | adjustMediaQuery: function(mediaQuery) { 18 | return mediaQuery; 19 | } 20 | } 21 | 22 | return; 23 | } 24 | 25 | function processRules(stylesheet, processor) { 26 | var rules = stylesheet.cssRules ? stylesheet.cssRules : stylesheet.media, 27 | rule, 28 | processed = [], 29 | i = 0, 30 | length = rules.length; 31 | 32 | for (i; i < length; i ++) { 33 | rule = rules[i]; 34 | 35 | if (processor(rule)) 36 | processed.push(rule); 37 | } 38 | 39 | return processed; 40 | } 41 | 42 | function getMediaQueries(stylesheet) { 43 | return processRules(stylesheet, function (rule) { 44 | return rule.constructor === CSSMediaRule; 45 | }); 46 | } 47 | 48 | function sameOrigin(url) { 49 | var loc = window.location, 50 | a = document.createElement('a'); 51 | 52 | a.href = url; 53 | 54 | return a.hostname === loc.hostname && a.protocol === loc.protocol; 55 | } 56 | 57 | function isInline(stylesheet) { 58 | return stylesheet.ownerNode.constructor === HTMLStyleElement; 59 | } 60 | 61 | function isValidExternal(stylesheet) { 62 | return stylesheet.href && sameOrigin(stylesheet.href); 63 | } 64 | 65 | function getStylesheets() { 66 | var sheets = document.styleSheets, 67 | sheet, 68 | length = sheets.length, 69 | i = 0, 70 | valid = []; 71 | 72 | for (i; i < length; i++) { 73 | sheet = sheets[i]; 74 | 75 | if (isValidExternal(sheet) || isInline(sheet)) 76 | valid.push(sheet); 77 | } 78 | 79 | return valid; 80 | } 81 | 82 | document.addEventListener('DOMContentLoaded', function() { 83 | window.mqGenie = (function() { 84 | var html = document.documentElement; 85 | 86 | html.style.overflowY = 'scroll'; 87 | 88 | var width = window.innerWidth - html.clientWidth, 89 | props = { 90 | adjusted: width > 0, 91 | fontSize: parseFloat(window.getComputedStyle(html).getPropertyValue('font-size')), 92 | width: width, 93 | adjustMediaQuery: function(mediaQuery) { 94 | if (!mqGenie.adjusted) 95 | return mediaQuery; 96 | 97 | var mq = mediaQuery.replace(/\d+px/gi, function(c) { 98 | return parseInt(c, 10) + mqGenie.width + 'px'; 99 | }); 100 | 101 | mq = mq.replace(/\d.+?em/gi, function(c) { 102 | return ((parseFloat(c) * mqGenie.fontSize) + mqGenie.width) / mqGenie.fontSize + 'em'; 103 | }); 104 | 105 | return mq; 106 | } 107 | }; 108 | 109 | if (props.adjusted) { 110 | if ('WebkitAppearance' in html.style) { 111 | var chromeRX = /Chrome\/(\d*?\.\d*?\.\d*?\.\d*?)\s/g, 112 | chrome = navigator.userAgent.match(chromeRX), 113 | chromeVersion; 114 | 115 | if (chrome) { 116 | chrome = chrome[0].replace(chromeRX, '$1'); 117 | chromeVersion = chrome.split('.'); 118 | chromeVersion[0] = parseInt(chromeVersion[0]); 119 | chromeVersion[2] = parseInt(chromeVersion[2]); 120 | chromeVersion[3] = parseInt(chromeVersion[3]); 121 | 122 | if (chromeVersion[0] <= 29) { 123 | if (chromeVersion[0] === 29 && chromeVersion[2] < 1548 && chromeVersion[3] < 57) { 124 | props.adjusted = false; 125 | } 126 | else if (chromeVersion[0] < 29) { 127 | props.adjusted = false; 128 | } 129 | } 130 | } 131 | else { 132 | props.adjusted = false; 133 | } 134 | 135 | if (!props.adjusted) 136 | return props; 137 | } 138 | 139 | var stylesheets = getStylesheets(), 140 | stylesheetsLength = stylesheets.length, 141 | i = 0, 142 | mediaQueries, 143 | mediaQueriesLength; 144 | 145 | for (i; i < stylesheetsLength; i++) { 146 | mediaQueries = getMediaQueries(stylesheets[i]); 147 | mediaQueriesLength = mediaQueries.length; 148 | 149 | for (var j = 0; j < mediaQueriesLength; j++) { 150 | mediaQueries[j].media.mediaText = mediaQueries[j].media.mediaText.replace(/m(in|ax)-width:\s*(\d|\.)+(px|em)/gi, function(strA) { 151 | if (strA.match('px')) { 152 | return strA.replace(/\d+px/gi, function(strB) { 153 | return parseInt(strB, 10) + props.width + 'px'; 154 | }); 155 | } 156 | else { 157 | return strA.replace(/\d.+?em/gi, function(strB) { 158 | return ((parseFloat(strB) * props.fontSize) + props.width) / props.fontSize + 'em'; 159 | }); 160 | } 161 | }); 162 | } 163 | } 164 | } 165 | 166 | return props; 167 | })(); 168 | }); 169 | })(window, document); --------------------------------------------------------------------------------