Electron 颜色拾取器开发实战适配鸿蒙
颜色拾取器(数码测色计)是一个实时屏幕颜色检测工具,能够精确获取屏幕上任意位置的颜色值。该工具广泛应用于设计、开发、调试等场景。✅屏幕截图技术:使用API 获取屏幕截图✅颜色提取算法:使用 Canvas API 提取像素颜色✅实时鼠标跟踪:使用screenAPI 跟踪鼠标位置✅Canvas 应用:实现放大镜和图像处理✅性能优化:缓存、节流、防抖等优化策略✅IPC 通信:主进程与渲染进程的数据传递。
·
Electron 颜色拾取器开发实战适配鸿蒙
目录
功能概述
颜色拾取器(数码测色计)是一个实时屏幕颜色检测工具,能够精确获取屏幕上任意位置的颜色值。该工具广泛应用于设计、开发、调试等场景。
核心功能
-
实时颜色拾取
- 实时跟踪鼠标位置
- 精确获取像素颜色值
- 支持 RGB 和 HEX 格式显示
-
放大镜功能
- 实时放大显示鼠标周围区域
- 可调节放大倍数(5x-30x)
- 像素级精确显示
-
颜色信息展示
- RGB 值显示
- HEX 颜色码
- 颜色样本预览
- 鼠标位置坐标
-
高性能优化
- 60fps 流畅更新
- 智能缓存机制
- 低延迟响应
应用场景
- 🎨 设计工作:快速获取设计稿中的颜色值
- 💻 前端开发:提取网页元素的颜色代码
- 🐛 调试工具:检查界面颜色是否符合设计规范
- 📊 数据分析:分析图片或界面的颜色分布
技术架构
系统架构图
┌─────────────────────────────────────────┐
│ 主进程 (main.js) │
│ ┌───────────────────────────────────┐ │
│ │ 屏幕截图获取 (desktopCapturer) │ │
│ │ 鼠标位置跟踪 (screen API) │ │
│ │ 截图缓存机制 │ │
│ └───────────────────────────────────┘ │
│ ↓ IPC 通信 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 渲染进程 (color-picker.html) │
│ ┌───────────────────────────────────┐ │
│ │ Canvas 颜色提取 │ │
│ │ 放大镜渲染 │ │
│ │ UI 更新 │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
数据流程
1. 主进程定时获取屏幕截图(16ms间隔)
↓
2. 获取鼠标当前位置
↓
3. 通过 IPC 发送截图和鼠标位置到渲染进程
↓
4. 渲染进程使用 Canvas 提取颜色
↓
5. 更新放大镜显示和颜色信息
关键技术栈
- Electron API:
desktopCapturer、screen、ipcMain、ipcRenderer - Canvas API:图像处理、像素提取、图像绘制
- 性能优化:缓存机制、节流防抖、requestAnimationFrame
屏幕截图技术
desktopCapturer API
desktopCapturer 是 Electron 提供的屏幕捕获 API,可以获取屏幕、窗口或应用的缩略图。
const { desktopCapturer } = require('electron')
// 获取屏幕截图
const sources = await desktopCapturer.getSources({
types: ['screen'], // 类型:screen, window, webview
thumbnailSize: { width: 1920, height: 1080 } // 缩略图尺寸
})
if (sources.length > 0) {
const screenshot = sources[0].thumbnail.toDataURL()
// screenshot 是 base64 编码的图片数据
}
截图参数说明
types:指定要捕获的内容类型
'screen':整个屏幕'window':应用窗口'webview':WebView 内容
thumbnailSize:缩略图尺寸
- 尺寸越大,质量越高,但性能开销也越大
- 建议使用实际屏幕分辨率
获取屏幕信息
const { screen } = require('electron')
// 获取主显示器信息
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.size
const scaleFactor = primaryDisplay.scaleFactor // 缩放因子
// 获取所有显示器
const displays = screen.getAllDisplays()
// 获取鼠标位置
const point = screen.getCursorScreenPoint()
console.log(`鼠标位置: (${point.x}, ${point.y})`)
截图缓存优化
频繁获取屏幕截图会消耗大量资源,使用缓存可以显著提升性能:
let lastScreenshot = null
let lastScreenshotTime = 0
const SCREENSHOT_CACHE_TIME = 16 // 缓存16ms
async function getScreenshot() {
const now = Date.now()
// 如果缓存有效,直接返回
if (lastScreenshot && (now - lastScreenshotTime) < SCREENSHOT_CACHE_TIME) {
return lastScreenshot
}
// 获取新截图
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: primaryDisplay.size
})
if (sources.length > 0) {
const screenshot = sources[0].thumbnail.toDataURL()
lastScreenshot = screenshot
lastScreenshotTime = now
return screenshot
}
return lastScreenshot || null
}
缓存策略:
- 缓存时间:16ms(约 60fps)
- 缓存失效:时间超过缓存时间或手动清空
- 内存管理:避免长时间缓存大量截图
颜色提取算法
Canvas 像素提取
使用 Canvas API 从截图中提取像素颜色:
function extractColorFromScreenshot(screenshotDataUrl, x, y) {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
// 创建临时 Canvas
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// 设置画布尺寸
canvas.width = img.width
canvas.height = img.height
// 绘制图片到画布
ctx.drawImage(img, 0, 0)
// 获取指定位置的像素数据
const imageData = ctx.getImageData(x, y, 1, 1)
const pixel = imageData.data
// 返回 RGB 值
resolve({
r: pixel[0], // Red (0-255)
g: pixel[1], // Green (0-255)
b: pixel[2], // Blue (0-255)
a: pixel[3] // Alpha (0-255)
})
}
img.src = screenshotDataUrl
})
}
ImageData 数据结构
getImageData() 返回的 ImageData 对象包含像素数据:
const imageData = ctx.getImageData(x, y, width, height)
// imageData.data 是 Uint8ClampedArray
// 每个像素占 4 个字节:R, G, B, A
// 访问像素 (x, y) 的颜色
const index = (y * width + x) * 4
const r = imageData.data[index]
const g = imageData.data[index + 1]
const b = imageData.data[index + 2]
const a = imageData.data[index + 3]
颜色格式转换
RGB 转 HEX
function rgbToHex(r, g, b) {
return `#${[r, g, b]
.map(x => x.toString(16).padStart(2, '0'))
.join('')
.toUpperCase()}`
}
// 使用示例
const hex = rgbToHex(255, 128, 64) // "#FF8040"
HEX 转 RGB
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
}
// 使用示例
const rgb = hexToRgb('#FF8040') // { r: 255, g: 128, b: 64 }
RGB 转 HSL
function rgbToHsl(r, g, b) {
r /= 255
g /= 255
b /= 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h, s, l = (max + min) / 2
if (max === min) {
h = s = 0 // 无色彩
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break
case g: h = ((b - r) / d + 2) / 6; break
case b: h = ((r - g) / d + 4) / 6; break
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
}
}
图片对象缓存
避免重复加载相同的截图:
let screenshotCache = null
let screenshotImageObj = null
function extractColorFromScreenshot(screenshotDataUrl, x, y) {
return new Promise((resolve) => {
// 如果截图没有变化,使用缓存的图片对象
if (screenshotCache === screenshotDataUrl && screenshotImageObj) {
extractColorFromImage(screenshotImageObj, x, y).then(resolve)
return
}
// 新的截图,需要加载
const img = new Image()
img.onload = () => {
screenshotImageObj = img
screenshotCache = screenshotDataUrl
extractColorFromImage(img, x, y).then(resolve)
}
img.src = screenshotDataUrl
})
}
实时鼠标跟踪
获取鼠标位置
const { screen } = require('electron')
// 获取当前鼠标位置(全局坐标)
const point = screen.getCursorScreenPoint()
console.log(`鼠标位置: (${point.x}, ${point.y})`)
定时跟踪
使用 setInterval 定时获取鼠标位置:
let trackingInterval = null
function startTracking() {
trackingInterval = setInterval(() => {
const point = screen.getCursorScreenPoint()
// 处理鼠标位置
handleMouseMove(point.x, point.y)
}, 16) // 每16ms更新一次(约60fps)
}
function stopTracking() {
if (trackingInterval) {
clearInterval(trackingInterval)
trackingInterval = null
}
}
更新频率优化
帧率选择:
- 30fps (33ms):适合一般应用,性能开销小
- 60fps (16ms):流畅体验,推荐用于颜色拾取器
- 120fps (8ms):极高流畅度,但 CPU 占用高
// 60fps 配置
const UPDATE_INTERVAL = 16 // 16ms = 1000ms / 60fps
colorPickingInterval = setInterval(async () => {
const point = screen.getCursorScreenPoint()
const screenshot = await getScreenshot()
// 发送数据到渲染进程
}, UPDATE_INTERVAL)
IPC 通信
主进程发送鼠标位置和截图到渲染进程:
// 主进程
ipcMain.on('start-color-picking', () => {
setInterval(async () => {
const point = screen.getCursorScreenPoint()
const screenshot = await getScreenshot()
colorPickerWindow.webContents.send('color-data', {
x: point.x,
y: point.y,
screenshot: screenshot,
timestamp: Date.now()
})
}, 16)
})
// 渲染进程
ipcRenderer.on('color-data', (event, data) => {
const { x, y, screenshot } = data
updateColorDisplay(x, y, screenshot)
})
Canvas API 应用
放大镜实现
使用 Canvas 绘制放大后的屏幕区域:
function drawMagnifier(img, mouseX, mouseY, scale) {
const canvas = document.getElementById('magnifierCanvas')
const ctx = canvas.getContext('2d')
// 计算放大区域
const sourceSize = canvas.width / scale
const sourceX = mouseX - sourceSize / 2
const sourceY = mouseY - sourceSize / 2
// 关闭图像平滑,实现像素化效果
ctx.imageSmoothingEnabled = false
// 绘制放大后的图像
ctx.drawImage(
img,
sourceX, sourceY, sourceSize, sourceSize, // 源区域
0, 0, canvas.width, canvas.height // 目标区域
)
// 绘制十字准线
drawCrosshair(ctx, canvas.width, canvas.height)
}
function drawCrosshair(ctx, width, height) {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'
ctx.lineWidth = 1
// 垂直线
ctx.beginPath()
ctx.moveTo(width / 2, 0)
ctx.lineTo(width / 2, height)
ctx.stroke()
// 水平线
ctx.beginPath()
ctx.moveTo(0, height / 2)
ctx.lineTo(width, height / 2)
ctx.stroke()
// 中心圆圈
ctx.beginPath()
ctx.arc(width / 2, height / 2, 10, 0, Math.PI * 2)
ctx.stroke()
}
图像平滑控制
// 关闭图像平滑,实现像素化效果
ctx.imageSmoothingEnabled = false
// 或者使用更精细的控制
ctx.imageSmoothingEnabled = true
ctx.imageSmoothingQuality = 'high' // 'low', 'medium', 'high'
性能优化技巧
1. 使用离屏 Canvas
// 创建离屏 Canvas 用于预处理
const offscreenCanvas = document.createElement('canvas')
const offscreenCtx = offscreenCanvas.getContext('2d')
// 在离屏 Canvas 上绘制
offscreenCtx.drawImage(img, 0, 0)
// 将离屏 Canvas 内容复制到主 Canvas
ctx.drawImage(offscreenCanvas, 0, 0)
2. 批量像素操作
// 一次性获取多个像素
const imageData = ctx.getImageData(x, y, width, height)
// 批量处理像素
for (let i = 0; i < imageData.data.length; i += 4) {
const r = imageData.data[i]
const g = imageData.data[i + 1]
const b = imageData.data[i + 2]
// 处理像素...
}
// 一次性写回
ctx.putImageData(imageData, x, y)
3. 使用 requestAnimationFrame
let animationFrameId = null
function updateDisplay() {
// 取消之前的动画帧
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}
// 使用 requestAnimationFrame 优化渲染
animationFrameId = requestAnimationFrame(() => {
// 渲染逻辑
drawMagnifier(img, mouseX, mouseY, scale)
})
}
性能优化策略
1. 截图缓存
let lastScreenshot = null
let lastScreenshotTime = 0
const CACHE_TIME = 16 // 16ms
async function getScreenshot() {
const now = Date.now()
// 缓存有效,直接返回
if (lastScreenshot && (now - lastScreenshotTime) < CACHE_TIME) {
return lastScreenshot
}
// 获取新截图
const screenshot = await captureScreen()
lastScreenshot = screenshot
lastScreenshotTime = now
return screenshot
}
2. 图片对象复用
let screenshotImageObj = null
let screenshotCache = null
function loadScreenshot(dataUrl) {
// 如果截图相同,复用图片对象
if (screenshotCache === dataUrl && screenshotImageObj) {
return Promise.resolve(screenshotImageObj)
}
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
screenshotImageObj = img
screenshotCache = dataUrl
resolve(img)
}
img.onerror = reject
img.src = dataUrl
})
}
3. 节流和防抖
// 节流:限制更新频率
let lastUpdateTime = 0
const UPDATE_THROTTLE = 16
function updateColor(data) {
const now = performance.now()
if (now - lastUpdateTime >= UPDATE_THROTTLE) {
lastUpdateTime = now
processUpdate(data)
} else {
// 保存最新数据,等待下次更新
pendingUpdate = data
}
}
// 防抖:延迟执行
let debounceTimer = null
function onApertureChange(value) {
clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
updateMagnifier(value)
}, 50)
}
4. 待处理队列
let isProcessing = false
let pendingUpdate = null
async function updateMagnifier(data) {
// 如果正在处理,保存最新请求
if (isProcessing) {
pendingUpdate = data
return
}
isProcessing = true
try {
await processUpdate(data)
} finally {
isProcessing = false
// 处理待处理的更新
if (pendingUpdate) {
const next = pendingUpdate
pendingUpdate = null
updateMagnifier(next)
}
}
}
5. 内存管理
function cleanup() {
// 清空缓存
lastScreenshot = null
screenshotImageObj = null
screenshotCache = null
// 取消动画帧
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
// 清空 Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
完整代码实现
主进程代码 (main.js)
const { app, BrowserWindow, ipcMain, screen, desktopCapturer } = require('electron')
let colorPickerWindow = null
let isColorPicking = false
let colorPickingInterval = null
let lastScreenshot = null
let lastScreenshotTime = 0
const SCREENSHOT_CACHE_TIME = 16
// 创建颜色拾取器窗口
function createColorPickerWindow() {
if (colorPickerWindow) {
colorPickerWindow.focus()
return
}
colorPickerWindow = new BrowserWindow({
width: 900,
height: 600,
title: '数码测色计',
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
colorPickerWindow.loadFile('color-picker.html')
colorPickerWindow.on('closed', () => {
colorPickerWindow = null
if (isColorPicking) {
stopColorPicking()
}
})
}
// 获取屏幕截图(带缓存)
async function getScreenshot() {
const now = Date.now()
if (lastScreenshot && (now - lastScreenshotTime) < SCREENSHOT_CACHE_TIME) {
return lastScreenshot
}
try {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.size
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: { width, height }
})
if (sources.length > 0) {
const screenshot = sources[0].thumbnail.toDataURL()
lastScreenshot = screenshot
lastScreenshotTime = now
return screenshot
}
} catch (error) {
console.error('获取截图失败:', error)
}
return lastScreenshot || null
}
// 开始颜色拾取
ipcMain.on('start-color-picking', () => {
if (isColorPicking) return
isColorPicking = true
lastScreenshot = null
lastScreenshotTime = 0
colorPickingInterval = setInterval(async () => {
if (!isColorPicking || !colorPickerWindow) return
try {
const point = screen.getCursorScreenPoint()
const screenshot = await getScreenshot()
if (screenshot && colorPickerWindow) {
colorPickerWindow.webContents.send('color-data', {
x: point.x,
y: point.y,
screenshot: screenshot,
timestamp: Date.now()
})
}
} catch (error) {
console.error('颜色拾取错误:', error)
}
}, 16) // 60fps
})
// 停止颜色拾取
ipcMain.on('stop-color-picking', () => {
stopColorPicking()
})
function stopColorPicking() {
isColorPicking = false
if (colorPickingInterval) {
clearInterval(colorPickingInterval)
colorPickingInterval = null
}
lastScreenshot = null
lastScreenshotTime = 0
}
// 处理打开颜色拾取器的请求
ipcMain.handle('open-color-picker', () => {
createColorPickerWindow()
})
渲染进程代码 (color-picker.html)
const { ipcRenderer } = require('electron')
let isCapturing = false
let apertureSize = 10
let screenshotCache = null
let screenshotImageObj = null
let isProcessing = false
let pendingUpdate = null
let animationFrameId = null
const tempCanvas = document.createElement('canvas')
const tempCtx = tempCanvas.getContext('2d')
const magnifierCanvas = document.getElementById('magnifierCanvas')
const magnifierCtx = magnifierCanvas.getContext('2d')
magnifierCanvas.width = 300
magnifierCanvas.height = 300
// 从截图中提取颜色
function extractColorFromScreenshot(screenshotDataUrl, x, y) {
return new Promise((resolve) => {
if (screenshotCache === screenshotDataUrl && screenshotImageObj) {
extractColorFromImage(screenshotImageObj, x, y).then(resolve)
return
}
const img = new Image()
img.onload = () => {
screenshotImageObj = img
screenshotCache = screenshotDataUrl
extractColorFromImage(img, x, y).then(resolve)
}
img.onerror = () => resolve({ r: 0, g: 0, b: 0 })
img.src = screenshotDataUrl
})
}
function extractColorFromImage(img, x, y) {
return new Promise((resolve) => {
if (tempCanvas.width !== img.width || tempCanvas.height !== img.height) {
tempCanvas.width = img.width
tempCanvas.height = img.height
}
tempCtx.drawImage(img, 0, 0)
const imageData = tempCtx.getImageData(x, y, 1, 1)
const pixel = imageData.data
resolve({
r: pixel[0],
g: pixel[1],
b: pixel[2]
})
})
}
// 更新放大镜
async function updateMagnifier(data) {
if (!data || !data.screenshot) return
if (isProcessing) {
pendingUpdate = data
return
}
isProcessing = true
const { screenshot, x, y } = data
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}
animationFrameId = requestAnimationFrame(async () => {
try {
let img = screenshotImageObj
if (screenshotCache !== screenshot || !img) {
img = new Image()
await new Promise((resolve, reject) => {
img.onload = resolve
img.onerror = reject
img.src = screenshot
})
screenshotImageObj = img
screenshotCache = screenshot
}
const scale = apertureSize
const sourceSize = 300 / scale
const sourceX = Math.max(0, x - sourceSize / 2)
const sourceY = Math.max(0, y - sourceSize / 2)
magnifierCtx.imageSmoothingEnabled = false
magnifierCtx.drawImage(
img,
sourceX, sourceY, sourceSize, sourceSize,
0, 0, 300, 300
)
const color = await extractColorFromScreenshot(screenshot, x, y)
updateColorDisplay({ ...color, x, y })
} catch (error) {
console.error('更新失败:', error)
} finally {
isProcessing = false
if (pendingUpdate) {
const next = pendingUpdate
pendingUpdate = null
updateMagnifier(next)
}
}
})
}
// 更新颜色显示
function updateColorDisplay(data) {
const { r, g, b, x, y } = data
document.getElementById('colorSwatch').style.backgroundColor = `rgb(${r}, ${g}, ${b})`
document.getElementById('redValue').textContent = r
document.getElementById('greenValue').textContent = g
document.getElementById('blueValue').textContent = b
const hex = `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`.toUpperCase()
document.getElementById('hexValue').textContent = hex
document.getElementById('pixelInfo').textContent = `位置: (${x}, ${y})`
}
// 开始拾色
function startColorPicking() {
isCapturing = true
ipcRenderer.send('start-color-picking')
let lastUpdateTime = 0
const UPDATE_THROTTLE = 16
ipcRenderer.on('color-data', (event, data) => {
if (!isCapturing) return
const now = performance.now()
if (now - lastUpdateTime >= UPDATE_THROTTLE) {
lastUpdateTime = now
updateMagnifier(data)
} else {
pendingUpdate = data
}
})
}
// 停止拾色
function stopColorPicking() {
isCapturing = false
ipcRenderer.send('stop-color-picking')
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
screenshotCache = null
screenshotImageObj = null
pendingUpdate = null
isProcessing = false
ipcRenderer.removeAllListeners('color-data')
}
功能扩展
1. 颜色历史记录
let colorHistory = []
function addToHistory(color) {
colorHistory.unshift(color)
if (colorHistory.length > 20) {
colorHistory.pop()
}
updateHistoryDisplay()
}
function updateHistoryDisplay() {
const container = document.getElementById('colorHistory')
container.innerHTML = colorHistory.map((color, index) => {
return `
<div class="history-item" data-index="${index}">
<div class="history-swatch" style="background: rgb(${color.r}, ${color.g}, ${color.b})"></div>
<div class="history-info">
<div>RGB: ${color.r}, ${color.g}, ${color.b}</div>
<div>HEX: ${rgbToHex(color.r, color.g, color.b)}</div>
</div>
</div>
`
}).join('')
}
2. 颜色格式转换
function convertColorFormat(r, g, b, format) {
switch (format) {
case 'hex':
return rgbToHex(r, g, b)
case 'rgb':
return `rgb(${r}, ${g}, ${b})`
case 'rgba':
return `rgba(${r}, ${g}, ${b}, 1)`
case 'hsl':
const hsl = rgbToHsl(r, g, b)
return `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`
default:
return rgbToHex(r, g, b)
}
}
3. 颜色对比度检测
function getContrastRatio(color1, color2) {
const l1 = getLuminance(color1.r, color1.g, color1.b)
const l2 = getLuminance(color2.r, color2.g, color2.b)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
function getLuminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(val => {
val = val / 255
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
4. 颜色调色板生成
function generatePalette(baseColor) {
const { r, g, b } = baseColor
const palette = []
// 生成不同亮度的颜色
for (let i = 0; i < 5; i++) {
const factor = i / 4
palette.push({
r: Math.round(r * factor),
g: Math.round(g * factor),
b: Math.round(b * factor)
})
}
return palette
}
最佳实践
1. 权限处理
在 macOS 上,屏幕录制需要用户授权:
// 检查权限
const { systemPreferences } = require('electron')
async function checkScreenRecordingPermission() {
if (process.platform === 'darwin') {
const status = systemPreferences.getMediaAccessStatus('screen')
if (status !== 'granted') {
// 提示用户授权
dialog.showMessageBox({
type: 'info',
title: '需要屏幕录制权限',
message: '请在系统偏好设置中允许此应用进行屏幕录制'
})
return false
}
}
return true
}
2. 错误处理
async function getScreenshot() {
try {
const sources = await desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: primaryDisplay.size
})
if (sources.length === 0) {
throw new Error('无法获取屏幕截图')
}
return sources[0].thumbnail.toDataURL()
} catch (error) {
console.error('截图失败:', error)
// 显示用户友好的错误信息
if (error.message.includes('permission')) {
showPermissionError()
}
return null
}
}
3. 资源清理
function cleanup() {
// 停止定时器
if (colorPickingInterval) {
clearInterval(colorPickingInterval)
colorPickingInterval = null
}
// 清空缓存
lastScreenshot = null
screenshotImageObj = null
// 取消动画帧
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
// 移除事件监听器
ipcRenderer.removeAllListeners('color-data')
}
4. 性能监控
let frameCount = 0
let lastFpsTime = Date.now()
function updateFPS() {
frameCount++
const now = Date.now()
if (now - lastFpsTime >= 1000) {
const fps = frameCount
frameCount = 0
lastFpsTime = now
console.log(`FPS: ${fps}`)
document.getElementById('fpsDisplay').textContent = `FPS: ${fps}`
}
}
常见问题
1. 截图权限问题
问题:在 macOS 上无法获取屏幕截图。
解决方案:
- 系统偏好设置 → 安全性与隐私 → 屏幕录制
- 勾选 Electron 应用
- 重启应用
2. 性能问题
问题:颜色拾取器运行卡顿。
解决方案:
- 降低更新频率(从 16ms 改为 33ms)
- 减小截图尺寸
- 优化缓存策略
- 使用 Web Worker 处理颜色提取
3. 颜色不准确
问题:提取的颜色值与实际不符。
可能原因:
- 屏幕缩放因子未考虑
- 颜色空间转换问题
- 截图质量过低
解决方案:
// 考虑屏幕缩放因子
const scaleFactor = primaryDisplay.scaleFactor
const pixelX = Math.floor(x * scaleFactor)
const pixelY = Math.floor(y * scaleFactor)
4. 内存泄漏
问题:长时间运行后内存占用增加。
解决方案:
- 定期清理缓存
- 限制历史记录数量
- 及时释放图片对象
- 使用 WeakMap 存储临时数据
5. 跨平台兼容性
问题:不同平台行为不一致。
解决方案:
function getPlatformSpecificConfig() {
switch (process.platform) {
case 'darwin':
return {
screenshotInterval: 16,
cacheTime: 16
}
case 'win32':
return {
screenshotInterval: 20,
cacheTime: 20
}
case 'linux':
return {
screenshotInterval: 33,
cacheTime: 33
}
default:
return {
screenshotInterval: 33,
cacheTime: 33
}
}
}
总结
通过本文,我们学习了:
- ✅ 屏幕截图技术:使用
desktopCapturerAPI 获取屏幕截图 - ✅ 颜色提取算法:使用 Canvas API 提取像素颜色
- ✅ 实时鼠标跟踪:使用
screenAPI 跟踪鼠标位置 - ✅ Canvas 应用:实现放大镜和图像处理
- ✅ 性能优化:缓存、节流、防抖等优化策略
- ✅ IPC 通信:主进程与渲染进程的数据传递
关键要点
- 截图缓存可以显著减少性能开销
- requestAnimationFrame提供流畅的渲染体验
- 节流和防抖避免过度更新
- 图片对象复用减少内存分配
- 错误处理对于提升用户体验至关重要
下一步学习
祝您开发愉快! 🚀
更多推荐

所有评论(0)