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



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Я думаю, что ваш лучший подход - объединить предложение изображения «сниффера» с матрицей известных 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 точек на дюйм.
Этот комментарий полон ошибок. DPI - это аббревиатура, означающая точек на дюйм. С экраном вам обычно предоставляют размер диагонали в дюймах. Чтобы вычислить, сколько пикселей находится на этой диагонали, вы должны вычислить квадратный корень из (x ^ 2 + y ^ 2), где x и y - горизонтальные и вертикальные пиксели. Например, для 1920 x 1080 на 24-дюймовом дисплее значение DPI составляет примерно 91,8. 72 * 33% равно примерно 24; вы имеете в виду 72 * 133%. Вероятно, 96 точек на дюйм было не больше, чем средний масштаб мониторов в то время, и имел Ничего общего с расстоянием, на котором вы держите книгу, по сравнению с расстоянием от монитора.
Вы больше ничего не можете сделать? Например, если вы создаете изображение для распознавания камерой (т.е. вы запускаете свою программу, проводите мобильным телефоном по камере, происходит волшебство), разве вы не можете использовать что-то независимое от размера?
Если это приложение будет развернуто в контролируемой среде, можете ли вы предоставить утилиту калибровки? (можно сделать что-нибудь простое, например, распечатать визитки с маленькой линейкой, использовать ее в процессе калибровки).
Мне также нужно было отображать одно и то же изображение того же размера и с разным разрешением экрана, но только для 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 точек на дюйм.
Похоже, это не работает ни на каких устройствах. Вроде все сообщает 96ppi: codepen.io/anon/full/mrfvg
Он ДОЛЖЕН работать, но, к сожалению, все браузеры считают, что 1 дюйм = 96 пикселей и 1 см == 37,8 пикселей. Ошибка?
Это не ошибка - «px» - это не пиксели экрана, это фиксированная длина 0,75 pt, которая сама по себе составляет 1/72 дюйма.
спецификация CSS определяет 1 пиксель как 1/96 дюйма, поэтому ваши вычисления всегда будут возвращать 96. Этот дюйм не является дюймом на экране, но учитывает расстояние до вашего глаза, что означает, что оно отличается в разных устройств. Производители браузеров должны установить для этого фактора что-то значимое, что не всегда так.
Существует медиа-запрос CSS resolution - он позволяет ограничивать стили CSS определенными разрешениями:
Однако поддерживается только Firefox 3.5 и более поздних версий, Opera 9 и более поздних версий и IE 9.. Другие браузеры вообще не будут применять ваши стили, зависящие от разрешения (хотя я не проверял браузеры, отличные от настольных).
Вот площадка для проверки home.heeere.com/tech-ppi-aware-css-media-query.html
Вау, я могу только представить себе создание таблицы стилей, в которой перечислены все возможные разрешения с шагом в 1 или 0,1 точки на дюйм и вывести ближайшее фактическое разрешение экрана в зависимости от того, какой стиль был применен ...
Это совсем не кажется точным ... ответ Endless, похоже, выполняет двоичный поиск с этим запросом, но на моих устройствах игровая площадка, связанная с @ Rémi, дает такие же недействительные результаты ...
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, так и на моем ноутбуке.
Я только что нашел эту ссылку: http://dpi.lv/. По сути, это веб-инструмент для определения разрешения клиентского устройства, точек на дюйм и размера экрана.
Я зашел на свой компьютер и мобильный телефон, и он обеспечивает мне правильное разрешение и DPI. Для него есть репозиторий на github, так что вы можете увидеть, как он работает.
Нет, они не могут определить диагональ физического устройства и всегда показывают 13,3 дюйма на всех моих устройствах. По нему можно вычислить «пиксель на дюйм», но на самом деле он просто не может определить размер устройства.
@Takol Это просто значение по умолчанию. Вы должны это изменить. Диагонали не могут быть обнаружены JavaScript. См. github.com/LeaVerou/dpi/issues/36
@FrederikKrautwald И все же вопрос "Обнаружение системного DPI / PPI из JS / CSS?" на который это не ответ.
@ Майкл Что? Я отвечал Takoi, используя инструмент Lea Verou, ссылка на который есть в ответе. Я не дал ответа на вопрос SO.
Этот инструмент не работает. Откройте его в Chrome, увеличьте масштаб страницы, обновите и вы получите новое значение.
<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.
Я не могу понять, почему ты так думаешь.
потому что слова, которые я напечатал, имеют значение? На всех устройствах 1 дюйм в css определяется как 96 пикселей, поэтому измерение div с шириной = 1 дюйм в пикселях всегда будет давать 96. Затем вы просто умножаете это (которое, опять же, всегда будет 96) на devicePixelRatio, которое не с учетом стандартов или ограничений, это может быть что угодно, и, в частности, любой может перезаписать его с любым значением, которое пожелает. Короче говоря, этот метод ненадежен.
На самом деле в firefox это не работает. Сам Firefox выглядит странно на 4К-дисплее. В хроме и т.е. вилка отлично.
Я придумал способ, который не требует 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)Этот код действительно работает очень хорошо, но совсем не читается
Результат должен быть точным? В моей документации по монитору указано 93, а в вашей функции - 96.
Число должно быть точным. Он делает предположение, выполняя n ^ 2 до тех пор, пока функция не вернет false, а затем работает ближе к числу, беря среднее число. Вы можете попробовать это здесь: рабочий пример. Если функция говорит, что это 93, тогда это должно быть 93. Думаю, что в браузере есть что-то принципиально неработающее, поэтому браузер не знает, какое разрешение экрана, поэтому он просто предполагает, что это 96
Это всегда возвращает 96, даже на мобильных устройствах. Это становится жертвой классической ошибки, заключающейся в непонимании того, что дюйм определяется как 96 пикселей.
+1 за усилие, но ... и этот код, и код, связанный с @eltomito, утверждают, что PPI моего телефона составляет ~ 336, тогда как на самом деле это 518. И они оба неправильно заявляют о моем Mac и ПК (с одинаковым исходным разрешением, но другим экраном sizes) оба имеют PPI 96, тогда как на самом деле фактические значения 102 и 157 соответственно.
это не будет работать в WebKit / Safari (iOS 11), похоже, максимальное разрешение там еще не поддерживается
Итак, похоже, проблема в том, что мобильные браузеры отображают сайты в скрытом невидимом окне просмотра, а затем масштабируют их и отображают на реальном экране. DPI и размеры, которые браузер показывает коду javascript и CSS, относятся к области просмотра, а не к фактическому отображению (что, IMHO, означает, что DPI полностью бесполезен). Размеры области просмотра можно контролировать с помощью тега <meta name = "viewport">. Подробнее об этом на MDN: developer.mozilla.org/en-US/docs/Mozilla/Mobile/…
У меня была похожая идея, когда вы просто продолжаете тестировать @media screen and (resolution: 96dpi) и увеличивать dpi, пока не получите совпадение. Но быстрое тестирование в Chrome показывает, что, к сожалению, эти запросы CSS resolution@media также, похоже, масштабируются вверх / вниз, когда пользователь увеличивает / уменьшает масштаб, как это делает window.devicePixelRatio. Так что, к сожалению, они тоже бесполезны.
Вот версия получше: repl.it/@duzun/findDPIjs#script.js
@DUzun мне нравится битовый сдвиг (влево и вправо) вместо * 2 и / 2, похоже, ваша доза лучше справляется с поиском # 97, а также больше читабельности, локальных переменных и отсутствия тернарного оператора. Что не так с тернарными операторами? они не нравятся? :П
@Endless Shifting использует дробную часть, которая актуальна для DPI. Мне нравится тернарный оператор, только в простых выражениях. Здесь я заменил его для лучшей читаемости и во избежание одного обращения к matchMedia().
Ответ от @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.
это не будет работать в WebKit / Safari (iOS 11), похоже, максимальное разрешение там еще не поддерживается
Сгенерируйте список известных 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;
})();
Не могли бы вы предложить изменить ответ пользователя вместо публикации нового ответа?
возможный дубликат Как определить уровень масштабирования страницы во всех современных браузерах?