Detecting the system DPI/PPI from JS/CSS?

66,969

Solution 1

<div id='testdiv' style='height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;'></div>
<script type='text/javascript'>
  var devicePixelRatio = window.devicePixelRatio || 1;
  dpi_x = document.getElementById('testdiv').offsetWidth * devicePixelRatio;
  dpi_y = document.getElementById('testdiv').offsetHeight * devicePixelRatio;
  
  console.log(dpi_x, dpi_y);
</script>

grabbed from here http://www.infobyip.com/detectmonitordpi.php. Works on mobile devices! (android 4.2.2 tested)

Solution 2

I came up with a way that doesn't require the DOM... at all

The DOM can be messy, requiring you to append stuff to the body without knowing what stuff is going on with width: x !important in your stylesheet. You would also have to wait for the DOM to be ready to use...

/**
 * Binary search for a max value without knowing the exact value, only that it can be under or over
 * It dose not test every number but instead looks for 1,2,4,8,16,32,64,128,96,95 to figure out that
 * you thought about #96 from 0-infinity
 *
 * @example findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
 * @author Jimmy Wärting
 * @see {@link https://stackoverflow.com/a/35941703/1008999}
 * @param {function} fn       The function to run the test on (should return truthy or falsy values)
 * @param {number}   start=1  Where to start looking from
 * @param {function} _        (private)
 * @returns {number}          Intenger
 */
function findFirstPositive (f,b=1,d=(e,g,c)=>g<e?-1:0<f(c=e+g>>>1)?c==e||0>=f(c-1)?c:d(e,c-1):d(c+1,g)) {
  for (;0>=f(b);b<<=1);return d(b>>>1,b)|0
}

var dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)

console.log(dpi)

Solution 3

There is the resolution CSS media query — it allows you to limit CSS styles to specific resolutions:

However, it’s only supported by Firefox 3.5 and above, Opera 9 and above, and IE 9. Other browsers won’t apply your resolution-specific styles at all (although I haven’t checked non-desktop browsers).

Solution 4

