electron perfect drag and drop : electron-drag よりもエレガントな実装。
プロダクトマネージャーから、エレクトロンウィンドウの下にあるツールバーでマウスの右ボタンをブロックしてほしいという要望がありました。
electron under window (app-region:drag
) のネイティブドラッグ&ドロップはクリックイベントを食べるので、右クリックブロックをする方法がありません。ドラッグ&ドロップを手動で実装する必要があります。
mousemoveイベントを使って要件を実装すると、マウスが素早く動くとウィンドウの範囲外に出てしまい、ウィンドウがマウスに追従しなくなります。
この要件を満たすために、ポインタイベントをベースに、 setPointerCaptureを使って BrowserWindow外のマウス位置を捕捉するように実装したところ、完璧に動作するようになりました。
この解決策は、electron-drag の解決策よりも優れています。プラットフォームに依存するバイナリ依存性があり、アップグレードやパッケージングに手間がかかる可能性があります。
この解決策については、まだウェブ上で誰も言及していないことがわかりましたので、ブログで紹介し、共有します。
以下、参考までに実装の違いによるコードの違いを列挙します。コアコードは、 web/src/lib/drag.coffee
、 main/ipc/drag.coffee
。
web/src/lib/drag.coffee
import $on from '~/lib/on.coffee'
import ipc from '~/lib/ipc.coffee'
import platform from '@/config/platform.mjs'
{drag:{setBounds,getBounds}} = ipc
pointermove = 'pointermove'
IGNORE = new Set('SELECT BUTTON A INPUT TEXTAREA'.split ' ')
{round} = Math
export default main = (elem)=>
if platform!='win32'
return
elem.style.appRegion = 'no-drag'
moving = false
init_w = init_h = init_x = init_y = init_top = init_left = undefined
_move = (e)=>
{screenX,screenY} = e
setBounds(
round screenX-init_x+init_left
round screenY-init_y+init_top
init_w
init_h
)
return
down = (e)=>
if moving
return
if e.button!=0 # 鼠标左键
return
{target} = e
p = e.target
loop
{nodeName} = p
if IGNORE.has nodeName
return
if nodeName == 'BODY'
break
p = p.parentNode
moving = true
{screenX:init_x,screenY:init_y} = e
elem.setPointerCapture e.pointerId
elem.addEventListener pointermove,_move
{
x:init_left
y:init_top
height:init_h
width:init_w
} = await getBounds()
console.log await getBounds()
return
up = (e)=>
if moving
await _move(e)
elem.releasePointerCapture e.pointerId
elem.removeEventListener pointermove,_move
moving = false
return
$on elem,{
lostpointercapture:up
pointercancel:up
pointerdown:down
pointerup:up
}
return
main/ipc/drag.coffee
export getBounds = ->
@sender.getOwnerBrowserWindow().getBounds()
export setBounds = (x,y,width,height)->
win = @sender.getOwnerBrowserWindow()
# 不用 setPosition 是因为 https://github.com/electron/electron/issues/9477 browserWindow.setPosition(x,y) changed window size (windows/linux) with non default scaleLevel (125% for example)
win.setBounds {
x:Math.round(x)
y:Math.round(y)
width
height
}
return
メイン/ipc.coffee
`export * as drag from './ipc/drag.coffee'`
web/src/page/recbar.vue
<template lang="pug">
-nav(:class="{ pause }")
nav(:class="{ pause }" ref="nav")
</template>
<script lang="coffee">
import drag from '~/lib/drag.coffee'
nav = shallowRef()
export default {
setup: =>
onMounted =>
drag(nav.value)
return
{
nav
}
}
</script>
main/boot.coffee
_handle = (k, v)=>
if v instanceof Function
ipcMain.handle k.join('.'), (e,args)=>
v.apply(e,args)
for [name,func] from Object.entries v
_handle [...k, name], func
return
for [k,v] from Object.entries ipc
_handle([k], v)
web/src/lib/_on.coffee
export default (elem, dict)=>
for event,func of dict
elem.addEventListener(event, func)
=>
for event,func of dict
elem.removeEventListener(event, func)
web/src/lib/on.coffee
import $on from './_on.coffee'
export default (elem, dict)=>
unbind = $on elem, dict
onUnmounted unbind
unbind