electron perfect drag and drop : elegantnější implementace než electron-drag
Správce produktu požadoval, aby panel nástrojů pod oknem electron blokoval pravé tlačítko myši.
Při nativním přetahování v Electronu pod oknem ( app-region:drag
) se událost kliknutí vyhodí a není možné blokovat kliknutí pravým tlačítkem myši. Přetahování je nutné provést ručně.
Pokud k implementaci požadavku použijete událost mousemove, při rychlém pohybu myši se tato dostane mimo dosah okna a okno již nebude sledovat myš.
Tento požadavek jsem implementoval na základě události ukazatele pomocí setPointerCapture pro zachycení polohy myši mimo okno BrowserWindow a funguje to perfektně.
Toto řešení je lepší než řešení electron-drag: má binární závislosti závislé na platformě a jeho aktualizace a balíčkování může být nepříjemné.
Zjistil jsem, že se o tomto řešení na webu zatím nikdo nezmínil, a tak o něm píšu blog a sdílím ho.
Rozdíly v kódu mezi implementacemi jsou uvedeny níže pouze pro informaci. Základní kód je web/src/lib/drag.coffee
a 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