Разработан инструмент для создания снимков экрана с помощью электрона.

JavaScript Electron

Сделал один некоторое время назадLinux-версия DingTalk, поскольку он основан на веб-версии, в нем отсутствуют многие функции настольных приложений. Поскольку большинство пользователей, которые его используют, являются пользователями Linux, в Linux нет функции скриншота, поэтому я решил сделать функцию скриншота по просьбе нескольких пользователей.

В настоящее время проект поддерживает скриншоты мониторов, и эффект идеально подходит для Windows. Есть определенные ошибки в Linux. В настоящее время он не может поддерживать функцию кросс-скриншотов (один скриншот охватывает два монитора). Эта статья также опубликована в Цзяньшу. также можно пойти в Цзяньшу, чтобы прочитать, порталу-у-у. Краткое описание.com/afraid/276ah 29, а не 28…

электронный скриншот API

предоставляется в электронном видеdesktopCapturerМодуль, этот модуль можно использовать только в процессе рендеринга.
Этот модуль предоставляет только один методdesktopCapturer.getSources(options, callback):

  • options — это объект, содержащий два параметра
    • типы: массив строк, в котором перечислены типы ресурсов рабочего стола, которые могут быть захвачены, доступные типы: экран и окно.
    • thumbnailSize (необязательно): рекомендуется масштабировать размер миниатюры, по умолчанию {ширина: 150, высота: 150}.
  • callback(error, sources) — это функция обратного вызова, которая передает два параметра:
    • error: сообщение об ошибке при неудачном получении снимка экрана
    • sources: представляет собой массив объектов Source, каждый Source представляет захваченный экран или отдельное окно и имеет следующие свойства
      • id: идентификатор окна захвата или экрана, используемый в navigator.webkitGetUserMedia, в формате window:XX или screen:XX, XX — случайное число.
      • имя: имя описания окна захвата или экрана, если ресурс является экраном, имя Весь экран или Экран; если ресурс является окном, имя является заголовком окна
      • миниатюра: миниатюра экрана

