前端周刊7-用JavaScript检测浏览器缩放变化

March 30, 2025 / Administrator / 8阅读 / 0评论 / 分类: fe

我们可以通过浏览器resize事件来监听浏览器的缩放操作,但是当浏览器窗口大小调整和浏览器zoom比例缩放都会导致resize事件被触发。

本文的目的是只想检测比例缩放,而不是普通窗口的变化;

缩放有点类似于调整大小

首先,并没有一个事件监听比例缩放。相反,比例缩放触发的是resize事件。如果从页面层级的角度分析,又是合理的。因为比例大小变大或者变小,宽度和高度都会发生变化。用户的页面元素也会随之变大或者变小,只是归为调整尺寸活动的一个细节。

但是我们有一种方法可以检测到比例变化。

检测视图窗口(viewport)和浏览器(browser)大小

网页有两种尺寸:

  • 浏览器browser大小(window.outerWidth和window.outerHeight),包括窗口框架、工具栏、滚动条

  • 视图窗口viewport大小(window.innerWidth和window.innerHeight),是我们内容可用的市级区域

当我们放大或者缩小网页比例的时候,浏览器大小不会改变,视图窗口大小会改变。这就是我们实现缩放检测逻辑的核心。

核心部分的代码如下:

class ZoomDetector {
  constructor() {
    this.lastWidth = window.innerWidth;
    this.lastScale = this.getZoomLevel();
    this.setupListeners();
  }

  getZoomLevel() {
    return Math.round(window.outerWidth / window.innerWidth * 100);
  }

  setupListeners() {
    window.addEventListener('resize', () => {
      requestAnimationFrame(() => this.checkZoom());
    });

    if (window.visualViewport) {
      window.visualViewport.addEventListener('resize', () => {
        if (window.innerWidth === this.lastWidth) {
          this.checkZoom();
        }
      });
    }
  }

  checkZoom() {
    const currentScale = this.getZoomLevel();
    const currentWidth = window.innerWidth;

    if (currentScale !== this.lastScale || currentWidth !== this.lastWidth) {
      const direction = currentScale > this.lastScale ? 'in' : 'out';
      console.log(`Zoom ${direction} detected: ${currentScale}%`);

      window.dispatchEvent(new CustomEvent('zoom', {
        detail: {
          oldScale: this.lastScale,
          newScale: currentScale,
          direction: direction,
          isWindowResize: currentWidth !== this.lastWidth
        }
      }));

      this.lastScale = currentScale;
      this.lastWidth = currentWidth;
    }
  }
}

理解代码

我们来解释一下代码,通过比较window.outerWidth和window.innerWidth的比率来计算缩放比。

/**
 * ZoomDetector类 - 用于监测和报告浏览器缩放级别变化
 * 同时处理浏览器缩放(Ctrl+/-或Cmd+/-)和手势缩放(双指缩放)
 */
class ZoomDetector {
  /**
   * 初始化探测器,记录初始窗口尺寸
   * 设置缩放检测的事件监听器
   */
  constructor() {
    // 存储初始窗口宽度用于后续比较
    this.lastWidth = window.innerWidth;
    // 计算并存储初始缩放级别
    this.lastScale = this.getZoomLevel();
    // 设置缩放检测的事件监听器
    this.setupListeners();
  }

  /**
   * 计算当前缩放百分比
   * 通过计算窗口外部宽度与内部宽度的比率实现
   * @returns {number} 缩放百分比(如100表示100%)
   */
  getZoomLevel() {
    return Math.round(window.outerWidth / window.innerWidth * 100);
  }

  /**
   * 设置不同类型缩放事件的事件监听器
   * 同时处理浏览器缩放和手势缩放
   */
  setupListeners() {
    // 监听浏览器窗口resize事件(由浏览器缩放触发)
    window.addEventListener('resize', () => {
      // 使用requestAnimationFrame优化性能
      // 确保在下一帧渲染时检查缩放
      requestAnimationFrame(() => this.checkZoom());
    });

    // 如果可用,使用visualViewport API处理手势缩放
    if (window.visualViewport) {
      window.visualViewport.addEventListener('resize', () => {
        // 仅当窗口宽度未变化时处理(避免重复触发)
        if (window.innerWidth === this.lastWidth) {
          this.checkZoom();
        }
      });
    }
  }

  /**
   * 检查缩放级别是否变化并触发相应事件
   * 判断缩放方向并派发自定义缩放事件
   */
  checkZoom() {
    // 获取当前测量值
    const currentScale = this.getZoomLevel();
    const currentWidth = window.innerWidth;

    // 仅当缩放级别或窗口大小实际变化时处理
    if (currentScale !== this.lastScale || currentWidth !== this.lastWidth) {
      // 判断是放大还是缩小
      const direction = currentScale > this.lastScale ? 'in' : 'out';
      console.log(`检测到缩放${direction}:当前${currentScale}%`);

      // 创建并派发包含详细信息的自定义事件
      window.dispatchEvent(new CustomEvent('zoom', {
        detail: {
          oldScale: this.lastScale,      // 原缩放级别
          newScale: currentScale,         // 新缩放级别
          direction: direction,           // 缩放方向('in'或'out')
          isWindowResize: currentWidth !== this.lastWidth  // 是否窗口大小变化
        }
      }));

      // 更新存储值供下次比较
      this.lastScale = currentScale;
      this.lastWidth = currentWidth;
    }
  }
}

