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?
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.
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
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();
MediaQueryList
s that just remain out here? –
Ruebenrueda 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.
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);
})();
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);
};
}
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()
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()
}
}
© 2022 - 2024 — McMap. All rights reserved.
resize
event will be fired whenwindow.devicePixelRatio
updates. – Cynara