Недавно я пытаюсь использовать электрон, чтобы обернуть веб-версию инструмента чата в настольное приложение. Можно сказать, что в качестве инструмента чата скриншоты являются обязательной функцией. Но, к сожалению, я не нашел очень зрелой библиотеки для использования, или она может быть открыта не так, короче, я не видел готовой библиотеки, поэтому я хотел создать простой инструмент для создания скриншотов с нуля. . Давайте перейдем к следующему пункту!
идеи
Electron предоставляет API для скриншотов, который может легко получить информацию об изображении каждого экрана (в случае внешнего монитора) и каждого окна.
- Сделайте снимок экрана, затем создайте полноэкранное окно, чтобы покрыть весь экран, нарисуйте захваченное изображение на окне, а затем покройте его слоем черных полупрозрачных элементов, что выглядит так, как будто экран зафиксирован;
- Добавить эффект интерактивного выделения в окне;
- Нажмите OK, используйте положение холста, соответствующее выделению, для захвата содержимого изображения, записи в буфер обмена и сохранения изображения.
Построить проект
Сначала создайтеpackage.json
Заполните необходимую информацию для проекта, обратите внимание, что основным является входной файл.
{
"name": "electorn-capture-screen",
"version": "1.0.0",
"main": "main.js",
"repository": "https://github.com/chrisbing/electorn-capture-screen.git",
"author": "Chris",
"license": "MIT",
"scripts": {
"start": "electron ."
},
"dependencies": {
"electron": "^3.0.2"
}
}
Создайтеmain.js
, код взят из электронной официальной документации
const { app, BrowserWindow, ipcMain, globalShortcut } = require('electron')
const os = require('os')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow() {
// 创建浏览器窗口。
win = new BrowserWindow({ width: 800, height: 600 })
// 然后加载应用的 index.html。
win.loadFile('index.html')
// 打开开发者工具
win.webContents.openDevTools()
// 当 window 被关闭,这个事件会被触发。
win.on('closed', () => {
// 取消引用 window 对象,如果你的应用支持多窗口的话,
// 通常会把多个 window 对象存放在一个数组里面,
// 与此同时,你应该删除相应的元素。
win = null
})
}
// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)
// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
// 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
// 否则绝大部分应用及其菜单栏会保持激活。
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 在macOS上,当单击dock图标并且没有其他窗口打开时,
// 通常在应用程序中重新创建一个窗口。
if (win === null) {
createWindow()
}
})
Создайтеindex.html
, в html помещается кнопка для запуска операции снимка экрана
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<button id="js-capture">Capture Screen</button>
<script>
const { ipcRenderer } = require('electron')
document.getElementById('js-capture').addEventListener('click', ()=>{
ipcRenderer.send('capture-screen')
})
</script>
</body>
</html>
Такой простой электронный проект завершен, выполнитеyarn start
илиnpm start
Вы можете увидеть окно с кнопкой в окне
вызвать скриншот
Скриншот — относительно независимая функция, и могут быть глобальные сочетания клавиш и триггеры меню, которые покидают окно, поэтому триггер скриншотов должен быть реализован в основном процессе.
Это можно сделать через ipc-связь в процессе рендеринга, используя ipcRenderer для отправки событий в коде страницы и используя ipcMain в main для получения событий.
// index.html
const { ipcRenderer } = require('electron')
document.getElementById('js-capture').addEventListener('click', ()=>{
ipcRenderer.send('capture-screen')
})
Получено в основном процессеcapture-screen
мероприятие
// main.js
// 接收事件
ipcMain.on('capture-screen', captureScreen)
В то же время добавьте глобальные сочетания клавиш для запуска и отмены снимков экрана.
// main.js
// 注册全局快捷键
// globalShortcut 需要在 app ready 之后
globalShortcut.register('CmdOrCtrl+Shift+A', captureScreen)
globalShortcut.register('Esc', () => {
if (captureWin) {
captureWin.close()
captureWin = null
}
})
Используйте сочетания клавиш и события для запуска методов создания снимков экрана.captureScreen
, затем реализуйте этот метод для создания окна скриншота
Создать окно скриншота
Окно скриншота предназначено для создания полноэкранного окна и рисования изображения экрана в окне, а затем выбора изображения определенной области с помощью интерактивных операций, таких как перетаскивание мышью.
Первым шагом является создание окна
// main.js
let captureWin = null
const captureScreen = (e, args) => {
if (captureWin) {
return
}
const { screen } = require('electron')
let { width, height } = screen.getPrimaryDisplay().bounds
captureWin = new BrowserWindow({
// window 使用 fullscreen, mac 设置为 undefined, 不可为 false
fullscreen: os.platform() === 'win32' || undefined, // win
width,
height,
x: 0,
y: 0,
transparent: true,
frame: false,
skipTaskbar: true,
autoHideMenuBar: true,
movable: false,
resizable: false,
enableLargerThanScreen: true, // mac
hasShadow: false,
})
captureWin.setAlwaysOnTop(true, 'screen-saver') // mac
captureWin.setVisibleOnAllWorkspaces(true) // mac
captureWin.setFullScreenable(false) // mac
captureWin.loadFile(path.join(__dirname, 'capture.html'))
// 调试用
// captureWin.openDevTools()
captureWin.on('closed', () => {
captureWin = null
})
}
Окно должно покрывать весь экран и быть полностью сверху, что можно использовать под окнами.fullscreen
Чтобы обеспечить полноэкранный режим, полноэкранный режим под Mac переместит окно на отдельный рабочий стол, поэтому используется другой метод: в комментариях к коду отмечены соответствующие параметры разных систем, а конкретное содержимое можно просмотреть в документе.
Обратите внимание, что это окно загружает еще один html-файл, который отвечает за некую интерактивную работу со скриншотами и кадрированием.
capture.html
Первая html-структура
// capture.html
<div id="js-bg" class="bg"></div>
<div id="js-mask" class="mask"></div>
<canvas id="js-canvas" class="image-canvas"></canvas>
<div id="js-size-info" class="size-info"></div>
<div id="js-toolbar" class="toolbar">
<div class="iconfont icon-zhongzhi" id="js-tool-reset"></div>
<div class="iconfont icon-xiazai" id="js-tool-save"></div>
<div class="iconfont icon-guanbi" id="js-tool-close"></div>
<div class="iconfont icon-duihao" id="js-tool-ok"></div>
</div>
<script src="capture-renderer.js"></script>
Bg : Скриншот изображения Маска: серая маска Холст: рисует выбранную область изображения и границу Информация о размере: определяет размер диапазона перехвата. Панель инструментов: кнопка действия, используемая для отмены и сохранения и т. д. Capture-renderer.js: код js
@import "./assets/iconfont/iconfont.css";
html, body, div {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
}
.bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.image-canvas {
position: absolute;
display: none;
z-index: 1;
}
.size-info {
position: absolute;
color: #ffffff;
font-size: 12px;
background: rgba(40, 40, 40, 0.8);
padding: 5px 10px;
border-radius: 2px;
font-family: Arial Consolas sans-serif;
display: none;
z-index: 2;
}
.toolbar {
position: absolute;
color: #343434;
font-size: 12px;
background: #f5f5f5;
padding: 5px 10px;
border-radius: 4px;
font-family: Arial Consolas sans-serif;
display: none;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
z-index: 2;
align-items: center;
}
.toolbar .iconfont {
font-size: 24px;
padding: 2px 5px;
}
Каждый элемент в основном позиционируется абсолютно, а позиция контролируется js Кнопка использует iconfont , все задействованные файлы ресурсов и полные проекты можно найти по адресуGitHub - chrisbing/electorn-capture-screen: electron capture screenскачать
взаимодействие со скриншотом
Завершенные функции включают фотографирование указанной области, перетаскивание для перемещения и изменение размера выделения, отображение размера в реальном времени и панель инструментов.
сделать скриншот
// capture-renderer.js
const { ipcRenderer, clipboard, nativeImage, remote, desktopCapturer, screen } = require('electron')
const Event = require('events')
const fs = require('fs')
const { bounds: { width, height }, scaleFactor } = screen.getPrimaryDisplay()
const $canvas = document.getElementById('js-canvas')
const $bg = document.getElementById('js-bg')
const $sizeInfo = document.getElementById('js-size-info')
const $toolbar = document.getElementById('js-toolbar')
const $btnClose = document.getElementById('js-tool-close')
const $btnOk = document.getElementById('js-tool-ok')
const $btnSave = document.getElementById('js-tool-save')
const $btnReset = document.getElementById('js-tool-reset')
console.time('capture')
desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: {
width: width * scaleFactor,
height: height * scaleFactor,
}
}, (error, sources) => {
console.timeEnd('capture')
let imgSrc = sources[0].thumbnail.toDataURL()
let capture = new CaptureRenderer($canvas, $bg, imgSrc, scaleFactor)
})
screen.getPrimaryDisplay()
Можно получить размер и коэффициент масштабирования основного экрана.Коэффициент масштабирования применим на экране с высоким разрешением.На экране с высоким разрешением физический размер экрана не совпадает с размером окна.Как правило, существует будет коэффициент масштабирования, например, 2-кратный и 3-кратный, поэтому для получения высокой четкости скриншот размера экрана необходимо умножить на коэффициент масштабирования.
desktopCapturer
Чтобы получить информацию о снимке экрана, получается массив, содержащий информацию о каждом экране, здесь временно обрабатывается только информация о первом экране.
После получения информации о снимке экрана создайте CaptureRenderer для интерактивной обработки.
CaptureRenderer
// capture-renderer.js
class CaptureRenderer extends Event {
constructor($canvas, $bg, imageSrc, scaleFactor) {
super()
// ...
this.init().then(() => {
console.log('init')
})
}
async init() {
this.$bg.style.backgroundImage = `url(${this.imageSrc})`
this.$bg.style.backgroundSize = `${width}px ${height}px`
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
let img = await new Promise(resolve => {
let img = new Image()
img.src = this.imageSrc
if (img.complete) {
resolve(img)
} else {
img.onload = () => resolve(img)
}
})
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
this.bgCtx = ctx
// ...
}
// ...
onMouseDrag(e) {
// ...
this.selectRect = {x, y, w, h, r, b}
this.drawRect()
this.emit('dragging', this.selectRect)
// ...
}
drawRect() {
if (!this.selectRect) {
this.$canvas.style.display = 'none'
return
}
const { x, y, w, h } = this.selectRect
const scaleFactor = this.scaleFactor
let margin = 7
let radius = 5
this.$canvas.style.left = `${x - margin}px`
this.$canvas.style.top = `${y - margin}px`
this.$canvas.style.width = `${w + margin * 2}px`
this.$canvas.style.height = `${h + margin * 2}px`
this.$canvas.style.display = 'block'
this.$canvas.width = (w + margin * 2) * scaleFactor
this.$canvas.height = (h + margin * 2) * scaleFactor
if (w && h) {
let imageData = this.bgCtx.getImageData(x * scaleFactor, y * scaleFactor, w * scaleFactor, h * scaleFactor)
this.ctx.putImageData(imageData, margin * scaleFactor, margin * scaleFactor)
}
this.ctx.fillStyle = '#ffffff'
this.ctx.strokeStyle = '#67bade'
this.ctx.lineWidth = 2 * this.scaleFactor
this.ctx.strokeRect(margin * scaleFactor, margin * scaleFactor, w * scaleFactor, h * scaleFactor)
this.drawAnchors(w, h, margin, scaleFactor, radius)
}
drawAnchors(w, h, margin, scaleFactor, radius) {
// ...
}
onMouseMove(e) {
// ...
document.body.style.cursor = 'move'
// ...
}
onMouseUp(e) {
this.emit('end-dragging')
this.drawRect()
}
getImageUrl() {
const { x, y, w, h } = this.selectRect
if (w && h) {
let imageData = this.bgCtx.getImageData(x * scaleFactor, y * scaleFactor, w * scaleFactor, h * scaleFactor)
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
ctx.putImageData(imageData, 0, 0)
return canvas.toDataURL()
}
return ''
}
reset() {
// ...
}
}
Код немного длинный. Из-за недостатка места здесь перечислены только ключевые части. Полный код см. на страницеGitHub - chrisbing/electorn-capture-screen: electron capture screenСмотреть на
При инициализации сохранить холст, рисующий все картинки, который используется для последующего выбора некоторых картинок в области выделения.
В процессе рисования от холста черезgetImageData
Получите содержимое изображения, а затем передайтеputImageData
рисовать на холсте дисплея
Дополнительный контент
Выбор изображения обрабатывается в классе CaptureRenderer. Также требуется информация о панели инструментов и размере.
Эта часть кода не очень связана с выбором изображения, поэтому она обрабатывается отдельно извне, а взаимодействие может осуществляться через события и некоторые свойства, отправляемые CaptureRenderer
// capture-renderer.js
let onDrag = (selectRect) => {
$toolbar.style.display = 'none'
$sizeInfo.style.display = 'block'
$sizeInfo.innerText = `${selectRect.w} * ${selectRect.h}`
if (selectRect.y > 35) {
$sizeInfo.style.top = `${selectRect.y - 30}px`
} else {
$sizeInfo.style.top = `${selectRect.y + 10}px`
}
$sizeInfo.style.left = `${selectRect.x}px`
}
capture.on('start-dragging', onDrag)
capture.on('dragging', onDrag)
let onDragEnd = () => {
if (capture.selectRect) {
const { x, r, b, y } = capture.selectRect
$toolbar.style.display = 'flex'
$toolbar.style.top = `${b + 15}px`
$toolbar.style.right = `${window.screen.width - r}px`
}
}
capture.on('end-dragging', onDragEnd)
capture.on('reset', () => {
$toolbar.style.display = 'none'
$sizeInfo.style.display = 'none'
})
Размер рассчитывается во время движения, а положение рассчитывается в реальном времени, а панель инструментов скрыта во время движения
Скрыть панель инструментов и индикатор размера при сбросе выбора
сохранить буфер обмена
// capture-renderer.js
const audio = new Audio()
audio.src = './assets/audio/capture.mp3'
let selectCapture = () => {
if (!capture.selectRect) {
return
}
let url = capture.getImageUrl()
remote.getCurrentWindow().hide()
audio.play()
audio.onended = () => {
window.close()
}
clipboard.writeImage(nativeImage.createFromDataURL(url))
ipcRenderer.send('capture-screen', {
type: 'complete',
url,
})
}
$btnOk.addEventListener('click', selectCapture)
пройти черезnativeImage.createFromDataURL
Создайте изображение для записи в буфер обмена, уведомите основной процесс о том, что снимок экрана завершен, и прикрепите URL-адрес изображения в формате base64, а затем закройте окно.
сохранить в файл
// capture-renderer.js
$btnSave.addEventListener(‘click’, () => {
let url = capture.getImageUrl()
remote.getCurrentWindow().hide()
remote.dialog.showSaveDialog({
filters: [{
name: ‘Images’,
extensions: [‘png’, ‘jpg’, ‘gif’]
}]
}, function (path) {
if (path) {
fs.writeFile(path, new Buffer(url.replace(‘data:image/png;base64,’, ‘’), ‘base64’), function () {
ipcRenderer.send(‘capture-screen’, {
type: ‘complete’,
url,
path,
})
window.close()
})
} else {
ipcRenderer.send(‘capture-screen’, {
type: ‘cancel’,
url,
})
window.close()
}
})
})
использоватьremote.dialog.showSaveDialog
Выберите имя файла сохранения, затем запишите в файл через модуль fs
Окончательная общая структура каталогов
├── index.html
├── lib // 截图核心代码
│ ├── assets // font 和 声音资源
│ ├── capture-main.js // main 中截图部分代码
│ ├── capture-renderer.js // 截图交互代码
│ └── capture.html // 截图 html
├── main.js
└── package.json
Итоги пит-пойнта
В процессе разработки было обнаружено несколько ям
Во-первых, полноэкранное окно обрабатывается по-разному в Windows и Mac, и это решение для Mac не найдено в Интернете, наконец, я нашел его случайно, читая документ.
Затем в процессе выбора операция перетаскивания каждой позиции и выбора требует много времени для отладки.
Кроме того, в процессе разработки могут быть ошибки в коде, из-за чего полноэкранное окно закрывалось на экране и не могло быть удалено.Наконец, окно было скрыто жестом раскрытия пяти пальцев на сенсорной панели Mac. , и программа закрылась. 😂