window.devicePixelRatio
will return 1 or 2 depending on if I'm using my retina monitor or standard. If I drag the window between the two monitors, this property will change. Is there a way I can have a listener fire when the change occurs?

- 29,855
- 23
- 108
- 144

- 3,418
- 5
- 38
- 63
-
I don't have two monitors to test this but I think the `resize` event will be fired when `window.devicePixelRatio` updates. – Mar 06 '15 at 20:10
-
1Just tried it, doesn't fire. – Matt Coady Mar 06 '15 at 22:44
-
1There is a good example at developer.mozilla.org [Example 2: Monitoring screen resolution or zoom level changes](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). – dastrobu Dec 23 '19 at 19:48
-
@user2570380 You don't need two monitors to test this. Just open e.g. Chrome DevTools → Toggle device toolbar (phone/tablet icon at the top left of DevTools) → Triple dot menu → Add device pixel ratio, then change the DPR at will. Note that I use Chrome 81 as of writing. – Michael Johansen May 06 '20 at 22:46
-
@MichaelJohansen: It doesn't trigger for me at all (Windows), it also seems to be buggy currently, see this issue on the Chromium tracker: https://bugs.chromium.org/p/chromium/issues/detail?id=1294293 – strarsis Feb 04 '22 at 19:13
7 Answers
You can listen to a media query with matchMedia
that will tell you when the devicePixelRatio goes past a certain barrier (unfortunately not for arbitrary scale changes).
e.g:
window.matchMedia('screen and (min-resolution: 2dppx)')
.addEventListener("change", function(e) {
if (e.matches) {
/* devicePixelRatio >= 2 */
} else {
/* devicePixelRatio < 2 */
}
});
The listener will be called when you drag a window between monitors, and when plugging in or unplugging an external non-retina monitor (if it causes the window to move from a retina to non-retina screen or vice-versa).
window.matchMedia
is supported in IE10+, and all other modern browsers.
References: https://code.google.com/p/chromium/issues/detail?id=123694, MDN on min-resolution

- 15,393
- 18
- 71
- 85
-
-
1This is much worse than the other answers, because it only allows for fixed boundaries – EzPizza Apr 06 '22 at 21:41
-
@James could you delete your comment, because it relates to an old version of the answer – Airsource Ltd Jan 31 '23 at 17:02
Most (or all?) answers on the internet only detect a specific change. Typically they detect whether the value is 2 or something else.
The issue probably lies in the MediaQuery, because they only allow checking for specific hardcoded values.
With some programming, it's possible to dynamically create a media query, which checks for a change of the current value.
let remove = null;
const updatePixelRatio = () => {
if(remove != null) {
remove();
}
let mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
let media = matchMedia(mqString);
media.addListener(updatePixelRatio);
remove = function() {media.removeListener(updatePixelRatio)};
console.log("devicePixelRatio: " + window.devicePixelRatio);
}
updatePixelRatio();

- 424
- 5
- 8
-
Smart, but isn't this repeatedly creating new `MediaQueryList`s that just remain out here? – Jean-Philippe Pellet Sep 14 '21 at 08:35
-
That's a good point. It probably doesn't causes a leak, because I remove the listener, and this seems to be the only way to dispose it. But it's dependent on how the browser have implemented this feature. – Florian Kirmaier Sep 16 '21 at 12:45
I took the IMO best answer (by @Neil) and made it a bit more human-readable:
function listenOnDevicePixelRatio() {
function onChange() {
console.log("devicePixelRatio changed: " + window.devicePixelRatio);
listenOnDevicePixelRatio();
}
matchMedia(
`(resolution: ${window.devicePixelRatio}dppx)`
).addEventListener("change", onChange, { once: true });
}
listenOnDevicePixelRatio();
No fixed boundary or variables needed.

- 979
- 1
- 13
- 22
-
3This approach also matches the documentation in MDN: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes – jtbandes Jun 28 '22 at 18:40
Thanks @florian-kirmaier this is exactly what I was looking for and if you pass in the option {once: true}
in the event listener there is no need to manually keep track and remove the event listener.
(function updatePixelRatio(){
matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
.addEventListener('change', updatePixelRatio, {once: true});
console.log("devicePixelRatio: " + window.devicePixelRatio);
})();

- 17,736
- 16
- 35
- 75

- 41
- 3
I prefer this one, so that I can provide a callback, and for the callback not to fire initially but only on changes, and to be able to stop it when no longer needed:
function onPixelRatioChange(cb) {
let mediaQuery
const listenerOptions = { once: true }
let firstRun = true
function onChange() {
if (firstRun) firstRun = false
else cb()
mediaQuery = matchMedia(`(resolution: ${devicePixelRatio}dppx)`)
mediaQuery.addEventListener('change', onChange, listenerOptions)
}
onChange()
return function unlisten() {
mediaQuery.removeEventListener('change', onChange, listenerOptions)
}
}
// Then use it like this:
const unlisten = onPixelRatioChange(() => {
console.log('pixel ratio changed:', devicePixelRatio)
})
// later, stop listening if desired:
unlisten()

- 44,284
- 53
- 191
- 263
Here's a typescript object version of @Florian's answer
export default class DevicePixelRatioObserver {
mediaQueryList: MediaQueryList | null = null
constructor(readonly onDevicePixelRatioChanged: () => void) {
this._onChange = this._onChange.bind(this)
this.createMediaQueryList()
}
createMediaQueryList() {
this.removeMediaQueryList()
let mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
this.mediaQueryList = matchMedia(mqString);
this.mediaQueryList.addEventListener('change', this._onChange)
}
removeMediaQueryList() {
this.mediaQueryList?.removeEventListener('change', this._onChange)
this.mediaQueryList = null
}
_onChange(event: MediaQueryListEvent) {
this.onDevicePixelRatioChanged()
this.createMediaQueryList()
}
destroy() {
this.removeMediaQueryList()
}
}

- 16,078
- 4
- 53
- 57
I made a function that will watch pixel ratio changes and will return a 'stop watching' function:
It also allows passing custom 'targetWindow' if you work with multi window setup.
function watchDevicePixelRatio(callback: (ratio: number) => void, targetWindow: Window = window) {
const media = targetWindow.matchMedia(`(resolution: ${targetWindow.devicePixelRatio}dppx)`);
function handleChange() {
callback(targetWindow.devicePixelRatio);
}
media.addEventListener("change", handleChange);
return function stopWatching() {
media.removeEventListener("change", handleChange);
};
}

- 12,773
- 9
- 78
- 91