我如何让 Electron 窗口可拖动并保持鼠标进入/离开检测

发布: (2025年12月3日 GMT+8 20:41)
3 min read
原文: Dev.to

Source: Dev.to

介绍

我希望我的应用能够完全可拖动,同时在鼠标 enter 和 leave 时仍然能够改变其高度和宽度。默认情况下,使窗口可拖动会忽略拖拽区域的鼠标事件,这会导致普通的 enter/leave 检测失效。

可视化对比

鼠标在外部

Focus app widget mouse outside

鼠标在内部

Focus app widget mouse inside

窗口配置

const mainWindow = new BrowserWindow({
  // width, height, x, y, etc.
  titleBarStyle: 'hidden',
  backgroundMaterial: "acrylic",
  visualEffectState: "active",
  vibrancy: "popover", // macOS
  frame: false, // frameless window
});

让窗口可拖动

为任何需要充当拖拽区域的区域添加以下类:

.dragAble {
  -webkit-user-select: none;
  -webkit-app-region: drag;
}

对于必须保持交互性的元素,使用:

.no-drag {
  -webkit-app-region: no-drag;
}

处理 mouse‑enter 和 mouse‑leave

Electron 并未为整个窗口提供原生的 mouse‑enter/leave 事件,所以我们自行实现。

1. 轮询监听器

一个工具函数在一定间隔内运行,检查光标位置是否在窗口边界内,并将结果通过回调返回。

function listenToMouseMovement(callback) {
  const id = setInterval(() => {
    const cursor = screen.getCursorScreenPoint();
    const bounds = esm.mainWindow.getBounds();

    const inside =
      cursor.x >= bounds.x &&
      cursor.x = bounds.y &&
      cursor.y  {
  if (esm.isMovingWindow) return; // ignore while dragging

  if (inside) {
    // Cancel any pending leave timeout
    if (leaveTimeout) {
      clearTimeout(leaveTimeout);
      leaveTimeout = null;
    }

    if (esm.mouseWasOutsideWindow) {
      // Debounce the enter event
      if (!enterTimeout) {
        enterTimeout = setTimeout(() => {
          esm.mouseWasOutsideWindow = false;
          mainWindow.webContents.send("onMouseIsInsideTheApp");
        }, 120);
      }
    }
  } else {
    // Cancel any pending enter timeout
    if (enterTimeout) {
      clearTimeout(enterTimeout);
      enterTimeout = null;
    }

    if (!esm.mouseWasOutsideWindow) {
      // Debounce the leave event
      if (!leaveTimeout) {
        leaveTimeout = setTimeout(() => {
          esm.mouseWasOutsideWindow = true;
          mainWindow.webContents.send("onMouseIsOutsideTheApp");
        }, 120);
      }
    }
  }
});

3. 窗口移动时忽略鼠标检查

我们使用 Electron 的原生事件来跟踪窗口的移动状态:

esm.mainWindow.on("will-move", () => {
  esm.isMovingWindow = true;
});

esm.mainWindow.on("move", () => {
  esm.isMovingWindow = true;
  if (esm.moveEndTimeout) {
    clearTimeout(esm.moveEndTimeout);
    esm.moveEndTimeout = null;
  }
});

esm.mainWindow.on("moved", () => {
  if (esm.moveEndTimeout) clearTimeout(esm.moveEndTimeout);
  esm.moveEndTimeout = setTimeout(() => {
    esm.isMovingWindow = false;
    esm.moveEndTimeout = null;
  }, 200);
});

结果

该方案消除了闪烁,并且在窗口仍然能够完整拖动的同时,仍然响应 mouse‑enter 和 mouse‑leave 事件。你可以在这条推文中看到实际效果(为简洁起见省略链接)。

感谢阅读!这是我的第一篇文章,后续还有更多内容。

Back to Blog

相关文章

阅读更多 »

我在 JavaScript 的第一步:简要解析

JavaScript 中的变量 **let** 用于可以在以后更改的值。 ```javascript let age = 20; age = 21; ``` **const** 用于不应被更改的值。 ```javascript const PI = 3.14159; ```