## Electron Window Auto Scale By Monitor
最近工作上開始接觸到 [Electron](https://www.electronjs.org/),覺得跟以前寫 Web 感覺非常不一樣,然後有接到一個**略微困難**的需求就是 ...
> 需要在現有產品上增加跨螢幕時,可以根據不同解析度縮放產品視窗。
乍看下好像直接使用 Electron 自己的 ZoomFactor 就可以了,但要前置一些事項

### 大綱
[TOC]
---
### 研究目標
✅ 定義 widthRatio 概念:以當前 裝置解析度 / 1920 ,去算出當前比例。
✅ Window:能依照 widthRatio 比例乘以 width、height,以符合螢幕呈現。
✅ Web:能依照 widthRatio,去 setZoomFactor(widthRatio) 縮放當前的 Web,以符合螢幕呈現。
✅ XY軸:能依照 widthRatio 換算乘以 width、height,再去跟當前螢幕比較位置,以符合螢幕呈現。
✅ 每次打開 APP 都能順利吃到我們設置的比例。
✅ Window 在使用情境下,突然要變寬變長依然要能照比例縮放。
✅ 滑鼠拖曳不同螢幕視窗時,針對當下 Window,重設 Window、Web。
✅ 鎖住/防止用戶使用 OS 檢視/縮放 功能。
> 以及困難的是我們不止一個視窗,我們同時有6、7個以上的視窗要處理。
### 1. Window 高寬 X、Y 計算縮放
- 所以我們先從從 Window預設縮放開始做
- 依照比例計算 Window高寬
- X、Y 位移計算
```javascript=
const createWindow = (index: number) => {
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize
const width = 900
const height = 500
const ratio = Number((screenWidth / 1920).toFixed(2))
const x = Math.round((screenWidth - width * ratio) / 2)
const y = Math.round((screenHeight - height * ratio) / 2)
const window = new BrowserWindow({
width: Math.round(width * ratio),
height: Math.round(height * ratio),
x,
y,
webPreferences: {
nodeIntegration: true
},
title: `Window ${index + 1}`,
})
window.loadURL(`data:text/html,<h1>Window ${index + 1}</h1>`)
}
```
### 2. APP 初始化時,要能吃到 Window 預設縮放
```javascript=
window.webContents.once('did-finish-load', () => {
window.webContents.setZoomFactor(ratio)
})
```
- 如果直接在 new BrowserWindow 設置 setZoomFactor 的話會有優先度問題,假如用戶自己在檢視或快捷鍵縮放APP,會先蓋過我們預先設置的縮放,所以你必須設置在 **did-finish-load** 事件後,才能順利蓋過。
### 3. 監聽 Window 自適應縮放不同解析度螢幕
- 使用 **moved** 事件,監聽比較滑鼠當前位置的 **displayId** 是否不同,一但移到不同的螢幕時,就會重新抓取當前螢幕重算縮放比例,重設 Window 高寬、ZommFactor縮放。
```javascript=
window.on('moved', () => {
const { x, y } = window.getBounds()
const { id: currentDisplayId, workAreaSize } =screen.getDisplayNearestPoint({
x,
y
})
if (initialDisplayId !== currentDisplayId) {
const { width: currentScreenWidth } = workAreaSize
const currentRatio = Number((currentScreenWidth / 1920).toFixed(2))
initialDisplayId = currentDisplayId
window.setBounds({
width: Math.round(width * currentRatio),
height: Math.round(height * currentRatio)
})
window.webContents.setZoomFactor(currentRatio)
}
})
```
### 4. 鎖住縮放快捷鍵
- 防止 User 使用縮放快捷功能,直接使用 **before-input-event** 監聽
-
```javascript=
window.webContents.on('before-input-event', (event, input) => {
if ((input.control || input.meta) && (input.key === '=' || input.key === '-' || input.key === '0')) {
event.preventDefault()
}
})
```
如果直接使用 Electron **globalShortcut** 會導致APP以外的快捷功能也會失效
```javascript=
const { app, globalShortcut } = require('electron')
app.whenReady().then(() => {
// Register a 'CommandOrControl+Y' shortcut listener.
globalShortcut.register('CommandOrControl+Y', () => {
// Do stuff when Y and either Command/Control is pressed.
})
})
```
### 5. Window moved 事件註銷
- 當預設 Window 高寬因應情境改變時,要記得註銷當前 **moved** 事件,再重新註冊監聽
```javascript=
window.removeAllListeners('moved')
```
### 6. 個自獨立縮放
- 因為專案性質我們有針對 dev 跟 打包 時載入不同方式,這大概是我花在所有研究上最多時間的地方。在新增 Window 時,會因為不同載入 web 方式,去進而影響到所有視窗內容的縮放。
- 以 dev 為例:
- win.loadURL(`${process.env['ELECTRON_RENDERER_URL']}${url}`)
- 因為是使用 URL 方式引入,所以按照瀏覽器預設方式,同 Domain 縮放,會變相影響到所有視窗縮放,這樣就會發生我原本只想從A螢幕移到B螢幕的視窗,只需要改變當下拖移的視窗縮放就好,但因為使用 **loadURL** 關係,移過去會連當前A螢幕不動的其他 Windows 同步影響到,這不是我們要的。
- 以打包為例:
- win.loadFile(join(__dirname, `../{url}`))
- 打包後則是對照的是每個獨立打包的檔案,所以不會出現 dev 縮放時同步的狀況。
> 所以我浪費了兩天在 dev 反覆測試跟解決為什麼會同步縮放問題,但根本原因就是沒有辦法解決,換個引入方式打包自然就解決了...。
```javascript=
function initWinUrl(win: BrowserWindow, url: string) {
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
win.loadURL(`${process.env['ELECTRON_RENDERER_URL']}${url}`)
} else {
win.loadFile(join(__dirname, `../{url}`))
}
}
const window = new BrowserWindow({
width: Math.round(width * ratio),
height: Math.round(height * ratio),
x,
y,
webPreferences: {
nodeIntegration: true
},
title: `Window ${index + 1}`,
})
window.initWinUrl(window, url)
```