electron perfect drag and drop : a more elegant implementation than electron-drag
The product manager asked the toolbar under electron window to block the right mouse button.
The native drag and drop of electron under window ( app-region:drag
) eats the click event, so there is no way to do right click blocking. I have to implement drag and drop manually.
If I use the mousemove event to achieve the requirement, when the mouse moves quickly, it will be out of the range of the form and the form will no longer follow the mouse movement.
I implemented this requirement based on the pointer event, using setPointerCapture to capture the mouse position outside of the BrowserWindow, and it works perfectly.
This solution is better than the electron-drag solution: it has platform-dependent binary dependencies, which makes it a pain to upgrade and package.
I found that no one has mentioned this solution on the web yet, so I'm blogging about it and sharing it.
The code differences between the implementations are as follows, just for reference. The core code is web/src/lib/drag.coffee
and 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
main/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