Electron 多显示器开发?这篇帮你搞定屏幕坐标与窗口定位!

Electron Screen API 知识点总结

目录

  • [1. 概述](#1. 概述 "#1-%E6%A6%82%E8%BF%B0")
  • [2. 核心概念](#2. 核心概念 "#2-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5")
  • [3. 事件](#3. 事件 "#3-%E4%BA%8B%E4%BB%B6")
  • [4. 方法](#4. 方法 "#4-%E6%96%B9%E6%B3%95")
  • [5. Display 对象](#5. Display 对象 "#5-display-%E5%AF%B9%E8%B1%A1")
  • [6. Point 和 Rectangle](#6. Point 和 Rectangle "#6-point-%E5%92%8C-rectangle")
  • [7. 实际应用示例](#7. 实际应用示例 "#7-%E5%AE%9E%E9%99%85%E5%BA%94%E7%94%A8%E7%A4%BA%E4%BE%8B")
  • [8. 坐标系统](#8. 坐标系统 "#8-%E5%9D%90%E6%A0%87%E7%B3%BB%E7%BB%9F")
  • [9. 平台差异](#9. 平台差异 "#9-%E5%B9%B3%E5%8F%B0%E5%B7%AE%E5%BC%82")

1. 概述

screen 模块作用

获取屏幕、显示器、鼠标位置等信息,用于:

  • 创建填充屏幕的窗口
  • 多显示器支持
  • 监听屏幕变化
  • 窗口定位

引入方式

javascript 复制代码
const { screen } = require('electron')

// ⚠️ 注意:在渲染进程中使用 window.screen 会冲突
// 应该使用 electron/main 的 screen

2. 核心概念

2.1 Display(显示器)

javascript 复制代码
{
  id: number,              // 显示器唯一标识
  label: string,          // 显示器标签
  bounds: Rectangle,      // 显示器的位置和大小
  workArea: Rectangle,     // 工作区域(排除任务栏等)
  size: Size,             // 显示器大小 { width, height }
  workAreaSize: Size,      // 工作区域大小
  scaleFactor: number,     // 缩放因子 (DPI)
  rotation: number,        // 旋转角度 (0, 90, 180, 270)
  touchSupport: string,    // 触摸支持: 'available' | 'none'
  internal: boolean,       // 是否为内置显示器
  primary: boolean         // 是否为主显示器
}

2.2 工作区域 vs 边界

scss 复制代码
┌────────────────────────────────────────────────────────┐
│                      边界 (bounds)                      │
│  ┌──────────────────────────────────────────────────┐  │
│  │                                                   │  │
│  │              工作区域 (workArea)                   │  │
│  │              (排除系统UI)                          │  │
│  │                                                   │  │
│  │  ┌────────────────────────────────────────────┐  │  │
│  │  │                                            │  │  │
│  │  │              可用区域                       │  │  │
│  │  │                                            │  │  │
│  │  └────────────────────────────────────────────┘  │  │
│  │                                                   │  │
│  │  ████████████████████████████████████████████  │  │
│  └──────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────┘

2.3 坐标系说明

类型 说明 用途
物理像素 实际硬件像素 精确屏幕坐标
DIP 点 虚拟化像素(自动缩放) 跨设备一致性

3. 事件

3.1 显示器连接/断开

javascript 复制代码
const { screen } = require('electron')

// 新显示器连接
screen.on('display-added', (event, newDisplay) => {
  console.log('新显示器:', newDisplay.label)
  console.log('分辨率:', newDisplay.bounds.width, 'x', newDisplay.bounds.height)
})

// 显示器断开
screen.on('display-removed', (event, oldDisplay) => {
  console.log('显示器断开:', oldDisplay.label)
})

3.2 显示器配置变化

javascript 复制代码
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
  console.log('变化的属性:', changedMetrics)
  // Possible values: 'bounds', 'workArea', 'scaleFactor', 'rotation'
  
  if (changedMetrics.includes('bounds')) {
    console.log('显示器位置或大小改变')
  }
  
  if (changedMetrics.includes('scaleFactor')) {
    console.log('DPI 缩放因子改变')
  }
})

4. 方法

4.1 获取鼠标位置

javascript 复制代码
const { screen } = require('electron')

const cursorPoint = screen.getCursorScreenPoint()

console.log('鼠标位置:', cursorPoint.x, cursorPoint.y)
// 返回 DIP 点

4.2 获取主显示器

javascript 复制代码
const primaryDisplay = screen.getPrimaryDisplay()

console.log('主显示器:')
console.log('  分辨率:', primaryDisplay.size)
console.log('  工作区:', primaryDisplay.workAreaSize)
console.log('  缩放:', primaryDisplay.scaleFactor)

4.3 获取所有显示器

javascript 复制代码
const displays = screen.getAllDisplays()

displays.forEach((display, index) => {
  console.log(`显示器 ${index + 1}:`)
  console.log('  ID:', display.id)
  console.log('  标签:', display.label)
  console.log('  位置:', display.bounds.x, display.bounds.y)
  console.log('  大小:', display.bounds.width, 'x', display.bounds.height)
  console.log('  是否主显示器:', display.primary)
})

4.4 获取最近的显示器

javascript 复制代码
// 根据点获取最近的显示器
const point = { x: 1920, y: 1080 }
const nearestDisplay = screen.getDisplayNearestPoint(point)

// 根据矩形获取匹配的显示器
const rect = { x: 100, y: 100, width: 800, height: 600 }
const matchingDisplay = screen.getDisplayMatching(rect)

5. Display 对象

5.1 完整示例

javascript 复制代码
const { screen } = require('electron')

const display = screen.getPrimaryDisplay()

console.log('=== 显示器信息 ===')
console.log('ID:', display.id)
console.log('标签:', display.label)
console.log('是否主显示器:', display.primary)
console.log('是否内置:', display.internal)

console.log('\n边界 (bounds):')
console.log('  位置: x=', display.bounds.x, 'y=', display.bounds.y)
console.log('  大小: width=', display.bounds.width, 'height=', display.bounds.height)

console.log('\n工作区 (workArea):')
console.log('  位置: x=', display.workArea.x, 'y=', display.workArea.y)
console.log('  大小: width=', display.workArea.width, 'height=', display.workArea.height)

console.log('\n尺寸 (size):')
console.log('  width:', display.size.width)
console.log('  height:', display.size.height)

console.log('\n缩放因子:', display.scaleFactor)
console.log('旋转角度:', display.rotation)
console.log('触摸支持:', display.touchSupport)

6. Point 和 Rectangle

6.1 Point 结构

javascript 复制代码
{ x: number, y: number }

6.2 Rectangle 结构

javascript 复制代码
{ x: number, y: number, width: number, height: number }

6.3 坐标转换方法

方法 说明 平台
screen.screenToDipPoint(point) 物理点 → DIP 点 Windows/Linux
screen.dipToScreenPoint(point) DIP 点 → 物理点 Windows/Linux
screen.screenToDipRect(window, rect) 物理矩形 → DIP 矩形 Windows
screen.dipToScreenRect(window, rect) DIP 矩形 → 物理矩形 Windows
javascript 复制代码
// Windows/Linux 示例
const physicalPoint = { x: 3840, y: 2160 }  // 4K 显示器
const dipPoint = screen.screenToDipPoint(physicalPoint)
console.log('DIP 点:', dipPoint)

// Windows 矩形转换
const physicalRect = { x: 0, y: 0, width: 2560, height: 1440 }
const dipRect = screen.screenToDipRect(null, physicalRect)

7. 实际应用示例

7.1 创建全屏窗口

javascript 复制代码
const { app, BrowserWindow, screen } = require('electron')

app.whenReady().then(() => {
  const primaryDisplay = screen.getPrimaryDisplay()
  const { width, height } = primaryDisplay.workAreaSize
  
  const mainWindow = new BrowserWindow({
    width,
    height,
    x: primaryDisplay.bounds.x,
    y: primaryDisplay.bounds.y
  })
  
  mainWindow.loadURL('https://example.com')
})

7.2 在外接显示器创建窗口

javascript 复制代码
const { app, BrowserWindow, screen } = require('electron')

app.whenReady().then(() => {
  const displays = screen.getAllDisplays()
  
  // 找到外接显示器(位置不在原点)
  const externalDisplay = displays.find(display => {
    return display.bounds.x !== 0 || display.bounds.y !== 0
  })
  
  if (externalDisplay) {
    const win = new BrowserWindow({
      x: externalDisplay.bounds.x + 50,
      y: externalDisplay.bounds.y + 50,
      width: 800,
      height: 600
    })
    
    win.loadURL('https://example.com')
  }
})

7.3 创建居中窗口

javascript 复制代码
function createCenteredWindow() {
  const primaryDisplay = screen.getPrimaryDisplay()
  const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize
  
  const windowWidth = 1024
  const windowHeight = 768
  
  const x = Math.round((screenWidth - windowWidth) / 2)
  const y = Math.round((screenHeight - windowHeight) / 2)
  
  return new BrowserWindow({
    width: windowWidth,
    height: windowHeight,
    x,
    y
  })
}

7.4 监听显示器变化

javascript 复制代码
const { app, BrowserWindow, screen } = require('electron')

let mainWindow = null

function createWindow() {
  mainWindow = new BrowserWindow({ ... })
}

app.whenReady().then(() => {
  createWindow()
  
  // 显示器配置改变时调整窗口
  screen.on('display-metrics-changed', (event, display, changedMetrics) => {
    if (mainWindow && display.id === screen.getPrimaryDisplay().id) {
      if (changedMetrics.includes('workArea')) {
        // 重新计算窗口位置
        const { width, height } = display.workArea
        mainWindow.setSize(Math.min(width, 1200), Math.min(height, 800))
        mainWindow.center()
      }
    }
  })
  
  // 显示器连接/断开
  screen.on('display-added', (event, newDisplay) => {
    console.log('新显示器连接:', newDisplay.label)
    // 可以自动在新显示器上创建窗口
  })
  
  screen.on('display-removed', (event, oldDisplay) => {
    console.log('显示器断开:', oldDisplay.label)
    // 检查是否有窗口在该显示器上,如果有则移动
  })
})

7.5 多显示器管理器

javascript 复制代码
class MultiMonitorManager {
  constructor() {
    this.displays = screen.getAllDisplays()
    this.windows = new Map()
    
    // 监听变化
    screen.on('display-added', (...args) => this.handleDisplayChange(...args))
    screen.on('display-removed', (...args) => this.handleDisplayChange(...args))
    screen.on('display-metrics-changed', (...args) => this.handleDisplayChange(...args))
  }
  
  handleDisplayChange() {
    this.displays = screen.getAllDisplays()
    console.log('当前显示器数量:', this.displays.length)
  }
  
  getAvailableDisplay() {
    // 找到没有窗口占用的显示器
    const usedDisplayIds = Array.from(this.windows.keys())
    
    for (const display of this.displays) {
      if (!usedDisplayIds.includes(display.id)) {
        return display
      }
    }
    
    // 如果都占用了,返回主显示器
    return screen.getPrimaryDisplay()
  }
  
  moveWindowToDisplay(window, displayId) {
    const display = this.displays.find(d => d.id === displayId)
    if (display) {
      window.setPosition(display.bounds.x, display.bounds.y)
      window.setSize(display.workArea.width, display.workArea.height)
    }
  }
}

7.6 检测 HiDPI 显示器

javascript 复制代码
const { screen } = require('electron')

function isHiDPIDisplay() {
  const primaryDisplay = screen.getPrimaryDisplay()
  return primaryDisplay.scaleFactor > 1
}

function getDisplayQuality() {
  const primaryDisplay = screen.getPrimaryDisplay()
  const { scaleFactor } = primaryDisplay
  
  if (scaleFactor <= 1) return '标准清晰度'
  if (scaleFactor <= 1.5) return 'Retina (HD)'
  if (scaleFactor <= 2) return 'Retina (FHD)'
  return 'Retina (4K+)'
}

console.log('是否为高清屏:', isHiDPIDisplay())
console.log('显示质量:', getDisplayQuality())

8. 坐标系统

8.1 多显示器坐标

ini 复制代码
显示器 1 (主)              显示器 2 (外接)
┌────────────────┐         ┌────────────────┐
│ x=0, y=0       │         │ x=1920, y=0    │
│                │         │                │
│  ┌──────────┐  │         │  ┌──────────┐  │
│  │ 1920x1080 │  │         │  │ 1920x1080 │  │
│  └──────────┘  │         │  └──────────┘  │
└────────────────┘         └────────────────┘

8.2 坐标转换说明

javascript 复制代码
// 高 DPI 显示器 (4K)
// 物理像素: 3840 x 2160
// DIP 点:   1920 x 1080 (缩放因子 2x)

// 如果代码使用 DIP 坐标 (1920x1080)
// 系统会自动转换为物理像素 (3840x2160) 显示

9. 平台差异

9.1 功能支持

功能 Windows macOS Linux
获取鼠标位置
获取所有显示器
主显示器
触摸支持
坐标转换 ⚠️
Wayland 支持 N/A N/A ⚠️

9.2 Wayland 注意事项

scss 复制代码
⚠️ Linux Wayland 限制:
   - screenToDipPoint() 不支持,会返回原值
   - dipToScreenPoint() 不支持,会返回原值
   - 需要使用 --ozone-platform=x11 参数启用 X11

9.3 Windows 坐标转换

javascript 复制代码
// Windows 支持完整的坐标转换
const point = { x: 100, y: 100 }

// 物理像素 → DIP 点
const dipPoint = screen.screenToDipPoint(point)

// DIP 点 → 物理像素
const physicalPoint = screen.dipToScreenPoint(dipPoint)

// 矩形转换
const rect = { x: 0, y: 0, width: 1920, height: 1080 }
const dipRect = screen.screenToDipRect(win, rect)

最佳实践

✅ 推荐做法

javascript 复制代码
// 1. 在 app ready 后使用
app.whenReady().then(() => {
  const display = screen.getPrimaryDisplay()
  // ...
})

// 2. 使用 workArea 而非 bounds
const { workAreaWidth, workAreaHeight } = screen.getPrimaryDisplay()

// 3. 监听显示器变化
screen.on('display-metrics-changed', (e, display, metrics) => {
  if (metrics.includes('workArea')) {
    // 调整窗口
  }
})

// 4. 窗口居中显示
mainWindow.center()

❌ 避免做法

javascript 复制代码
// 1. 不要在 ready 前使用
// app.ready() 之前调用 screen 会报错

// 2. 不要硬编码分辨率
// 使用 screen API 获取实际屏幕大小

// 3. 渲染进程避免与 window.screen 冲突
// 使用 require('electron').screen 而不是 DOM 的 window.screen

方法速查表

方法 返回值 说明
getCursorScreenPoint() Point 获取鼠标位置
getPrimaryDisplay() Display 获取主显示器
getAllDisplays() Display[] 获取所有显示器
getDisplayNearestPoint(point) Display 获取最近的显示器
getDisplayMatching(rect) Display 获取匹配的显示器
screenToDipPoint(point) Point 物理→DIP (Win/Linux)
dipToScreenPoint(point) Point DIP→物理 (Win/Linux)
screenToDipRect(win, rect) Rectangle 物理→DIP矩形 (Win)
dipToScreenRect(win, rect) Rectangle DIP→物理矩形 (Win)

事件速查表

事件 触发条件 参数
display-added 新显示器连接 newDisplay
display-removed 显示器断开 oldDisplay
display-metrics-changed 显示器配置改变 display, changedMetrics[]

文档基于 Electron v28+ Screen API 编写

相关推荐
七十二時_阿川4 小时前
Electron App 速查表:生命周期事件、方法、平台差异
前端·electron
七十二時_阿川4 小时前
Electron Tray API 详解:托盘图标、右键菜单、气泡通知
前端·electron
番茄炒韭菜4 小时前
windows10下安装mise
前端
用户938515635075 小时前
AI全栈前端实战|DeepSeek + CC插件,1小时产出高质量外卖App落地页
前端
AI2中文网5 小时前
App Inventor 2 向心力实验App - 探究向心力F与角速度ω、半径r、质量m的关系
前端·javascript·r语言
程序软件分享5 小时前
vue多语言交易所系统/期货/合约交易/质押生息/盲盒/挖矿/跟单源码
前端·javascript·vue.js·期货平台源码
悟空瞎说5 小时前
【前端视角学 Rust】1.3 一文吃透 Cargo:Rust 的 npm+webpack,新手必懂工程化工具
前端
yingyima5 小时前
Linux Crontab 速查手册:5 个问题直击核心语法与常用场景
前端
用户4445543654265 小时前
Android compose
前端