electron perfect drag and drop : electron-drag よりもエレガントな実装。

プロダクトマネージャーから、エレクトロンウィンドウの下にあるツールバーでマウスの右ボタンをブロックしてほしいという要望がありました。

electron under window (app-region:drag) のネイティブドラッグ&ドロップはクリックイベントを食べるので、右クリックブロックをする方法がありません。ドラッグ&ドロップを手動で実装する必要があります。

mousemoveイベントを使って要件を実装すると、マウスが素早く動くとウィンドウの範囲外に出てしまい、ウィンドウがマウスに追従しなくなります。

この要件を満たすために、ポインタイベントをベースに、 setPointerCaptureを使って新しいウィンドウで開く BrowserWindow外のマウス位置を捕捉するように実装したところ、完璧に動作するようになりました。

この解決策は、electron-drag の解決策よりも優れています。プラットフォームに依存するバイナリ依存性があり、アップグレードやパッケージングに手間がかかる可能性があります。

この解決策については、まだウェブ上で誰も言及していないことがわかりましたので、ブログで紹介し、共有します。

以下、参考までに実装の違いによるコードの違いを列挙します。コアコードは、 web/src/lib/drag.coffeemain/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
アップデート:
より: gcxfd