Electron 颜色拾取器开发实战适配鸿蒙
Electron 颜色拾取器开发实战适配鸿蒙
目录
- 功能概述
- 技术架构
- 屏幕截图技术
- 颜色提取算法
- 实时鼠标跟踪
- Canvas API 应用
- 性能优化策略
- 完整代码实现
- 功能扩展
- 最佳实践
- 常见问题
功能概述
颜色拾取器(数码测色计)是一个实时屏幕颜色检测工具,能够精确获取屏幕上任意位置的颜色值。该工具广泛应用于设计、开发、调试等场景。
核心功能
-
实时颜色拾取
- 实时跟踪鼠标位置
- 精确获取像素颜色值
- 支持 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, webviewthumbnailSize: { 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 // 缓存16msasync 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 = screenshotlastScreenshotTime = nowreturn screenshot}return lastScreenshot || null
}
缓存策略:
- 缓存时间:16ms(约 60fps)
- 缓存失效:时间超过缓存时间或手动清空
- 内存管理:避免长时间缓存大量截图
颜色提取算法
Canvas 像素提取
使用 Canvas API 从截图中提取像素颜色:
function extractColorFromScreenshot(screenshotDataUrl, x, y) {return new Promise((resolve) => {const img = new Image()img.onload = () => {// 创建临时 Canvasconst canvas = document.createElement('canvas')const ctx = canvas.getContext('2d')// 设置画布尺寸canvas.width = img.widthcanvas.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 /= 255g /= 255b /= 255const max = Math.max(r, g, b)const min = Math.min(r, g, b)let h, s, l = (max + min) / 2if (max === min) {h = s = 0 // 无色彩} else {const d = max - mins = l > 0.5 ? d / (2 - max - min) : d / (max + min)switch (max) {case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; breakcase g: h = ((b - r) / d + 2) / 6; breakcase 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 = nullfunction 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 = imgscreenshotCache = screenshotDataUrlextractColorFromImage(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 = nullfunction 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 / 60fpscolorPickingInterval = 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 } = dataupdateColorDisplay(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 / scaleconst sourceX = mouseX - sourceSize / 2const 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 = nullfunction updateDisplay() {// 取消之前的动画帧if (animationFrameId) {cancelAnimationFrame(animationFrameId)}// 使用 requestAnimationFrame 优化渲染animationFrameId = requestAnimationFrame(() => {// 渲染逻辑drawMagnifier(img, mouseX, mouseY, scale)})
}
性能优化策略
1. 截图缓存
let lastScreenshot = null
let lastScreenshotTime = 0
const CACHE_TIME = 16 // 16msasync function getScreenshot() {const now = Date.now()// 缓存有效,直接返回if (lastScreenshot && (now - lastScreenshotTime) < CACHE_TIME) {return lastScreenshot}// 获取新截图const screenshot = await captureScreen()lastScreenshot = screenshotlastScreenshotTime = nowreturn screenshot
}
2. 图片对象复用
let screenshotImageObj = null
let screenshotCache = nullfunction loadScreenshot(dataUrl) {// 如果截图相同,复用图片对象if (screenshotCache === dataUrl && screenshotImageObj) {return Promise.resolve(screenshotImageObj)}return new Promise((resolve, reject) => {const img = new Image()img.onload = () => {screenshotImageObj = imgscreenshotCache = dataUrlresolve(img)}img.onerror = rejectimg.src = dataUrl})
}
3. 节流和防抖
// 节流:限制更新频率
let lastUpdateTime = 0
const UPDATE_THROTTLE = 16function updateColor(data) {const now = performance.now()if (now - lastUpdateTime >= UPDATE_THROTTLE) {lastUpdateTime = nowprocessUpdate(data)} else {// 保存最新数据,等待下次更新pendingUpdate = data}
}// 防抖:延迟执行
let debounceTimer = nullfunction onApertureChange(value) {clearTimeout(debounceTimer)debounceTimer = setTimeout(() => {updateMagnifier(value)}, 50)
}
4. 待处理队列
let isProcessing = false
let pendingUpdate = nullasync function updateMagnifier(data) {// 如果正在处理,保存最新请求if (isProcessing) {pendingUpdate = datareturn}isProcessing = truetry {await processUpdate(data)} finally {isProcessing = false// 处理待处理的更新if (pendingUpdate) {const next = pendingUpdatependingUpdate = nullupdateMagnifier(next)}}
}
5. 内存管理
function cleanup() {// 清空缓存lastScreenshot = nullscreenshotImageObj = nullscreenshotCache = null// 取消动画帧if (animationFrameId) {cancelAnimationFrame(animationFrameId)animationFrameId = null}// 清空 Canvasctx.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 = nullif (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.sizeconst sources = await desktopCapturer.getSources({types: ['screen'],thumbnailSize: { width, height }})if (sources.length > 0) {const screenshot = sources[0].thumbnail.toDataURL()lastScreenshot = screenshotlastScreenshotTime = nowreturn screenshot}} catch (error) {console.error('获取截图失败:', error)}return lastScreenshot || null
}// 开始颜色拾取
ipcMain.on('start-color-picking', () => {if (isColorPicking) returnisColorPicking = truelastScreenshot = nulllastScreenshotTime = 0colorPickingInterval = setInterval(async () => {if (!isColorPicking || !colorPickerWindow) returntry {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 = falseif (colorPickingInterval) {clearInterval(colorPickingInterval)colorPickingInterval = null}lastScreenshot = nulllastScreenshotTime = 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 = nullconst 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 = imgscreenshotCache = screenshotDataUrlextractColorFromImage(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.widthtempCanvas.height = img.height}tempCtx.drawImage(img, 0, 0)const imageData = tempCtx.getImageData(x, y, 1, 1)const pixel = imageData.dataresolve({r: pixel[0],g: pixel[1],b: pixel[2]})})
}// 更新放大镜
async function updateMagnifier(data) {if (!data || !data.screenshot) returnif (isProcessing) {pendingUpdate = datareturn}isProcessing = trueconst { screenshot, x, y } = dataif (animationFrameId) {cancelAnimationFrame(animationFrameId)}animationFrameId = requestAnimationFrame(async () => {try {let img = screenshotImageObjif (screenshotCache !== screenshot || !img) {img = new Image()await new Promise((resolve, reject) => {img.onload = resolveimg.onerror = rejectimg.src = screenshot})screenshotImageObj = imgscreenshotCache = screenshot}const scale = apertureSizeconst sourceSize = 300 / scaleconst sourceX = Math.max(0, x - sourceSize / 2)const sourceY = Math.max(0, y - sourceSize / 2)magnifierCtx.imageSmoothingEnabled = falsemagnifierCtx.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 = falseif (pendingUpdate) {const next = pendingUpdatependingUpdate = nullupdateMagnifier(next)}}})
}// 更新颜色显示
function updateColorDisplay(data) {const { r, g, b, x, y } = datadocument.getElementById('colorSwatch').style.backgroundColor = `rgb(${r}, ${g}, ${b})`document.getElementById('redValue').textContent = rdocument.getElementById('greenValue').textContent = gdocument.getElementById('blueValue').textContent = bconst hex = `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`.toUpperCase()document.getElementById('hexValue').textContent = hexdocument.getElementById('pixelInfo').textContent = `位置: (${x}, ${y})`
}// 开始拾色
function startColorPicking() {isCapturing = trueipcRenderer.send('start-color-picking')let lastUpdateTime = 0const UPDATE_THROTTLE = 16ipcRenderer.on('color-data', (event, data) => {if (!isCapturing) returnconst now = performance.now()if (now - lastUpdateTime >= UPDATE_THROTTLE) {lastUpdateTime = nowupdateMagnifier(data)} else {pendingUpdate = data}})
}// 停止拾色
function stopColorPicking() {isCapturing = falseipcRenderer.send('stop-color-picking')if (animationFrameId) {cancelAnimationFrame(animationFrameId)animationFrameId = null}screenshotCache = nullscreenshotImageObj = nullpendingUpdate = nullisProcessing = falseipcRenderer.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 / 255return 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 } = baseColorconst palette = []// 生成不同亮度的颜色for (let i = 0; i < 5; i++) {const factor = i / 4palette.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 = nullscreenshotImageObj = 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 = frameCountframeCount = 0lastFpsTime = nowconsole.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提供流畅的渲染体验
- 节流和防抖避免过度更新
- 图片对象复用减少内存分配
- 错误处理对于提升用户体验至关重要
下一步学习
- IPC 通信详解 - 深入学习 IPC 机制
- 原生API集成 - 更多系统 API
- 性能优化指南 - 性能优化技巧
祝您开发愉快! 🚀