Написание функции скриншота

  1. Чтобы соответствовать привычкам большинства пользователей, особенно тех, кто привык к функции скриншота QQ, используются сочетания клавиш.ctrl+alt+aсделать скриншот
  2. Весь код обработки программы должен дождаться события готовности приложения перед обработкой, иначе будет сообщено об ошибке, поэтому весь код помещается в функцию обратного вызова события готовности.
  3. Чтобы отделить функцию скриншота и не мешать другим модулям, основной код процесса, относящийся к скриншоту, записывается в файл отдельно.shortcut-capture.js, и инкапсулируйте модуль как функцию, а также с помощью управления переменными, чтобы гарантировать, что модуль снимка экрана инициализации будет выполняться только один раз во всем процессе приложения.
  4. Основной код процесса выглядит следующим образом

     // 引入各个模块
     const {
       globalShortcut,
       ipcMain,
       BrowserWindow,
       clipboard,
       nativeImage
     } = require('electron')
    
     // 保证函数只执行一次
     let isRuned = false
     // 截图时会出现截图界面,如下就是保存截图窗口的数组
     const $windows = []
     // 判断是否为快捷键退出,其他的退出方式都不被允许
     let isClose = false
     module.exports = mainWindow => {
       if (isRuned) {
         return
       }
       isRuned = true
    
       // 注册全局快捷键
       globalShortcut.register('ctrl+alt+a', function () {
         mainWindow.webContents.send('shortcut-capture')
       })
    
       // 抓取截图之后显示窗口
       ipcMain.on('shortcut-capture', (e, sources) => {
         // 如果有以前的窗口就关闭以前的窗口
         // 然后根据截图资源于屏幕数据生成窗口
         closeWindow()
         sources.forEach(source => {
           createWindow(source)
         })
       })
       // 有一个窗口关闭就关闭所有的窗口
       ipcMain.on('cancel-shortcut-capture', closeWindow)
    
       // 截图窗口确认截图时把数据传递到主进程
       // 然后把数据写入到剪切板,并关闭窗口
       // 没有直接在渲染进程把数据写入剪切板是因为在Linux上会报错
       // 所以就把这一步改到主进程完成
       ipcMain.on('set-shortcut-capture', (e, dataURL) => {
         clipboard.writeImage(nativeImage.createFromDataURL(dataURL))
         closeWindow()
       })
     }
    
     // 创建窗口
     function createWindow (source) {
       // display为屏幕相关信息
       // 特别再多屏幕的时候要定位各个窗口到对应的屏幕
       const { display } = source
       const $win = new BrowserWindow({
         title: '截图',
         width: display.size.width,
         height: display.size.height,
         x: display.bounds.x,
         y: display.bounds.y,
         frame: false,
         show: false,
         transparent: true,
         resizable: false,
         alwaysOnTop: true,
         fullscreen: true,
         skipTaskbar: true,
         closable: true,
         minimizable: false,
         maximizable: false
       })
       // 全屏窗口
       setFullScreen($win, display)
       // 只能通过cancel-shortcut-capture的方式关闭窗口
       $win.on('close', e => {
         if (!isClose) {
           e.preventDefault()
         }
       })
       // 页面初始化完成之后再显示窗口
       // 并检测是否有版本更新
       $win.once('ready-to-show', () => {
         $win.show()
         $win.focus()
         // 重新调整窗口位置和大小
         setFullScreen($win, display)
       })
    
       // 当页面加载完成时通知截图窗口开始程序的执行
       $win.webContents.on('dom-ready', () => {
         $win.webContents.executeJavaScript(`window.source = ${JSON.stringify(source)}`)
         $win.webContents.send('dom-ready')
         $win.focus()
       })
       // 加载地址
       $win.loadURL(`file://${__dirname}/window/shortcut-capture.html`)
       $windows.push($win)
     }
    
     // 让窗口全屏
     function setFullScreen ($win, display) {
       $win.setBounds({
         width: display.size.width,
         height: display.size.height,
         x: display.bounds.x,
         y: display.bounds.y
       })
       $win.setAlwaysOnTop(true)
       $win.setFullScreen(true)
     }
    
     // 关闭窗口
     function closeWindow () {
       isClose = true
       while ($windows.length) {
         const $winItem = $windows.pop()
         $winItem.close()
       }
       isClose = false
     }
  5. Связь между основным процессом и процессом рендеринга осуществляется через модуль ipcMain.ipcMain получает данные процесса рендеринга, прослушивая события, передаваемые процессом рендеринга, а данные связи между двумя процессами могут быть только простыми объектами. Передача данных из основного процесса в процесс рендеринга достигается через метод send webContents.Процесс рендеринга реализуется через мониторинг событий объекта ipcRender.Этот же основной процесс может также проходитьwebContents.executeJavaScriptМетод вводит js на страницу в виде строки для выполнения.
  6. После запуска программы, когда пользователь нажимает клавишу быстрого доступа, начинается процесс рендеринга главного окна, чтобы сделать снимок экрана, а после того, как снимок экрана сделан, данные передаются основному процессу, а затем основной процесс создает новый окно и передает данные снимка экрана во вновь созданное окно. , затем подождите, пока пользователь сделает снимок экрана
     // 主进程捕获到截图快捷键就让渲染进程截图
     ipcRenderer.on('shortcut-capture', () => {
       // 获取屏幕数量
       // screen为electron的模块
       const displays = screen.getAllDisplays()
       // 每个屏幕都截图一个
       // desktopCapturer.getSources可以一次获取所有桌面的截图
       // 但由于thumbnailSize不一样所以就采用了每个桌面尺寸都捕获一张
       const getDesktopCapturer = displays.map((display, i) => {
         return new Promise((resolve, reject) => {
           desktopCapturer.getSources({
             types: ['screen'],
             thumbnailSize: display.size
           }, (error, sources) => {
             if (!error) {
               return resolve({
                 display,
                 thumbnail: sources[i].thumbnail.toDataURL()
               })
             }
             return reject(error)
           })
         })
       })
       Promise.all(getDesktopCapturer)
         .then(sources => {
           // 把数据传递到主进程
           ipcRenderer.send('shortcut-capture', sources)
         })
         .catch(error => console.log(error))
     })
  7. используется в этом проектеwebContents.executeJavaScriptМетод передачи данных скриншота на страницу
  8. После того, как процесс рендеринга получает событие dom-ready основного процесса, он начинает отрисовывать интерфейс скриншота и инициализирует функцию перетаскивания страницы для создания скриншотов. когда пользователь нажимаетESCЗакройте окно скриншота, когда нажата клавиша, чтобы выйти из скриншота
  9. Функция обрезки изображения. Обрезка изображения достигается с помощью холста. Canvas может рисовать графику на основе изображения, а затем использовать API холста для получения нарисованного изображения в качестве полезного ресурса изображения, а затем отправлять его в основной процесс. Среди них основное использование холстаctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)метод
    • где изображение - это ресурс изображения
    • sx и sy — начальные позиции исходных ресурсов изображения, которые нужно отрисовать.
    • sWidth, sHeight исходный размер изображения для отрисовки ресурсов
    • dx и dy — начальные позиции для рисования изображения на холсте.
    • dWidth и dHeight — размер изображения, нарисованного на холсте.
    • В этом проекте sx, sy, sWidth и sHeight — это размер перехватываемой области и координатное положение области относительно левого верхнего угла окна.И dx, и dy равны 0, что означает, что рисунок начинается с левый верхний угол холста, а dWidth и dHeight - размер перехватываемой области. , если dWidth, dHeight и sWidth, sHeight не равны, можно добиться масштабирования перехватываемой области, но в данном проекте 1:1
  10. После захвата воспроизводимого изображения нажмите кнопку «ОК» на панели инструментов «Снимок экрана», после чего информация об изображении будет считана с холста, а затем преобразована в URL-адрес данных и передана основному процессу.Основной процесс записывает данные изображения в буфер обмена. и закрывает окно.
  11. Из-за большого количества кода в процессе рендеринга окна скриншота он не будет здесь указан.GithubПосмотрите выше, прикрепите отношение процесса всего снимка экрана ниже
    示意图.png
    схематическая диаграмма.png

Наконец, если у вас есть время, вы также можете рассмотреть возможность извлечения функции скриншота отдельно и превращения ее в модуль, на который можно напрямую ссылаться в других электронных проектах. Пожалуйста, потерпите меня, если я пишу плохо.Адрес проекта GitHub:GitHub.com/эта молодая женщина/первая…