窗口.devicePixelRatio变化监听器

21

window.devicePixelRatio根据我使用的视网膜显示器或标准显示器返回1或2。如果我在两个显示器之间拖动窗口,此属性将发生变化。是否有办法在发生变化时触发一个监听器?


我没有两个显示器来测试,但我认为当window.devicePixelRatio更新时,resize事件将会被触发。 - user2570380
1
刚试了一下,没有触发。 - Matt Coady
1
在developer.mozilla.org上有一个很好的例子 Example 2: Monitoring screen resolution or zoom level changes - dastrobu
@user2570380,您不需要两个显示器来测试此功能。只需打开Chrome DevTools→切换设备工具栏(DevTools左上角的手机/平板电脑图标)→三个点的菜单→添加设备像素比,然后随意更改DPR即可。请注意,我在撰写时使用的是Chrome 81。 - Michael Johansen
@MichaelJohansen:在我的电脑上(Windows),它根本没有触发,而且目前似乎存在一些错误,请参见Chromium跟踪器上的此问题:https://bugs.chromium.org/p/chromium/issues/detail?id=1294293 - strarsis
7个回答

15

您可以使用matchMedia监听媒体查询,这将告诉您当设备像素比超过某个临界值时(不幸的是不能用于任意比例变化)。

e.g:

window.matchMedia('screen and (min-resolution: 2dppx)')
    .addEventListener("change", function(e) {
      if (e.matches) {
        /* devicePixelRatio >= 2 */
      } else {
        /* devicePixelRatio < 2 */
      }
    });

监听器将在您在显示器之间拖动窗口以及插入或拔出外部非Retina显示器(如果它导致窗口从Retina屏幕移动到非Retina屏幕或反之亦然)时调用。 window.matchMedia 在IE10+和所有其他现代浏览器中得到支持。
参考资料:https://code.google.com/p/chromium/issues/detail?id=123694, MDN on min-resolution

谢谢,我已经编辑了答案以反映新的方法。 - nornagon
1
这比其他答案要糟糕得多,因为它只允许固定边界。 - EzPizza
@James,你能删除你的评论吗?因为它与旧版本的答案有关。 - Airsource Ltd

10

互联网上的大部分(或全部?)答案仅检测特定的更改。通常它们会检测值是否为2或其他值。

问题可能在于MediaQuery,因为它们只允许检查特定的硬编码值。

通过一些编程,可以动态创建媒体查询,从而检查当前值的变化。

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”,而它们只是留在这里吗? - Jean-Philippe Pellet
这是一个很好的观点。它可能不会导致泄漏,因为我移除了监听器,而这似乎是唯一的处理方法。但这取决于浏览器如何实现此功能。 - Florian Kirmaier
https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes - undefined
现在,MDN页面是正确的。:) 回答问题时,我确实创建了一个工单。 https://github.com/mdn/content/issues/4478 他们大约一年前修复了它。 - undefined

7

我采用了IMO中最好的答案(由@Neil提供),并使其更加人性化,具有易读性:

function listenOnDevicePixelRatio() {
  function onChange() {
    console.log("devicePixelRatio changed: " + window.devicePixelRatio);
    listenOnDevicePixelRatio();
  }
  matchMedia(
    `(resolution: ${window.devicePixelRatio}dppx)`
  ).addEventListener("change", onChange, { once: true });
}
listenOnDevicePixelRatio();

不需要固定的边界或变量。


3
这种方法也符合MDN文档:https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes - jtbandes

4

感谢 @florian-kirmaier 这正是我所要寻找的。如果在事件监听器中传递选项 {once: true},则无需手动跟踪和删除事件监听器。

(function updatePixelRatio(){
matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
.addEventListener('change', updatePixelRatio, {once: true});
console.log("devicePixelRatio: " + window.devicePixelRatio);
})();

0

我更喜欢这个选项,因为我可以提供一个回调函数,并且回调函数只在更改时触发,而不是最初触发,当不再需要时也可以停止它:

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()


0

这是@Florian答案的TypeScript对象版本

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()
    }
}

0
我创建了一个函数,它将监视像素比例的变化,并返回一个“停止监视”的函数:
如果您使用多窗口设置,则还允许传递自定义“targetWindow”。
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);
  };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接