Обнаружение системного DPI / PPI из JS / CSS?

Я работаю над своего рода уникальным приложением, которое должно генерировать изображения с определенными разрешениями в зависимости от устройства, на котором они отображаются. Таким образом, вывод отличается в обычном браузере Windows (96 пикселей на дюйм), iPhone (163 пикселей на дюйм), Android G1 (180 пикселей на дюйм) и других устройствах. Мне интересно, есть ли способ обнаружить это автоматически.

Мои первоначальные исследования, кажется, говорят нет. Единственное предложение, которое я видел, - это создать элемент, ширина которого указана как «1 дюйм» в CSS, а затем проверить его offsetWidth (см. Также Как получить доступ к настройкам разрешения экрана дисплея через JavaScript?). Это имеет смысл, но iPhone обманывает меня с помощью этой техники, говоря, что это 96 пикселей на дюйм.

Другой подход может заключаться в том, чтобы получить размеры дисплея в дюймах, а затем разделить их на ширину в пикселях, но я тоже не знаю, как это сделать.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
55
1
62 996
13

Ответы 13

Я думаю, что ваш лучший подход - объединить предложение изображения «сниффера» с матрицей известных DPI для устройств (через пользовательский агент и другие методы). Это не будет точно, и будет сложно поддерживать его, но, не зная больше о приложении, которое вы пытаетесь сделать, это лучшее предложение, которое я могу предложить.

DPI по определению привязан к физическому размеру дисплея. Таким образом, вы не сможете получить реальный DPI, не зная точно, какое оборудование стоит за ним.

Современные операционные системы согласовали общее значение для совместимых дисплеев: 96 точек на дюйм. Обидно, но это факт.

Вам придется полагаться на сниффинг, чтобы угадать реальный размер экрана, необходимый для вычисления разрешения (DPI = PixelSize / ScreenSize).

DPI по определению - это нетnumberOfPixels / sizeOfMonitorInInches. DPI определяется таким образом, что текст размером 10 пунктов отображается на экране и имеет тот же размер, что и стандартный текст в 10 пунктов в книге. В печати и типографии - 1 inch = 72 points. Важное различие состоит в том, что люди обычно держат монитор дальше, чем держат книгу (на самом деле, на 33% дальше). Вот откуда Microsoft взяла значение 96 dpi: 72 * 33% = 96. В идеале ваш монитор должен располагаться так далеко от лица, как вы обычно держите книгу. Но у вас этого нет, поэтому у вас 96 точек на дюйм.

Ian Boyd 22.06.2013 00:56

Этот комментарий полон ошибок. DPI - это аббревиатура, означающая точек на дюйм. С экраном вам обычно предоставляют размер диагонали в дюймах. Чтобы вычислить, сколько пикселей находится на этой диагонали, вы должны вычислить квадратный корень из (x ^ 2 + y ^ 2), где x и y - горизонтальные и вертикальные пиксели. Например, для 1920 x 1080 на 24-дюймовом дисплее значение DPI составляет примерно 91,8. 72 * 33% равно примерно 24; вы имеете в виду 72 * 133%. Вероятно, 96 точек на дюйм было не больше, чем средний масштаб мониторов в то время, и имел Ничего общего с расстоянием, на котором вы держите книгу, по сравнению с расстоянием от монитора.

davtom 23.05.2014 15:29

Вы больше ничего не можете сделать? Например, если вы создаете изображение для распознавания камерой (т.е. вы запускаете свою программу, проводите мобильным телефоном по камере, происходит волшебство), разве вы не можете использовать что-то независимое от размера?

Если это приложение будет развернуто в контролируемой среде, можете ли вы предоставить утилиту калибровки? (можно сделать что-нибудь простое, например, распечатать визитки с маленькой линейкой, использовать ее в процессе калибровки).

Мне также нужно было отображать одно и то же изображение того же размера и с разным разрешением экрана, но только для Windows IE. Я использовал:

<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;
}

В этом случае исходная ширина / высота изображения составляет 270 и 438, а изображение было проявлено на экране с разрешением 192 точек на дюйм. screen.deviceXDPI не определен в Chrome, и функцию масштабирования необходимо обновить для поддержки браузеров, отличных от IE.

Вот что у меня работает (но не тестировал на мобильных телефонах):

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

Затем я вставляю .js: screenPPI = document.getElementById('ppitest').offsetWidth;

Получилось 96, что соответствует ppi моей системы.

Это работает не на всех устройствах. Мой телефон Android тоже сообщает 96 точек на дюйм.

Pointy 29.01.2012 20:07

Похоже, это не работает ни на каких устройствах. Вроде все сообщает 96ppi: codepen.io/anon/full/mrfvg

mckamey 16.11.2012 22:46

Он ДОЛЖЕН работать, но, к сожалению, все браузеры считают, что 1 дюйм = 96 пикселей и 1 см == 37,8 пикселей. Ошибка?

Yukulélé 04.04.2014 15:02

Это не ошибка - «px» - это не пиксели экрана, это фиксированная длина 0,75 pt, которая сама по себе составляет 1/72 дюйма.

Mathew 20.03.2015 05:33

