如何通过electron js访问htmlDOM元素

How to access html DOM element through electron js

我正在用 electron js 制作一个文本编辑器,一旦用户按下 ctrl + s,我希望将文件保存为 txt 文件。问题是,我似乎无法找到直接访问包含文本的 div 的方法。我试过使用预加载,但只有在程序为 运行 时才有效。如何将元素保存为变量?

这是主要的 javascript 代码:

const { app, BrowserWindow, globalShortcut } = require('electron');
const path = require('path');

// Create the main window
const createWindow = () => {

  // Adjust a few settings
  const win = new BrowserWindow({
    // What the height and width that you open up to
    width: 500,
    height: 600,

    // Minimun width and height
    minWidth: 400,
    minHeight: 400,

    icon: __dirname + '/icon.png',
    
    // Change the window title
    title: "text editor",

    webPreferences: {
      // Preload so that the javascript can access the text you write
      preload: path.join(__dirname, 'preload.js'),
    }    
  });
  
  win.loadFile('index.html');

  // Remove that ugly title bar and remove unnecessary keyboard shortcuts
  win.removeMenu();
}

// Create window on ready so that no nasty errors happen
app.whenReady().then(() => {
  createWindow();
});

app.whenReady().then(() => {

  // Global shortcut so the user has the ablitiy to exit
  globalShortcut.register('ctrl+e', () => {
    console.log("exiting...");
    app.exit();
  });

  globalShortcut.register('ctrl+s', () => {
    console.log("saving...");
  });
})


// when all windows close this app actually closes
app.on('window-all-closed', () => {
  if (process !== 'darwin') app.quit();
})

要获取 index.html window 中 div 元素的 innerText(或等同物),您需要向渲染线程发送消息请求这条信息。在此之后,您将需要渲染线程将 innerText 发送回主线程进行处理(保存)。

Electron 的 Inter-Process Communication 有时会令人困惑,但如果正确实施,它会简单而安全。

要了解有关所涉及过程的更多信息,您需要阅读并尝试理解以下链接:


让我们首先构建您的 html 文档。它至少必须包含一个可编辑的 <div> 标签和一个 'save' 按钮。

index.html(渲染线程)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Test Editor</title>
        <style>
            #editor {
                width: 50vw;
                height: 50vh;
            }
        <style>
    </head>

    <body>
        <div id="content" contenteditable="true"></div>
        <input type="button" id="save" value="Save">
    </body>

    <script src="script.js"></script>
</html>

See Example: A simple but complete rich text editor for some cool ideas.


现在让我们添加 'save' 按钮和 IPC 消息功能。

script.js(渲染线程)

// IIFE (Immediately Invoked Function Expression)
(function() => {
    let content = document.getElemetById('content').innerText;

    document.getElementById('save').addEventListener('click', saveContent(content));

    window.ipcRender.receive('editor:getContent', () => { saveContent(content); });
});

function saveContent(content) {
    window.ipcRender.send('editor:saveContent', content);
}

这是您的 main.js 文件,其中包含以下更新。

  • 添加 Electron 的 ipcMain 模块。
  • win 对象添加到顶级范围,这样它就不会被垃圾回收。
  • 侦听来自呈现线程的消息(使用 IFFE)。
  • 添加saveContent()功能(待您充实)。
  • new BrowserWindow 行删除 const
  • Return win 来自 createWindow() 函数,以便稍后参考。
  • 更新 globalShortcut ctrl+s 函数。

main.js(主线程)

const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron');
const path = require('path');

let win = null;

// IIFE (Immediately Invoked Function Expression)
(function() => {
  ipcMain.on('editor:saveContent', (event, content) => { saveContent(content); });
})();

function saveContent(content) {
  console.log("saving...");
  // Save content...
  console.log("saved...");
}

// Create the main window
function createWindow() {

  // Adjust a few settings
  win = new BrowserWindow({
    // What the height and width that you open up to
    width: 500,
    height: 600,

    // Minimun width and height
    minWidth: 400,
    minHeight: 400,

    icon: __dirname + '/icon.png',
    
    // Change the window title
    title: "text editor",

    webPreferences: {
      // Preload so that the javascript can access the text you write
      preload: path.join(__dirname, 'preload.js'),
    }    
  });
  
  win.loadFile('index.html');

  // Remove that ugly title bar and remove unnecessary keyboard shortcuts
  win.removeMenu();

  return win;
}

// Create window on ready so that no nasty errors happen
app.on('ready', () => {
  // Create the window.
  win = createWindow();

  // Global shortcut so the user has the ability to exit
  globalShortcut.register('ctrl+e', () => {
    console.log("exiting...");
    app.exit();
  });

  // Global shortcut to save editable content.
  globalShortcut.register('ctrl+s', () => {
    console.log('ctrl+s pressed.');
    win.webContents.send('editor:getContent');
  });
})

// when all windows close this app actually closes
app.on('window-all-closed', () => {
  if (process !== 'darwin') app.quit();
})

Note that I have left the actual saving to the filesystem functionality to you. See Node.js: fs.writeFile() for more information.


好的,拼图的最后一块是一个有效的 preload.js 脚本。这是授予在主线程和渲染线程之间使用白名单通道列表的脚本。

在这里我们添加 editor:saveContenteditor:getContent 频道名称。

preload.js(主线程)

const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

// White-listed channels.
const ipc = {
  'render': {
    // From render to main.
    'send': [
      'editor:saveContent'
    ],
    // From main to render.
    'receive': [
      'editor:getContent'
    ],
    // From render to main and back again.
    'sendReceive': []
  }
};

contextBridge.exposeInMainWorld(
  // Allowed 'ipcRenderer' methods.
  'ipcRender', {
    // From render to main.
    send: (channel, args) => {
      let validChannels = ipc.render.send;
      if (validChannels.includes(channel)) {
        ipcRenderer.send(channel, args);
      }
    },
    // From main to render.
    receive: (channel, listener) => {
      let validChannels = ipc.render.receive;
      if (validChannels.includes(channel)) {
        // Deliberately strip event as it includes `sender`
        ipcRenderer.on(channel, (event, ...args) => listener(...args));
      }
    },
    // From render to main and back again.
    invoke: (channel, args) => {
      let validChannels = ipc.render.sendReceive;
      if (validChannels.includes(channel)) {
        return ipcRenderer.invoke(channel, args);
      }
    }
  }
);

Note that I do not perform any functions so-to-speak in the preload script. I only manage a list of channel names and the transfer of any data associated with those channel names.