Electron屏幕捕获:实现屏幕截图与录屏功能

Electron屏幕捕获:实现屏幕截图与录屏功能

【免费下载链接】electron 使用Electron构建跨平台桌面应用程序,支持JavaScript、HTML和CSS 【免费下载链接】electron 项目地址: https://gitcode.com/GitHub_Trending/el/electron

在桌面应用开发中,屏幕捕获是一个常见且重要的功能需求。无论是用于远程协助、教学演示、游戏录制还是应用监控,屏幕截图和录屏功能都扮演着关键角色。Electron作为跨平台桌面应用开发框架,提供了强大的desktopCapturer API来实现这些功能。

本文将深入探讨如何在Electron应用中实现专业的屏幕捕获功能,包括截图、录屏以及相关的权限处理和优化技巧。

屏幕捕获技术概览

Electron的屏幕捕获功能基于Chromium的桌面捕获API,主要通过desktopCapturer模块实现。该模块允许应用访问桌面上的媒体源,包括屏幕和应用程序窗口。

核心API介绍

const { desktopCapturer } = require('electron')

// 获取可用的媒体源
desktopCapturer.getSources({
  types: ['screen', 'window'],
  thumbnailSize: { width: 150, height: 150 },
  fetchWindowIcons: true
}).then(sources => {
  console.log('Available sources:', sources)
})

实现屏幕截图功能

基础截图实现

以下是一个完整的屏幕截图示例,包含主进程、预加载脚本和渲染进程的完整实现:

// main.js - 主进程
const { app, BrowserWindow, desktopCapturer, ipcMain, shell } = require('electron')
const fs = require('fs').promises
const path = require('path')
const os = require('os')

let mainWindow = null

// 计算截图尺寸
function calculateScreenshotSize(devicePixelRatio) {
  const { width, height } = require('electron').screen.getPrimaryDisplay().workAreaSize
  const maxDimension = Math.max(width, height)
  return {
    width: maxDimension * devicePixelRatio,
    height: maxDimension * devicePixelRatio
  }
}

// 截图处理函数
async function takeScreenshot(devicePixelRatio) {
  try {
    const thumbSize = calculateScreenshotSize(devicePixelRatio)
    const sources = await desktopCapturer.getSources({
      types: ['screen'],
      thumbnailSize: thumbSize
    })

    // 查找主屏幕
    const primaryScreen = sources.find(source => 
      source.name.toLowerCase().includes('screen') || 
      source.name.toLowerCase().includes('entire')
    )

    if (primaryScreen) {
      const screenshotPath = path.join(os.tmpdir(), `screenshot_${Date.now()}.png`)
      await fs.writeFile(screenshotPath, primaryScreen.thumbnail.toPNG())
      
      // 在文件管理器中打开截图
      shell.showItemInFolder(screenshotPath)
      return { success: true, path: screenshotPath }
    }
    
    return { success: false, error: 'No screen found' }
  } catch (error) {
    return { success: false, error: error.message }
  }
}