спецификация CSS определяет 1 пиксель как 1/96 дюйма, поэтому ваши вычисления всегда будут возвращать 96. Этот дюйм не является дюймом на экране, но учитывает расстояние до вашего глаза, что означает, что оно отличается в разных устройств. Производители браузеров должны установить для этого фактора что-то значимое, что не всегда так.

Andy 21.05.2015 15:59

Существует медиа-запрос CSS resolution - он позволяет ограничивать стили CSS определенными разрешениями:

Однако поддерживается только Firefox 3.5 и более поздних версий, Opera 9 и более поздних версий и IE 9.. Другие браузеры вообще не будут применять ваши стили, зависящие от разрешения (хотя я не проверял браузеры, отличные от настольных).

Вот площадка для проверки home.heeere.com/tech-ppi-aware-css-media-query.html

Rémi 18.01.2015 19:13

Вау, я могу только представить себе создание таблицы стилей, в которой перечислены все возможные разрешения с шагом в 1 или 0,1 точки на дюйм и вывести ближайшее фактическое разрешение экрана в зависимости от того, какой стиль был применен ...

Michael 08.02.2018 06:26

Это совсем не кажется точным ... ответ Endless, похоже, выполняет двоичный поиск с этим запросом, но на моих устройствах игровая площадка, связанная с @ Rémi, дает такие же недействительные результаты ...

Michael 08.02.2018 06:56
function getPPI(){
  // create an empty element
  var div = document.createElement("div");
  // give it an absolute size of one inch
  div.style.width = "1in";
  // append it to the body
  var body = document.getElementsByTagName("body")[0];
  body.appendChild(div);
  // read the computed width
  var ppi = document.defaultView.getComputedStyle(div, null).getPropertyValue('width');
  // remove it again
  body.removeChild(div);
  // and return the value
  return parseFloat(ppi);
} 

(От VodaFone)

Не работает, дает 96 как на моем Android-смартфоне 1080p, так и на моем ноутбуке.

nmz787 26.06.2014 11:34

Я только что нашел эту ссылку: http://dpi.lv/. По сути, это веб-инструмент для определения разрешения клиентского устройства, точек на дюйм и размера экрана.

Я зашел на свой компьютер и мобильный телефон, и он обеспечивает мне правильное разрешение и DPI. Для него есть репозиторий на github, так что вы можете увидеть, как он работает.

Нет, они не могут определить диагональ физического устройства и всегда показывают 13,3 дюйма на всех моих устройствах. По нему можно вычислить «пиксель на дюйм», но на самом деле он просто не может определить размер устройства.

Takol 02.01.2015 10:19

@Takol Это просто значение по умолчанию. Вы должны это изменить. Диагонали не могут быть обнаружены JavaScript. См. github.com/LeaVerou/dpi/issues/36

Frederik Krautwald 25.05.2015 23:24

@FrederikKrautwald И все же вопрос "Обнаружение системного DPI / PPI из JS / CSS?" на который это не ответ.

Michael 08.02.2018 07:01

@ Майкл Что? Я отвечал Takoi, используя инструмент Lea Verou, ссылка на который есть в ответе. Я не дал ответа на вопрос SO.

Frederik Krautwald 21.02.2018 15:40

Этот инструмент не работает. Откройте его в Chrome, увеличьте масштаб страницы, обновите и вы получите новое значение.

V. Rubinetti 15.09.2020 21:17

<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.info(dpi_x, dpi_y);
</script>

прихватил отсюда http://www.infobyip.com/detectmonitordpi.php. Работает на мобильных устройствах! (проверено Android 4.2.2)

Все, что вы делаете, это умножаете константу 96 на devicePixelRatio (которое можно переопределить). Это не последовательный способ получения точного ppi.

GreySage 22.06.2017 22:18

Я не могу понять, почему ты так думаешь.

MaxXx1313 26.06.2017 15:48

потому что слова, которые я напечатал, имеют значение? На всех устройствах 1 дюйм в css определяется как 96 пикселей, поэтому измерение div с шириной = 1 дюйм в пикселях всегда будет давать 96. Затем вы просто умножаете это (которое, опять же, всегда будет 96) на devicePixelRatio, которое не с учетом стандартов или ограничений, это может быть что угодно, и, в частности, любой может перезаписать его с любым значением, которое пожелает. Короче говоря, этот метод ненадежен.

GreySage 26.06.2017 17:34

На самом деле в firefox это не работает. Сам Firefox выглядит странно на 4К-дисплее. В хроме и т.е. вилка отлично.

MaxXx1313 27.06.2017 16:38

Я придумал способ, который не требует DOM ... вообще

DOM может быть беспорядочным, требуя, чтобы вы добавляли что-то в тело, не зная, что происходит с width: x !important в вашей таблице стилей. Вам также придется подождать, пока DOM будет готов к использованию ...

