Electron 办公党的摸鱼神器

概要

俗话说水生金,摸鱼 = 水,得出定式摸鱼 ≈ 生金。
下面提到的项目我已经开源,发布在 gitee 上,可自行查看,发行版中下载 exe 文件安装后可以直接使用。

正文

前段时间刷小红薯看到一些将桌面背景改为客厅的效果图,然后将视频的小窗口模式放在效果图的电视上的达人。

13907412-c34a29b48970eeb5

我心想这小窗口模式太危险了吧,而且谁上班一直待在桌面不动的呀,直到后来刷到了下面这个视频。

13907412-0adbdce48e020a89

看来大家都有在努力上班啊!
她把小窗口视频叠加到了 360 弹层之上,同事愣是没发现(当然也有摆拍可能)。
但是仔细看的话视频窗口的大小和弹层的大小是不匹配的,最右边超出了一小部分,而且这样做虽然比桌面效果图的方法更安全些,但是弹层被关闭后视频窗口不会一起关闭。
不过想法是很不错,如果视频能嵌入到弹层里(大小统一,弹层关闭时一起消失)变成一个真的假弹层,不就更完美了。那这个时候我就想到了 Electron,一款使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。那我本人前端会一些,Election 之前也跟着官网起步文档运行过,既然理论成立直接开搞。
一开始我的想法是这样的,输入视频地址然后直接进入到弹层模式显示。但是后来我觉得既然 Chromium 已经嵌入到 Electron 框架中了,要不直接跳出一个搜索引擎的界面让使用者自己搜索到想看的视频地址页面,然后再变成弹层不是更加舒服。
那第一个窗口我是这样做的:

  // Create the browser window.
  const win = new BrowserWindow({
    width: 800,
    height: 600
  })

  await win.loadURL("https://cn.bing.com/")

  // 拦截打开新窗口
  win.webContents.setWindowOpenHandler(details => {
    win.loadURL(details.url)
    return { action: 'deny' }
  })

创建一个默认窗口 使用 loadURL 加载一个搜索引擎的地址(以必应为例),然后我只想在一个窗口中跳转链接,于是当该窗口点击了跳转链接时阻止跳转且让当前窗口重新加载跳转地址。

那么除了自己搜索外,大多数时候我们是在B站腾讯视频爱奇艺等主流的视频网站上刷剧看视频的。那么我们需要自定义一个Menu,加入一些主流视频网站的地址作为快捷跳转。最后把选定的视频地址传入到弹层窗口显示,那么Menu的配置大致如下:

const template = [
    {
        label: '视频',
        submenu: [
            {
                label: '哔哩哔哩',
                click: () => {
                    var focusedWin = BrowserWindow.getFocusedWindow()
                    focusedWin.loadURL('https://www.bilibili.com/').catch(error => console.error(error))
                }
            },
            {
                label: '腾讯视频',
                click: () => {
                    var focusedWin = BrowserWindow.getFocusedWindow()
                    focusedWin.loadURL('https://v.qq.com/').catch(error => console.error(error))
                }
            },
            // ......
        ],
    },
    {
        label: '弹窗',
        click: async () => {
            var focusedWin = BrowserWindow.getFocusedWindow()
            // 这里的地址是为了重新选择视频地址是记录上一个播放地址
            lastUrl = focusedWin.webContents.getURL()
            focusedWin.hide()
            createPopupWindow()
        }
    }
]

const createPopupWindow = async () => {
    const primaryDisplay = screen.getPrimaryDisplay()
    const { width, height } = primaryDisplay.workAreaSize

    // ipc 监听在 loadURL 之前,防止事件未注册
    initIpcMain()

    const win = new BrowserWindow({
        width: 370,
        height: 470,
        x: width - (370 + 10),
        y: height - (470 + 10),
        movable: false, // 窗口是否可移动
        hasShadow: false, // 窗口是否显示阴影
        frame: false, // 窗口是否显示边框
        show: false, // 等 loadURL 完成之后再显示
        alwaysOnTop: true, // 窗口是否置顶
        skipTaskbar: true, // 窗口是否显示在任务栏中
        webPreferences: {
            webviewTag: true, // 启用 webview
            preload: path.join(__dirname, 'preload.js'), // 预加载脚本
            nodeIntegration: true,
            contextIsolation: false
        }
    })
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL).catch(error => console.error(error))
    win.show()
}

