35

I am currently using JavaScript for mobile device detection on my website, this then allows me to serve different content for mobile or desktop users.

Currently I use window.devicePixelRatio and screen.width to work out if the user if on a mobile device or not, like so:

var isMobileScreenWidth = ((screen.width / window.devicePixelRatio) < 768)

768px is the point at which we define mobile or desktop so 767 and below is mobile and 768 and above is desktop.

This works perfectly, but I have recently come across an issue with Firefox, when Firefox is zoomed in and out it changes the window.devicePixelRatio, so:

zoom = 30%, window.devicePixelRatio = 0.3
zoom = 100%, window.devicePixelRatio = 1.0
zoom = 300%, window.devicePixelRatio = 3.0

This now causes me a problem because any users which have their browser zoomed in on Firefox get the mobile version of the site.

I was wondering if anyone knew of a different or better way of getting the pixel density which is separate from desktop browsers.

I do use a small amount of User Agent detection as well but because it is a massive job to keep up with the constantly changing list of mobile user agents it is not possible for me to depend on both the screen resolution and user agent at the same time.

If anyone has any ideas about this and can help that would be awesome.

UPDATE:

I have just come across this:

window.screen.availWidth / document.documentElement.clientWidth

This quick bit of math is suggested in this post:

window.devicePixelRatio does not work in IE 10 Mobile?

I have tested it and it work in Firefox, and solves my problem, but, unfortunately now causes a problem in Chrome, so:

Chrome zoom = 100%,
window.devicePixelRatio = 1.0,
window.screen.availWidth / document.documentElement.clientWidth = 3.0

I cannot seem to find a solid solution which works for everything.

Community
  • 1
  • 1
lukehillonline
  • 2,430
  • 2
  • 32
  • 48
  • You should not use device pixel ratio to check mobile: some non mobile device are "retina" (iPad, mac book pro...) and will show mobile styles. use it only for HD images. – Yukulélé Apr 08 '14 at 10:36
  • @Yukulélé I see your concern. This is why I was dividing `screen.width` by `window.devicePixelRatio` then checking if the result of this is less than `768px` (the width of an iPad). Doing that would have prevented most tablet devices from seeing the mobile style. Although some tablet devices like the Kindle Fire did show mobile styles as they are smaller tablets. I did not consider this a concern as the mobile styles displayed quite well for these devices. – lukehillonline Apr 08 '14 at 12:20

2 Answers2

24

You should leverage the manufacturer's hint via the <meta name="viewport" content="width=device-width"/> or @-ms-viewport {width:device-width} feature. It basically exposes the default zoom the device manufacturer considers optimal given the pixel density of the screen. After you do that, when you call window.innerWidth it will give you what your original equation was intended for but without relying on a measurement of pixel density.

Avoid relying on window.devicePixelRatio for anything. Its meaning and the value it returns is currently in a state of flux and anything you make right now based around it will most likely break very soon.

Note: Meta viewport only works on Android, iOS, and Windows Phone 8. @-ms-viewport only works (properly) on IE10 Metro and can interfere with proper Meta viewport behavior on Windows Phone 8.

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Amann Malik
  • 708
  • 5
  • 8
  • 2
    Thank you very much for coming back to me on this I have been completely lost on a solution for this. So if you do not think that using `window.devicePixelRatio` is a safe option what would you use instead? I have looked at many options for detecting mobile devices, unfortunately a server side solution is not possible for me so JavaScript is my only option. – lukehillonline Jul 05 '13 at 13:23
  • 1
    one idea is to force vertical scrollbars to appear in css, then compare `document.documentElement.clientWidth` to `window.innerWidth`. They should be equal in mobile environments (no pinch zoom) but not in windowed desktop environments. – Amann Malik Jul 07 '13 at 23:18
  • 1
    I downvoted this because this doesn't allow a developer full control over the dimensions of an HTML app (nor does there seem to exist any such mechanism). It's not your fault, but a limitation of web technology. There's no way to consistently calculate what 1 point truly measures in the real world, unlike with native development. – trusktr Jul 20 '14 at 20:51
  • 1
    Instead of window.innertWidth I would use document.documentElement.clientWidth http://www.quirksmode.org/mobile/tableViewport.html , also, devicePixelRatio is not supported by the Windows Phone 8.1 browser. – andreszs Feb 02 '15 at 02:56
10

There is a Generic Solution to get device Pixel Ratio

Next code uses window.devicePixelRatio as a starting point BUT also has a fallback based on window.matchMedia() Web API.

The browser support for both those features is almost perfect, so this should work great for most of use cases.

Here is a function that retrieves this information, originally written by PatrickJS and published as a GitHub Gist:

function getDevicePixelRatio() {
    var mediaQuery;
    var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
    if (window.devicePixelRatio !== undefined && !is_firefox) {
        return window.devicePixelRatio;
    } else if (window.matchMedia) {
        mediaQuery = "(-webkit-min-device-pixel-ratio: 1.5),\
          (min--moz-device-pixel-ratio: 1.5),\
          (-o-min-device-pixel-ratio: 3/2),\
          (min-resolution: 1.5dppx)";
        if (window.matchMedia(mediaQuery).matches) {
            return 1.5;
        }
        mediaQuery = "(-webkit-min-device-pixel-ratio: 2),\
          (min--moz-device-pixel-ratio: 2),\
          (-o-min-device-pixel-ratio: 2/1),\
          (min-resolution: 2dppx)";
        if (window.matchMedia(mediaQuery).matches) {
            return 2;
        }
        mediaQuery = "(-webkit-min-device-pixel-ratio: 0.75),\
          (min--moz-device-pixel-ratio: 0.75),\
          (-o-min-device-pixel-ratio: 3/4),\
          (min-resolution: 0.75dppx)";
        if (window.matchMedia(mediaQuery).matches) {
            return 0.7;
        }
    } else {
        return 1;
    }
}

Useful links: MDN - window.devicePixelRatio, MDN - Window.matchMedia()

CanIUse: window.devicePixelRatio, Window.matchMedia()

Andrii Verbytskyi
  • 7,155
  • 3
  • 47
  • 38
  • 6
    The best approach in 2019 is `return window.devicePixelRatio || 1;` – Finesse Mar 13 '19 at 04:50
  • 1
    @Finesse doesn't reliably work for Safari 12.x (get the same value regardless of zoom level). – onassar May 09 '19 at 16:57
  • @onassar, the code from the answer and the `matchMedia` solution in particular give the same result in Safari 12.1 so they're not more precise – Finesse May 11 '19 at 02:50
  • 1
    Sorry? I'm saying that in Safari 12.x, running that command when the user has zoomed in/out does not return a new value. It returns the same value each time unfort. – onassar May 12 '19 at 14:27