/**
 * 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.info(dpi)

Этот код действительно работает очень хорошо, но совсем не читается

jrmgx 30.09.2016 18:40

Результат должен быть точным? В моей документации по монитору указано 93, а в вашей функции - 96.

Supersharp 15.11.2016 19:11

Число должно быть точным. Он делает предположение, выполняя n ^ 2 до тех пор, пока функция не вернет false, а затем работает ближе к числу, беря среднее число. Вы можете попробовать это здесь: рабочий пример. Если функция говорит, что это 93, тогда это должно быть 93. Думаю, что в браузере есть что-то принципиально неработающее, поэтому браузер не знает, какое разрешение экрана, поэтому он просто предполагает, что это 96

Endless 15.11.2016 23:31

Это всегда возвращает 96, даже на мобильных устройствах. Это становится жертвой классической ошибки, заключающейся в непонимании того, что дюйм определяется как 96 пикселей.

GreySage 22.06.2017 22:32

+1 за усилие, но ... и этот код, и код, связанный с @eltomito, утверждают, что PPI моего телефона составляет ~ 336, тогда как на самом деле это 518. И они оба неправильно заявляют о моем Mac и ПК (с одинаковым исходным разрешением, но другим экраном sizes) оба имеют PPI ​​96, тогда как на самом деле фактические значения 102 и 157 соответственно.

Michael 08.02.2018 06:53

это не будет работать в WebKit / Safari (iOS 11), похоже, максимальное разрешение там еще не поддерживается

Eugen 14.02.2018 18:00

Итак, похоже, проблема в том, что мобильные браузеры отображают сайты в скрытом невидимом окне просмотра, а затем масштабируют их и отображают на реальном экране. DPI и размеры, которые браузер показывает коду javascript и CSS, относятся к области просмотра, а не к фактическому отображению (что, IMHO, означает, что DPI полностью бесполезен). Размеры области просмотра можно контролировать с помощью тега <meta name = "viewport">. Подробнее об этом на MDN: developer.mozilla.org/en-US/docs/Mozilla/Mobile/…

eltomito 09.03.2018 17:04

У меня была похожая идея, когда вы просто продолжаете тестировать @media screen and (resolution: 96dpi) и увеличивать dpi, пока не получите совпадение. Но быстрое тестирование в Chrome показывает, что, к сожалению, эти запросы CSS resolution@media также, похоже, масштабируются вверх / вниз, когда пользователь увеличивает / уменьшает масштаб, как это делает window.devicePixelRatio. Так что, к сожалению, они тоже бесполезны.

V. Rubinetti 15.09.2020 21:14

Вот версия получше: repl.it/@duzun/findDPIjs#script.js

DUzun 30.09.2020 10:09

@DUzun мне нравится битовый сдвиг (влево и вправо) вместо * 2 и / 2, похоже, ваша доза лучше справляется с поиском # 97, а также больше читабельности, локальных переменных и отсутствия тернарного оператора. Что не так с тернарными операторами? они не нравятся? :П

Endless 30.09.2020 15:33

@Endless Shifting использует дробную часть, которая актуальна для DPI. Мне нравится тернарный оператор, только в простых выражениях. Здесь я заменил его для лучшей читаемости и во избежание одного обращения к matchMedia().

DUzun 01.10.2020 17:47

Ответ от @Endless довольно хорош, но совсем не читается, это аналогичный подход с фиксированным минимумом / максимумом (он должен быть хорошим)

var dpi = (function () {
    for (var i = 56; i < 2000; i++) {
        if (matchMedia("(max-resolution: " + i + "dpi)").matches === true) {
            return i;
        }
    }
    return i;
})();

matchMedia теперь хорошо поддерживается и должен дать хороший результат, см. http://caniuse.com/#feat=matchmedia

Будьте осторожны, браузер не покажет вам точное разрешение экрана, а только приблизительное значение.

Это определенно более читабельно. Но у меня и у вас разница в том, что у меня matchMedia выполняется только 11 раз, чтобы выяснить, что это 96, а ваш код пробует все возможные числа от 56 до 2000.

Endless 15.11.2016 23:06

это не будет работать в WebKit / Safari (iOS 11), похоже, максимальное разрешение там еще не поддерживается

Eugen 14.02.2018 18:00

Сгенерируйте список известных DPI: https://stackoverflow.com/a/6793227

Определите точное устройство. Используя что-то вроде:

navigator.userAgent.toLowerCase();

Например, при обнаружении мобильного:

window.isMobile=/iphone|ipod|ipad|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec/i.test(navigator.userAgent.toLowerCase());

И прибыль!

Читаемый код из ответа @Endless:

const dpi = (function () {
    let i = 1;
    while ( !hasMatch(i) ) i *= 2;

    function getValue(start, end) {
        if (start > end) return -1;
        let average = (start + end) / 2;
        if ( hasMatch(average) ) {
            if ( start == average || !hasMatch(average - 1) ) {
                return average;
            } else {
                return getValue(start, average - 1);
            }
        } else {
            return getValue(average + 1, end);
        }
    }

    function hasMatch(x) {
        return matchMedia(`(max-resolution: ${x}dpi)`).matches;
    }

    return getValue(i / 2, i) | 0;
})();

Не могли бы вы предложить изменить ответ пользователя вместо публикации нового ответа?

Yves Gurcan 22.03.2021 06:28

Другие вопросы по теме