// IPC处理器
ipcMain.handle('take-screenshot', async (event, devicePixelRatio) => {
  return await takeScreenshot(devicePixelRatio)
})

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      enableRemoteModule: false
    }
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(createWindow)
// preload.js - 预加载脚本
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  takeScreenshot: () => ipcRenderer.invoke('take-screenshot', window.devicePixelRatio)
})
<!-- index.html - 渲染进程界面 -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>屏幕截图工具</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 20px; }
        .container { max-width: 600px; margin: 0 auto; }
        button { 
            background: #007acc; color: white; border: none; 
            padding: 12px 24px; border-radius: 6px; cursor: pointer;
            font-size: 16px; margin: 10px 0;
        }
        button:hover { background: #005a9e; }
        .status { margin: 10px 0; color: #666; }
        .error { color: #d13438; }
        .success { color: #107c10; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Electron屏幕截图工具</h1>
        <button id="screenshotBtn">拍摄屏幕截图</button>
        <div id="status" class="status"></div>
    </div>
    <script src="renderer.js"></script>
</body>
</html>
// renderer.js - 渲染进程逻辑
const screenshotBtn = document.getElementById('screenshotBtn')
const statusDiv = document.getElementById('status')

screenshotBtn.addEventListener('click', async () => {
    statusDiv.textContent = '正在捕获屏幕...'
    statusDiv.className = 'status'
    
    try {
        const result = await window.electronAPI.takeScreenshot()
        
        if (result.success) {
            statusDiv.textContent = `截图已保存: ${result.path}`
            statusDiv.className = 'status success'
        } else {
            statusDiv.textContent = `错误: ${result.error}`
            statusDiv.className = 'status error'
        }
    } catch (error) {
        statusDiv.textContent = `捕获失败: ${error.message}`
        statusDiv.className = 'status error'
    }
})

高级截图功能

多屏幕支持
// 支持多显示器截图
async function captureAllScreens(devicePixelRatio) {
  const displays = require('electron').screen.getAllDisplays()
  const screenshots = []

  for (const display of displays) {
    const thumbSize = {
      width: display.bounds.width * devicePixelRatio,
      height: display.bounds.height * devicePixelRatio
    }

    const sources = await desktopCapturer.getSources({
      types: ['screen'],
      thumbnailSize: thumbSize
    })

    const screenSource = sources.find(source => 
      source.display_id === display.id.toString()
    )

    if (screenSource) {
      const screenshotPath = path.join(os.tmpdir(), `screen_${display.id}_${Date.now()}.png`)
      await fs.writeFile(screenshotPath, screenSource.thumbnail.toPNG())
      screenshots.push({ display: display.id, path: screenshotPath })
    }
  }

  return screenshots
}
区域截图
// 实现区域截图功能
async function captureRegion(devicePixelRatio, region) {
  const sources = await desktopCapturer.getSources({
    types: ['screen'],
    thumbnailSize: {
      width: require('electron').screen.getPrimaryDisplay().bounds.width * devicePixelRatio,
      height: require('electron').screen.getPrimaryDisplay().bounds.height * devicePixelRatio
    }
  })

  const primaryScreen = sources.find(source => 
    source.name.toLowerCase().includes('screen')
  )

  if (primaryScreen) {
    const image = primaryScreen.thumbnail
    const { x, y, width, height } = region
    
    // 创建canvas进行区域裁剪
    const canvas = document.createElement('canvas')
    canvas.width = width
    canvas.height = height
    const ctx = canvas.getContext('2d')
    
    ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
    
    return canvas.toDataURL('image/png')
  }
  
  return null
}

实现屏幕录制功能

基础录屏实现

// 屏幕录制功能
class ScreenRecorder {
  constructor() {
    this.mediaRecorder = null
    this.recordedChunks = []
    this.isRecording = false
  }

  async startRecording() {
    try {
      // 获取屏幕流
      const stream = await navigator.mediaDevices.getDisplayMedia({
        video: {
          cursor: 'always',
          width: { ideal: 1920 },
          height: { ideal: 1080 },
          frameRate: { ideal: 30 }
        },
        audio: true
      })

      this.mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'video/webm; codecs=vp9',
        videoBitsPerSecond: 5000000
      })

      this.mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 0) {
          this.recordedChunks.push(event.data)
        }
      }

      this.mediaRecorder.onstop = () => {
        this.saveRecording()
      }

      this.mediaRecorder.start(1000) // 每1秒收集一次数据
      this.isRecording = true

    } catch (error) {
      console.error('Recording failed:', error)
      throw error
    }
  }

  stopRecording() {
    if (this.mediaRecorder && this.isRecording) {
      this.mediaRecorder.stop()
      this.isRecording = false
      
      // 停止所有轨道
      this.mediaRecorder.stream.getTracks().forEach(track => track.stop())
    }
  }

  async saveRecording() {
    const blob = new Blob(this.recordedChunks, { type: 'video/webm' })
    const buffer = await blob.arrayBuffer()
    
    // 通过IPC发送到主进程保存
    const result = await window.electronAPI.saveVideo(buffer, 'recording.webm')
    
    if (result.success) {
      console.log('Recording saved:', result.path)
    } else {
      console.error('Save failed:', result.error)
    }
    
    this.recordedChunks = []
  }
}