// 初始化 IPC 事件
const initIpcMain = () => {
    // 获取最新页面的 URL(页面加载就要获取的方法要写在 loadURL 之前)
    ipcMain.handleOnce('webview-url', () => {
        return lastUrl
    })
}

这里主要是处理了快捷地址和选择后的地址通过ipc 通信传入到页面,这里配置的预加载脚本 preload.js 通过 ipcRenderer.invoke('webview-url')能访问到如上配置的ipcMain.handleOnce('webview-url')从而获得上一个窗口的选择地址。点击窗口模式后创建一个新的 BrowserWindow,大小类似广告弹层且显示在桌面的右下角。

Electron 在流程上差不多就这些,接下来就要考虑页面的展示了。首先想到的是将视频内容通过iframe标签嵌入在中间,上面是假的广告,下面是时间,假期的显示(如视频中展示的位置一样)。但是 iframe 有一个有同源策略的安全限制问题,不是所有的网站都允许你页面里面套页面的。于是就要用到另一个标签webview,与 iframe不同, webview 独立于你的应用程序运行,在 Electron 的文档中有提到 webview 的部分,可以自行查看更多详情。

<body>
    <!-- 省略 -->
    <main></main>
    <!-- 省略 -->
</body>

HTML 部分只需要像视频里那样排版就行,这里我们主要关注中间部分。

const main = document.querySelector('main')
const webview = document.createElement('webview')

window.electronAPI.webviewUrl().then(url => {
    webview.src = url
})
main.appendChild(webview)

webview.addEventListener('dom-ready', () => {
    webview.style.display = 'inline-flex'
    webview.style.width = '100%'
    webview.style.height = 'calc(100vh - 263px)'
})

JS部分在获取地址之后,创建一个webview标签设置完属性和样式后追加到 main 标签里。这样基本上就大功告成了。

但是在我做出第一版的时候发现一个问题。想要完美显示得需要视频网站支持网页全屏显示的功能,注意是网页全屏,不是屏幕全屏,只需要在显示的部分占满即可。因为一开始我只以B站和腾讯视频作为示例,它两都有网页全屏,而其他网站是没有或不完美支持的(如爱奇艺,有这个功能但是有最小宽度(lll¬ω¬))。

既然原站没有只能自己造了,还好 webview 支持插入脚本,<webview>.executeJavaScript(code[, userGesture])

Election webview 标签 部分的文档中有提到:在页面中执行 code。 如果设置了userGesture,它将在页面中创建用户手势上下文。 像 requestFullScreen 这样的需要用户操作的HTML API可以利用这个选项来实现自动化。

当然我们不需要实现 requestFullScreen,因为那是屏幕全屏的方法。不过我们需要这个用户手势上下文,将userGesture设置为true即可。

既然可以插入脚本了,那么我的第一个想法就是,把无关内容全部设置displaynone,并且将video标签复制到body标签下成为它的的子集,然后将video设置样式,zInde调大置顶显示。

但是我想到这个解决方案的时候觉得自己真是个天才,然后不是所有的网站都是以video标签播放的,可能在页面里还套了个 iframe 标签来展示视频,当然这是试了腾讯视频之后得出来的,一开始我一直以为是我脚本的问题,没想到广告的 video 播放完之后,页面上再也不存在 video 标签了(好在广告的 video 都是播放完就消失,不然很难区分有效的 video 标签)。后来我暂时妥协,因为腾讯支持网页全屏功能,我只要模拟点击按钮就可以了(lll¬ω¬),于是第二版诞生,支持自动网页全屏,当然只支持Menu中配置的一些。

不过 iframe 播放的方式也是可以像 video 复制的方法解决的,之后的版本应该会解决这个问题,使得通过 iframe 播放网站也能使用。

总结

可以看出,对于办公党 Electron 的用途还是很广的。你只要会前端且跟着起步文档搭一搭,加上你自己的想法,一个有趣的桌面工具就诞生了!

posted @ 2025-06-30 11:51  五岁能抬头  阅读(1142)  评论(2)    收藏  举报