Here is what works for me (but didn't test it on mobile phones):

<body><div id="ppitest" style="width:1in;visible:hidden;padding:0px"></div></body>

Then I put in the .js: screenPPI = document.getElementById('ppitest').offsetWidth;

This got me 96, which corresponds to my system's ppi.

Solution 5

I also needed to display the same image at the same size at different screen dpi but only for Windows IE. I used:

<img src="image.jpg" style="
    height:expression(scale(438, 192)); 
    width:expression(scale(270, 192))" />

function scale(x, dpi) {

    // dpi is for orignal dimensions of the image
    return x * screen.deviceXDPI/dpi;
}

In this case the original image width/height are 270 and 438 and the image was developed on 192dpi screen. screen.deviceXDPI is not defined in Chrome and the scale function would need to be updated to support browsers other than IE

Share:
66,969
Admin
Author by

Admin

Updated on March 22, 2021

Comments

  • Admin
    Admin over 3 years

    I'm working on a kind of unique app which needs to generate images at specific resolutions according to the device they are displayed on. So the output is different on a regular Windows browser (96ppi), iPhone (163ppi), Android G1 (180ppi), and other devices. I'm wondering if there's a way to detect this automatically.

    My initial research seems to say no. The only suggestion I've seen is to make an element whose width is specified as "1in" in CSS, then check its offsetWidth (see also How to access screen display’s DPI settings via javascript?). Makes sense, but iPhone is lying to me with that technique, saying it's 96ppi.

    Another approach might be to get the dimensions of the display in inches and then divide by the width in pixels, but I'm not sure how to do that either.

  • Pointy
    Pointy over 12 years
    This doesn't work on all devices. My Android phone reports 96dpi too.
  • mckamey
    mckamey over 11 years
    This doesn't seem to work on any devices. Everything seems to report 96ppi: codepen.io/anon/full/mrfvg
  • mistertodd
    mistertodd about 11 years
    DPI by definition is not the numberOfPixels / sizeOfMonitorInInches. DPI is defined such that 10 point text appears on your screen to be the same size as standard 10 point text in a book. In print, and typography, 1 inch = 72 points. The critical distinction is that people usually have their monitor's further away than they hold a book (33% farther, in fact). That is where Microsoft got the value value 96 dpi from: 72 * 33% = 96. Ideally you would have your monitor as far away from your face as you typically hold a book. But you don't, so that is why you have 96dpi.
  • Yukulélé
    Yukulélé about 10 years
    It SHOULD works, but unfortunately all browsers consider 1in = 96px and 1cm == 37.8px. Bug?
  • rbncrthms
    rbncrthms about 10 years
    That comment is full of errors. DPI is an abbreviation that means dots per inch. With a screen, you are usually provided with the size of the diagonal in inches. To calculate how many pixels lie on this diagonal, you must work out the square root of (x^2 + y^2) where x and y are the horizontal and vertical pixels. For example, for 1920x1080 on a 24" display, the DPI is approx 91.8. And 72*33% is approx 24; you mean 72*133%. Probably 96DPI was no more than the mean scale of monitors at the time, and has nothing to do with the distance you hold a book compared to that of a monitor.
  • nmz787
    nmz787 about 10 years
    Doesn't work, gives 96 on both my 1080p android smartphone as well as my laptop.
  • Takol
    Takol over 9 years
    No they cannot tell physical device diagonal and always show up 13.3" on all of my devices. You can calculate the "pixel per inch" by it but truly it just cannot detect the device size.
  • Rémi
    Rémi over 9 years
    Here is a playground to test it home.heeere.com/tech-ppi-aware-css-media-query.html
  • Mathew
    Mathew over 9 years
    Not a bug - "px" isn't screen pixels, it is a fixed length of 0.75 pt, which itself is 1/72th of an inch.
  • Andy
    Andy about 9 years
    the CSS spec defines 1 px as 1/96th of an inch, which is why your calculation will always return 96. That inch is not an inch on-screen, but takes the distance to your eye into account, meaning that it's different on different devices. The browser vendors need to set that factor to something meaningful, which is not always the case.
  • Frederik Krautwald
    Frederik Krautwald about 9 years
    @Takol It’s just the default value. You are meant to change it. Diagonals cannot be detected by JavaScript. See github.com/LeaVerou/dpi/issues/36
  • jrmgx
    jrmgx over 7 years
    This code works pretty well indeed, but is not readable at all
  • Supersharp
    Supersharp over 7 years
    Should the result be exact? My monitor documentation says 93 while your function says 96.
  • Endless
    Endless over 7 years
    This is definitely more readable. But the different in mine and yours are that mine only executed matchMedia 11 times to figure out that it's 96 where as your code tries every possible number between 56 and 2000
  • Endless
    Endless over 7 years
    The number should be exact. It makes assumption by doing n^2 upon till the function returns false and then works closer towards the number by taking the middle number. You can try it here: fiddle. If the function says it's 93 then it should be 93. Think there is something fundamental broken with the browser in that way browser don't know what the screen resolution is so it just assumes it's 96
  • GreySage
    GreySage about 7 years
    All you are doing is multiplying the constant 96 by the devicePixelRatio (which can be overridden). This is not a consistent way of getting the accurate ppi.
  • GreySage
    GreySage about 7 years
    This always returns 96, even on mobile devices. This falls victim to the classic blunder of not realizing that inch is defined at 96px.
  • MaxXx1313
    MaxXx1313 almost 7 years
    I can't figure out why you think so.
  • GreySage
    GreySage almost 7 years
    because the words that I typed have meanings? On all devices, 1in in css is defined to be 96px, so measuring a div with a width=1in in px will always give 96. THen you are just multiplying that (which again, will always be 96) by the devicePixelRatio which is not subject to standards or limitations, it could be anything and in particular anyone could overwrite it with whatever value they want. In short, this method isn't reliable.
  • MaxXx1313
    MaxXx1313 almost 7 years
    Actually, it doesn't work in firefox. Firefox itself looks weird on 4k display. In chrome and ie it forks perfectly fine.
  • Michael
    Michael over 6 years
    Wow, I can only imaging creating a style sheet listing every possible resolution in 1 or 0.1dpi range increments and inferring the closest actual screen resolution based on which style was applied...
  • Michael
    Michael over 6 years
    +1 for effort, but... both this code and the code linked by @eltomito claim my phone PPI is ~336 when it's actually 518. And they both incorrectly claim my Mac and PC (with have the same raw resolution but different screen sizes) both have a PPI of 96 when in fact the actual values are 102 and 157, respectively.
  • Michael
    Michael over 6 years
    This doesn't seem accurate at all... the answer by Endless appears to do a binary search with this query, yet across my devices the playground linked by @Rémi gives the same invalid results...
  • Michael
    Michael over 6 years
    @FrederikKrautwald And yet the question is "Detecting the system DPI/PPI from JS/CSS?" which this is not an answer to.
  • Eugen
    Eugen over 6 years
    this will not work on WebKit/Safari (iOS 11), it appears max-resolution is not supported there yet
  • Eugen
    Eugen over 6 years
    this will not work on WebKit/Safari (iOS 11), it appears max-resolution is not supported there yet
  • Frederik Krautwald
    Frederik Krautwald over 6 years
    @Michael What? I was replying to Takoi in using Lea Verou's tool as linked to in the answer. I didn't provide an answer to the SO question.
  • eltomito
    eltomito over 6 years
    Okay, so it seems the problem is that mobile browsers render sites into a hidden invisible viewport and then rescale them and display them on the actual screen. The DPI and dimensions the browser reveals to the javascript code and CSS are that of the viewport, not of the actual display (which IMHO means the DPI is totally useless). The dimensions of the viewport can be controlled by the <meta name="viewport"> tag. Read more about it on MDN: developer.mozilla.org/en-US/docs/Mozilla/Mobile/…
  • V. Rubinetti
    V. Rubinetti almost 4 years
    I had a similar idea, where you just keep testing @media screen and (resolution: 96dpi) and increasing dpi until you get a match. But testing it quickly on Chrome reveals that unfortunately, these CSS resolution @media queries also seem to scale up/down when the user zooms in/out, just like window.devicePixelRatio does. So unfortunately they are also useless.
  • V. Rubinetti
    V. Rubinetti almost 4 years
    This tool doesn't work. Open it in Chrome, zoom the page in, refresh, and you get a new value.
  • DUzun
    DUzun over 3 years
    Here is a nicer version: repl.it/@duzun/findDPIjs#script.js
  • Endless
    Endless over 3 years
    @DUzun i like the bit shifting part (left & right) instead of *2 and /2, looks like your dose a better job at finding #97, also more readability, local variables and no ternary operator. What's wrong with ternary operators? don't like them? :P
  • DUzun
    DUzun over 3 years
    @Endless Shifting gets ride of the fractional part, which is relevant for DPI. I like the ternary operator, only in simple expressions. Here I've replaced it for better readability and to avoid one call to matchMedia().
  • Yves Gurcan
    Yves Gurcan over 3 years
    Could you suggest an edit on the reply of the user instead of posting a new answer?
  • cdauth
    cdauth about 2 years
    This doesn't make any sense, the user agent doesn't contain any information about what screen I have attached to my device.
  • Avi Tshuva
    Avi Tshuva about 2 years
    i'm afraid not... The assumption in the code is that "in" represent a real, physical inch in the screen. It is not. Sadly.