├── .gitignore
├── Makefile
├── README.md
├── detect-zoom.js
├── detect-zoom.min.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Python files
2 | *.pyc
3 |
4 | # Folder view configuration files
5 | .DS_Store
6 | Desktop.ini
7 |
8 | # Thumbnail cache files
9 | ._*
10 | Thumbs.db
11 |
12 | # Files that might appear on external disks
13 | .Spotlight-V100
14 | .Trashes
15 |
16 | # IntelliJ
17 | *.iml
18 | *.ipr
19 | *.iws
20 | .idea
21 |
22 | # npm
23 | node_modules
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | UGLIFY=./node_modules/uglify-js/bin/uglifyjs
2 |
3 | detect-zoom.min.js: detect-zoom.js
4 | $(UGLIFY) detect-zoom.js -c > detect-zoom.min.js
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Cross Browser Zoom and Pixel Ratio Detector
2 | ======
3 | ------
4 |
5 | ### READ THIS: Detect-zoom is currently unusable for desktop
6 |
7 | Last update: Aug 7 2013
8 |
9 | **In the past few months both Mozilla and Google made some changes to their browsers that make it almost impossible to do
10 | what detect-zoom is here to do:**
11 |
12 | #### Firefox
13 | On *Firefox 18* Mozilla changes the `devicePixelRatio` value on manual zoom (cmd/ctrl +/-), making it impossible
14 | to know whether the browser is in zoom mode or is it a retina device, ignoring what the word DEVICE represents.
15 | I personally believe someone there refuses to admit this is a mistake and revert this decision.
16 |
17 | #### Chrome
18 | On *Chrome 27* (Meaning WebKit and Blink) `webkitTextSizeAdjust` was deprecated on desktops versions of the browser.
19 | This was the only bullet proof way to detect zoom in desktop chrome that I am aware of.
20 | There are couple of other ways, but they don't cover all the bases - one uses SVG but is not working in iFrames, the other
21 | uses window.inner/outerWidth and is not working when there is a sidebar or the DevTools are open on the side.
22 |
23 | ### Other Known issues:
24 | * In some multi-monitor enviroments where each monitor has a different 'pixel aspect ratio' windows that span accross both monitors might return false pixelAspectRatio values.
25 |
26 | What is this for?
27 | ------
28 | Detecting the browser zoom level and device pixel ratio relative to the zoom level.
29 |
30 | It can be used to show higher-resolution `canvas` or `img` when necessary,
31 | to warn users that your site's layout will be broken in their current zoom level,
32 | and much more.
33 | Personally I'm maintaining it to use Detect-zoom in [Wix.com](http://wix.com)'s editor to warn users
34 | that their browser is in zoom mode before saving important changes to their website.
35 |
36 | What happend to @yonran?
37 | ------
38 | Don't worry, he is well.
39 | As of January 2013 [@yonran](https://github.com/yonran) stopped maintaining his source of detect-zoom, and transferred the repository to me.
40 | If you are looking to update previous versions note that there were some breaking changes
41 |
42 | * **Major Changes from the latest yonran version:**
43 | * `DetectZoom` object name changed to `detectZoom`
44 | * `DetectZoom.ratio()` is no longer publicly accessible
45 | * Supported browsers: IE8+, FF4+, modern Webkit, mobile Webkit, Opera 11.1+
46 | * *IE6, IE7, FF 3.6 and Opera 10.x are no longer supported*
47 | * Added support to be loaded as an AMD and CommonJS module
48 |
49 | Live Example
50 | ------
51 | See the Live Example section in
52 | http://tombigel.github.com/detect-zoom/
53 |
54 | Usage
55 | ------
56 | **Detect-zoom has only two public functions:**
57 | * `zoom()` Returns the zoom level of the user's browser using Javascript.
58 | * `device()` Returns the device pixel ratio multiplied by the zoom level (Read [more about devicePixelRatio](http://www.quirksmode.org/blog/archives/2012/07/more_about_devi.html) at QuirksMode)
59 |
60 | ```html
61 |
62 |
68 | ```
69 |
70 | **AMD Usage**
71 |
72 | ```javascript
73 | require(['detect-zoom'], function(detectZoom){
74 | var zoom = detectZoom.zoom();
75 | });
76 | ```
77 |
78 | **Installing with NPM**
79 |
80 | ```bash
81 | > npm install detect-zoom
82 | ```
83 |
84 | Changelog
85 | ------
86 |
87 | 2013/4/01
88 | * Changed WebKit detection from deprecated webkitTextSizeAdjust to webkitMarquee
89 | * Changed WebKitMobile detection from unreliable 'ontouchstart' event (can be triggered on desktops too) to 'orientation' property that is hopefully more reliable
90 | * Minor version bump to 1.0.4
91 |
92 | 2013/3/29
93 | * Added package.json (thanks [@TCampaigne](https://github.com/TCampaigne))
94 | * Some documentation fixes
95 | * Added detect-zoom to npm package manager (again, thanks [@TCampaigne](https://github.com/TCampaigne))
96 |
97 | 2013/2/25
98 | * Fixed a missing 'else' between ie8 and ie10 detection
99 | * Minor version bump to 1.0.2
100 |
101 | 2013/2/15
102 | * Added a fix for IE10 Metro (or whatever MS calls it these days..) by [@stefanvanburen](https://github.com/stefanvanburen)
103 | * Minor version bump to 1.0.1
104 | * Added minimized version
105 |
106 | 2013/2/05
107 | * Merged a pull request that fixed zoom on IE being returned X100 (thanks [@kreymerman](https://github.com/kreymerman))
108 | * Refactored the code some more, changed some function names
109 | * Browser dependent main function is created only on initialization (thanks [@jsmaker](https://github.com/jsmaker))
110 | * _Open Issue: Firefox returns `zoom` and `devicePixelRatio` the same. Still looking for a solution here._
111 | * Started versioning - this is version 1.0.0
112 |
113 | 2013/1/27
114 | * Added a fix to Mozilla's (Broken as I see it - https://bugzilla.mozilla.org/show_bug.cgi?id=809788)
115 | implementation of window.devicePixel starting Firefox 18
116 |
117 | 2013/1/26
118 | * Repository moved here
119 | * Refactored most of the code
120 | * Removed support for older browsers
121 | * Added support for AMD and CommonJS
122 |
123 |
124 | Help Needed
125 | ------
126 |
127 | ***Detect-zoom is not complete, many parts of the code are 6 to 12 months old and I'm still reviewing them
128 | I need help testing different browsers, finding better ways to measure zoom on problematic browsers (ahm.. Firefox.. ahm)
129 | patches are more than welcome.***
130 |
131 |
132 | License
133 | ------
134 |
135 | Detect-zoom is dual-licensed under the [WTFPL](http://www.wtfpl.net/about/) and [MIT](http://opensource.org/licenses/MIT) license, at the recipient's choice.
136 |
--------------------------------------------------------------------------------
/detect-zoom.js:
--------------------------------------------------------------------------------
1 | /* Detect-zoom
2 | * -----------
3 | * Cross Browser Zoom and Pixel Ratio Detector
4 | * Version 1.0.4 | Apr 1 2013
5 | * dual-licensed under the WTFPL and MIT license
6 | * Maintained by https://github/tombigel
7 | * Original developer https://github.com/yonran
8 | */
9 |
10 | //AMD and CommonJS initialization copied from https://github.com/zohararad/audio5js
11 | (function (root, ns, factory) {
12 | "use strict";
13 |
14 | if (typeof (module) !== 'undefined' && module.exports) { // CommonJS
15 | module.exports = factory(ns, root);
16 | } else if (typeof (define) === 'function' && define.amd) { // AMD
17 | define("detect-zoom", function () {
18 | return factory(ns, root);
19 | });
20 | } else {
21 | root[ns] = factory(ns, root);
22 | }
23 |
24 | }(window, 'detectZoom', function () {
25 |
26 | /**
27 | * Use devicePixelRatio if supported by the browser
28 | * @return {Number}
29 | * @private
30 | */
31 | var devicePixelRatio = function () {
32 | return window.devicePixelRatio || 1;
33 | };
34 |
35 | /**
36 | * Fallback function to set default values
37 | * @return {Object}
38 | * @private
39 | */
40 | var fallback = function () {
41 | return {
42 | zoom: 1,
43 | devicePxPerCssPx: 1
44 | };
45 | };
46 | /**
47 | * IE 8 and 9: no trick needed!
48 | * TODO: Test on IE10 and Windows 8 RT
49 | * @return {Object}
50 | * @private
51 | **/
52 | var ie8 = function () {
53 | var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100) / 100;
54 | return {
55 | zoom: zoom,
56 | devicePxPerCssPx: zoom * devicePixelRatio()
57 | };
58 | };
59 |
60 | /**
61 | * For IE10 we need to change our technique again...
62 | * thanks https://github.com/stefanvanburen
63 | * @return {Object}
64 | * @private
65 | */
66 | var ie10 = function () {
67 | var zoom = Math.round((document.documentElement.offsetHeight / window.innerHeight) * 100) / 100;
68 | return {
69 | zoom: zoom,
70 | devicePxPerCssPx: zoom * devicePixelRatio()
71 | };
72 | };
73 |
74 | /**
75 | * For chrome
76 | *
77 | */
78 | var chrome = function()
79 | {
80 | var zoom = Math.round(((window.outerWidth) / window.innerWidth)*100) / 100;
81 | return {
82 | zoom: zoom,
83 | devicePxPerCssPx: zoom * devicePixelRatio()
84 | };
85 | }
86 |
87 | /**
88 | * For safari (same as chrome)
89 | *
90 | */
91 | var safari= function()
92 | {
93 | var zoom = Math.round(((document.documentElement.clientWidth) / window.innerWidth)*100) / 100;
94 | return {
95 | zoom: zoom,
96 | devicePxPerCssPx: zoom * devicePixelRatio()
97 | };
98 | }
99 |
100 |
101 | /**
102 | * Mobile WebKit
103 | * the trick: window.innerWIdth is in CSS pixels, while
104 | * screen.width and screen.height are in system pixels.
105 | * And there are no scrollbars to mess up the measurement.
106 | * @return {Object}
107 | * @private
108 | */
109 | var webkitMobile = function () {
110 | var deviceWidth = (Math.abs(window.orientation) == 90) ? screen.height : screen.width;
111 | var zoom = deviceWidth / window.innerWidth;
112 | return {
113 | zoom: zoom,
114 | devicePxPerCssPx: zoom * devicePixelRatio()
115 | };
116 | };
117 |
118 | /**
119 | * Desktop Webkit
120 | * the trick: an element's clientHeight is in CSS pixels, while you can
121 | * set its line-height in system pixels using font-size and
122 | * -webkit-text-size-adjust:none.
123 | * device-pixel-ratio: http://www.webkit.org/blog/55/high-dpi-web-sites/
124 | *
125 | * Previous trick (used before http://trac.webkit.org/changeset/100847):
126 | * documentElement.scrollWidth is in CSS pixels, while
127 | * document.width was in system pixels. Note that this is the
128 | * layout width of the document, which is slightly different from viewport
129 | * because document width does not include scrollbars and might be wider
130 | * due to big elements.
131 | * @return {Object}
132 | * @private
133 | */
134 | var webkit = function () {
135 | var important = function (str) {
136 | return str.replace(/;/g, " !important;");
137 | };
138 |
139 | var div = document.createElement('div');
140 | div.innerHTML = "1
2
3
4
5
6
7
8
9
0";
141 | div.setAttribute('style', important('font: 100px/1em sans-serif; -webkit-text-size-adjust: none; text-size-adjust: none; height: auto; width: 1em; padding: 0; overflow: visible;'));
142 |
143 | // The container exists so that the div will be laid out in its own flow
144 | // while not impacting the layout, viewport size, or display of the
145 | // webpage as a whole.
146 | // Add !important and relevant CSS rule resets
147 | // so that other rules cannot affect the results.
148 | var container = document.createElement('div');
149 | container.setAttribute('style', important('width:0; height:0; overflow:hidden; visibility:hidden; position: absolute;'));
150 | container.appendChild(div);
151 |
152 | document.body.appendChild(container);
153 | var zoom = 1000 / div.clientHeight;
154 | zoom = Math.round(zoom * 100) / 100;
155 | document.body.removeChild(container);
156 |
157 | return{
158 | zoom: zoom,
159 | devicePxPerCssPx: zoom * devicePixelRatio()
160 | };
161 | };
162 |
163 | /**
164 | * no real trick; device-pixel-ratio is the ratio of device dpi / css dpi.
165 | * (Note that this is a different interpretation than Webkit's device
166 | * pixel ratio, which is the ratio device dpi / system dpi).
167 | *
168 | * Also, for Mozilla, there is no difference between the zoom factor and the device ratio.
169 | *
170 | * @return {Object}
171 | * @private
172 | */
173 | var firefox4 = function () {
174 | var zoom = mediaQueryBinarySearch('min--moz-device-pixel-ratio', '', 0, 10, 20, 0.0001);
175 | zoom = Math.round(zoom * 100) / 100;
176 | return {
177 | zoom: zoom,
178 | devicePxPerCssPx: zoom
179 | };
180 | };
181 |
182 | /**
183 | * Firefox 18.x
184 | * Mozilla added support for devicePixelRatio to Firefox 18,
185 | * but it is affected by the zoom level, so, like in older
186 | * Firefox we can't tell if we are in zoom mode or in a device
187 | * with a different pixel ratio
188 | * @return {Object}
189 | * @private
190 | */
191 | var firefox18 = function () {
192 | return {
193 | zoom: firefox4().zoom,
194 | devicePxPerCssPx: devicePixelRatio()
195 | };
196 | };
197 |
198 | /**
199 | * works starting Opera 11.11
200 | * the trick: outerWidth is the viewport width including scrollbars in
201 | * system px, while innerWidth is the viewport width including scrollbars
202 | * in CSS px
203 | * @return {Object}
204 | * @private
205 | */
206 | var opera11 = function () {
207 | var zoom = window.top.outerWidth / window.top.innerWidth;
208 | zoom = Math.round(zoom * 100) / 100;
209 | return {
210 | zoom: zoom,
211 | devicePxPerCssPx: zoom * devicePixelRatio()
212 | };
213 | };
214 |
215 | /**
216 | * Use a binary search through media queries to find zoom level in Firefox
217 | * @param property
218 | * @param unit
219 | * @param a
220 | * @param b
221 | * @param maxIter
222 | * @param epsilon
223 | * @return {Number}
224 | */
225 | var mediaQueryBinarySearch = function (property, unit, a, b, maxIter, epsilon) {
226 | var matchMedia;
227 | var head, style, div;
228 | if (window.matchMedia) {
229 | matchMedia = window.matchMedia;
230 | } else {
231 | head = document.getElementsByTagName('head')[0];
232 | style = document.createElement('style');
233 | head.appendChild(style);
234 |
235 | div = document.createElement('div');
236 | div.className = 'mediaQueryBinarySearch';
237 | div.style.display = 'none';
238 | document.body.appendChild(div);
239 |
240 | matchMedia = function (query) {
241 | style.sheet.insertRule('@media ' + query + '{.mediaQueryBinarySearch ' + '{text-decoration: underline} }', 0);
242 | var matched = getComputedStyle(div, null).textDecoration == 'underline';
243 | style.sheet.deleteRule(0);
244 | return {matches: matched};
245 | };
246 | }
247 | var ratio = binarySearch(a, b, maxIter);
248 | if (div) {
249 | head.removeChild(style);
250 | document.body.removeChild(div);
251 | }
252 | return ratio;
253 |
254 | function binarySearch(a, b, maxIter) {
255 | var mid = (a + b) / 2;
256 | if (maxIter <= 0 || b - a < epsilon) {
257 | return mid;
258 | }
259 | var query = "(" + property + ":" + mid + unit + ")";
260 | if (matchMedia(query).matches) {
261 | return binarySearch(mid, b, maxIter - 1);
262 | } else {
263 | return binarySearch(a, mid, maxIter - 1);
264 | }
265 | }
266 | };
267 |
268 | /**
269 | * Generate detection function
270 | * @private
271 | */
272 | var detectFunction = (function () {
273 | var func = fallback;
274 | //IE8+
275 | if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) {
276 | func = ie8;
277 | }
278 | // IE10+ / Touch
279 | else if (window.navigator.msMaxTouchPoints) {
280 | func = ie10;
281 | }
282 | //chrome
283 | else if(!!window.chrome && !(!!window.opera || navigator.userAgent.indexOf(' Opera') >= 0)){
284 | func = chrome;
285 | }
286 | //safari
287 | else if(Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0){
288 | func = safari;
289 | }
290 | //Mobile Webkit
291 | else if ('orientation' in window && 'webkitRequestAnimationFrame' in window) {
292 | func = webkitMobile;
293 | }
294 | //WebKit
295 | else if ('webkitRequestAnimationFrame' in window) {
296 | func = webkit;
297 | }
298 | //Opera
299 | else if (navigator.userAgent.indexOf('Opera') >= 0) {
300 | func = opera11;
301 | }
302 | //Last one is Firefox
303 | //FF 18.x
304 | else if (window.devicePixelRatio) {
305 | func = firefox18;
306 | }
307 | //FF 4.0 - 17.x
308 | else if (firefox4().zoom > 0.001) {
309 | func = firefox4;
310 | }
311 |
312 | return func;
313 | }());
314 |
315 |
316 | return ({
317 |
318 | /**
319 | * Ratios.zoom shorthand
320 | * @return {Number} Zoom level
321 | */
322 | zoom: function () {
323 | return detectFunction().zoom;
324 | },
325 |
326 | /**
327 | * Ratios.devicePxPerCssPx shorthand
328 | * @return {Number} devicePxPerCssPx level
329 | */
330 | device: function () {
331 | return detectFunction().devicePxPerCssPx;
332 | }
333 | });
334 | }));
335 |
--------------------------------------------------------------------------------
/detect-zoom.min.js:
--------------------------------------------------------------------------------
1 | (function(root,ns,factory){"use strict";"undefined"!=typeof module&&module.exports?module.exports=factory(ns,root):"function"==typeof define&&define.amd?define("detect-zoom",function(){return factory(ns,root)}):root[ns]=factory(ns,root)})(window,"detectZoom",function(){var devicePixelRatio=function(){return window.devicePixelRatio||1},fallback=function(){return{zoom:1,devicePxPerCssPx:1}},ie8=function(){var zoom=Math.round(100*(screen.deviceXDPI/screen.logicalXDPI))/100;return{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},ie10=function(){var zoom=Math.round(100*(document.documentElement.offsetHeight/window.innerHeight))/100;return{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},webkitMobile=function(){var deviceWidth=90==Math.abs(window.orientation)?screen.height:screen.width,zoom=deviceWidth/window.innerWidth;return{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},webkit=function(){var important=function(str){return str.replace(/;/g," !important;")},div=document.createElement("div");div.innerHTML="1
2
3
4
5
6
7
8
9
0",div.setAttribute("style",important("font: 100px/1em sans-serif; -webkit-text-size-adjust: none; text-size-adjust: none; height: auto; width: 1em; padding: 0; overflow: visible;"));var container=document.createElement("div");container.setAttribute("style",important("width:0; height:0; overflow:hidden; visibility:hidden; position: absolute;")),container.appendChild(div),document.body.appendChild(container);var zoom=1e3/div.clientHeight;return zoom=Math.round(100*zoom)/100,document.body.removeChild(container),{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},firefox4=function(){var zoom=mediaQueryBinarySearch("min--moz-device-pixel-ratio","",0,10,20,1e-4);return zoom=Math.round(100*zoom)/100,{zoom:zoom,devicePxPerCssPx:zoom}},firefox18=function(){return{zoom:firefox4().zoom,devicePxPerCssPx:devicePixelRatio()}},opera11=function(){var zoom=window.top.outerWidth/window.top.innerWidth;return zoom=Math.round(100*zoom)/100,{zoom:zoom,devicePxPerCssPx:zoom*devicePixelRatio()}},mediaQueryBinarySearch=function(property,unit,a,b,maxIter,epsilon){function binarySearch(a,b,maxIter){var mid=(a+b)/2;if(0>=maxIter||epsilon>b-a)return mid;var query="("+property+":"+mid+unit+")";return matchMedia(query).matches?binarySearch(mid,b,maxIter-1):binarySearch(a,mid,maxIter-1)}var matchMedia,head,style,div;window.matchMedia?matchMedia=window.matchMedia:(head=document.getElementsByTagName("head")[0],style=document.createElement("style"),head.appendChild(style),div=document.createElement("div"),div.className="mediaQueryBinarySearch",div.style.display="none",document.body.appendChild(div),matchMedia=function(query){style.sheet.insertRule("@media "+query+"{.mediaQueryBinarySearch "+"{text-decoration: underline} }",0);var matched="underline"==getComputedStyle(div,null).textDecoration;return style.sheet.deleteRule(0),{matches:matched}});var ratio=binarySearch(a,b,maxIter);return div&&(head.removeChild(style),document.body.removeChild(div)),ratio},detectFunction=function(){var func=fallback;return isNaN(screen.logicalXDPI)||isNaN(screen.systemXDPI)?window.navigator.msMaxTouchPoints?func=ie10:"orientation"in window&&"string"==typeof document.body.style.webkitMarquee?func=webkitMobile:"string"==typeof document.body.style.webkitMarquee?func=webkit:navigator.userAgent.indexOf("Opera")>=0?func=opera11:window.devicePixelRatio?func=firefox18:firefox4().zoom>.001&&(func=firefox4):func=ie8,func}();return{zoom:function(){return detectFunction().zoom},device:function(){return detectFunction().devicePxPerCssPx}}});
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "detect-zoom",
3 | "version": "1.0.4",
4 | "description": "Cross Browser Zoom and Pixel Ratio Detector",
5 | "main": "detect-zoom.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/tombigel/detect-zoom.git"
12 | },
13 | "keywords": [
14 | "browser",
15 | "zoom",
16 | "compatibility",
17 | "pixel",
18 | "ratio",
19 | "retina"
20 | ],
21 | "author": "Yonathan Randolph",
22 | "contributors": [
23 | "Tom Bigelajzen "
24 | ],
25 | "license": "MIT",
26 | "readmeFilename": "README.md",
27 | "gitHead": "6eaf3107a6913a4f7b93665ba6d5bc16cdf0f3ab",
28 | "devDependencies": {
29 | "uglify-js": "~3.6.6"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------