使用缩放监听器

我们用一段简单的html代码做一个示例

<!DOCTYPE html>
<html lang="zh-CN">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>浏览器缩放检测</title>

    <h1 id="zoomText">100%</h1>

    <style>
      /* 样式部分无需翻译 */
      body {
        margin: 0;
        padding: 0;
        height: 100vh;
        width: 100vw;

        display: flex;
        justify-content: center;
        align-items: center;

        background-color: #D3FAC7;
      }

      h1 {
        font-family: sans-serif;
        color: #32432c;
        opacity: .7;
      }
    </style>
  </head>

  <body>
    <script>
      /**
     * ZoomDetector 类 - 监控并报告浏览器缩放级别的变化
     * 处理浏览器缩放(Ctrl+/- 或 Cmd+/-)和手势缩放操作
     */
      class ZoomDetector {
        /**
       * 初始化检测器,记录初始窗口尺寸
       * 设置缩放检测的事件监听器
       */
        constructor() {
          // 存储初始窗口宽度用于后续比较
          this.lastWidth = window.innerWidth;
          // 计算并存储初始缩放级别
          this.lastScale = this.getZoomLevel();
          // 设置缩放检测的事件监听器
          this.setupListeners();
        }

        /**
       * 计算当前缩放百分比
       * 通过窗口外部宽度与内部宽度的比率计算
       * @returns {number} 缩放百分比(例如 100 表示 100%)
       */
        getZoomLevel() {
          return Math.round(window.outerWidth / window.innerWidth * 100);
        }

        /**
       * 设置不同类型缩放事件的事件监听器
       * 同时处理浏览器缩放和手势缩放手势
       */
        setupListeners() {
          // 监听浏览器窗口resize事件(由浏览器缩放触发)
          window.addEventListener('resize', () => {
            // 使用requestAnimationFrame优化性能
            // 确保缩放检查在下一帧渲染时执行
            requestAnimationFrame(() => this.checkZoom());
          });

          // 如果可用,使用visualViewport API处理手势缩放事件
          if (window.visualViewport) {
            window.visualViewport.addEventListener('resize', () => {
              // 仅当窗口宽度未变化时处理
              // 避免viewport和窗口resize同时触发重复事件
              if (window.innerWidth === this.lastWidth) {
                this.checkZoom();
              }
            });
          }
        }

        /**
       * 检查缩放级别是否变化并触发相应事件
       * 判断缩放方向并触发自定义zoom事件
       */
        checkZoom() {
          // 获取当前测量值
          const currentScale = this.getZoomLevel();
          const currentWidth = window.innerWidth;

          // 仅当缩放或窗口尺寸实际发生变化时处理
          if (currentScale !== this.lastScale || currentWidth !== this.lastWidth) {
            // 判断用户是在放大还是缩小
            const direction = currentScale > this.lastScale ? '放大' : '缩小';
            console.log(`检测到${direction}操作: ${currentScale}%`);

            // 创建并派发包含详细信息的自定义zoom事件
            window.dispatchEvent(new CustomEvent('zoom', {
              detail: {
                oldScale: this.lastScale,       // 之前的缩放级别
                newScale: currentScale,         // 新的缩放级别
                direction: direction,           // 缩放方向('放大'或'缩小')
                isWindowResize: currentWidth !== this.lastWidth  // 是否窗口尺寸变化
              }
            }));

            // 更新存储值用于下次比较
            this.lastScale = currentScale;
            this.lastWidth = currentWidth;
          }
        }
      }

      // 初始化缩放检测器并更新初始缩放显示
      const zoomDetector = new ZoomDetector();
      updateZoomText(zoomDetector.getZoomLevel());

      // 为自定义zoom事件设置监听器
      window.addEventListener('zoom', (e) => {
        // 从事件中提取缩放详情
        const { oldScale, newScale, direction, isWindowResize } = e.detail;
        // 更新缩放级别显示
        updateZoomText(newScale);
      });

      /**
     * 在界面中更新缩放百分比显示
     * @param {number} zoomValue - 当前缩放百分比
     */
      function updateZoomText(zoomValue) {
        document.querySelector("#zoomText").innerText = "🔎 " + zoomValue + "%";
      }
    </script>
  </body>

</html>

#zoom(1)

文章作者:Administrator

文章链接:http://halo.chenkeyan.com/archives/fe-weekly-7-zoom-detect-event

版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!


评论