录屏功能集成

// 主进程录屏支持
const { ipcMain } = require('electron')
const fs = require('fs').promises

ipcMain.handle('save-video', async (event, buffer, filename) => {
  try {
    const videosDir = path.join(os.homedir(), 'Videos')
    await fs.mkdir(videosDir, { recursive: true })
    
    const filePath = path.join(videosDir, filename)
    await fs.writeFile(filePath, Buffer.from(buffer))
    
    return { success: true, path: filePath }
  } catch (error) {
    return { success: false, error: error.message }
  }
})

// 预加载脚本暴露API
contextBridge.exposeInMainWorld('recorderAPI', {
  saveVideo: (buffer, filename) => 
    ipcRenderer.invoke('save-video', buffer, filename)
})

权限处理与用户体验

macOS权限处理

// 检查屏幕录制权限
async function checkScreenRecordingPermission() {
  if (process.platform === 'darwin') {
    const { systemPreferences } = require('electron')
    const status = await systemPreferences.getMediaAccessStatus('screen')
    
    if (status !== 'granted') {
      // 请求权限
      const granted = await systemPreferences.askForMediaAccess('screen')
      if (!granted) {
        throw new Error('Screen recording permission denied')
      }
    }
  }
}

// 在应用启动时检查权限
app.whenReady().then(async () => {
  await checkScreenRecordingPermission()
  createWindow()
})

Windows权限处理

// Windows系统权限检查
function checkWindowsPermissions() {
  if (process.platform === 'win32') {
    // Windows通常不需要特殊权限,但可以检查其他条件
    const version = os.release()
    console.log('Windows version:', version)
  }
}

性能优化与最佳实践

内存管理

// 优化内存使用的截图功能
class OptimizedScreenshot {
  constructor() {
    this.screenshotCache = new Map()
    this.cacheTimeout = 30000 // 30秒缓存
  }

  async getScreenshot(forceRefresh = false) {
    const cacheKey = 'current_screen'
    
    if (!forceRefresh && this.screenshotCache.has(cacheKey)) {
      const cached = this.screenshotCache.get(cacheKey)
      if (Date.now() - cached.timestamp < this.cacheTimeout) {
        return cached.data
      }
    }

    const screenshot = await this.captureScreen()
    this.screenshotCache.set(cacheKey, {
      data: screenshot,
      timestamp: Date.now()
    })

    return screenshot
  }

  async captureScreen() {
    // 实现截图逻辑
    const sources = await desktopCapturer.getSources({
      types: ['screen'],
      thumbnailSize: { width: 800, height: 600 } // 优化尺寸
    })
    
    return sources[0]?.thumbnail.toPNG()
  }

  clearCache() {
    this.screenshotCache.clear()
  }
}

错误处理与重试机制

// 健壮的错误处理
async function robustScreenCapture(maxRetries = 3) {
  let lastError = null
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await desktopCapturer.getSources({
        types: ['screen'],
        thumbnailSize: { width: 150, height: 150 }
      })
    } catch (error) {
      lastError = error
      console.warn(`Attempt ${attempt} failed:`, error.message)
      
      if (attempt < maxRetries) {
        // 指数退避重试
        await new Promise(resolve => 
          setTimeout(resolve, Math.pow(2, attempt) * 1000)
        )
      }
    }
  }
  
  throw lastError
}

安全考虑

内容安全策略

<!-- 安全的内容安全策略 -->
<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob:;
">

安全的IPC通信

// 安全的IPC通信验证
ipcMain.handle('secure-screenshot', (event, ...args) => {
  // 验证请求来源

【免费下载链接】electron 使用Electron构建跨平台桌面应用程序,支持JavaScript、HTML和CSS 【免费下载链接】electron 项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值