Skip to content

ECRomaneli/electron-draggable

Repository files navigation

Electron Draggable Demo

Multi-platform Electron window drag tool

Version Last Commit License Contributions Welcome

Installation

Install the electron-draggable package via npm:

npm install electron-draggable

Overview

The electron-draggable package provides a lightweight, main-process solution for enabling window dragging in frameless Electron windows. It hooks into the webContents.on('before-mouse-event') to intercept mouse events and move the window accordingly, without requiring any CSS (-webkit-app-region) or renderer-process code.

Highlights

  • Zero renderer-side code — everything runs in the main process.
  • Works with BrowserWindow and BaseWindow — supports both standard and multi-view setups.
  • Selector-based filtering — include or exclude specific elements from dragging via CSS selectors.
  • Drag region — restrict dragging to a specific region (e.g., a custom title bar) using partial bounds.
  • Double-click to maximize — optional built-in support for maximize/unmaximize on double-click.
  • Configurable FPS — drag update rate defaults to the screen refresh rate.

Usage

All public methods are documented with JSDoc and can be referenced during import.

Importing

To import the Draggable class:

const { Draggable } = require('electron-draggable')

Creating an Instance

Using Draggable.from (recommended)

The from static method returns a single cached instance per window. If an instance already exists for the window, it is returned immediately:

const drag = Draggable.from(window)

You can also pass drag options during creation:

const drag = Draggable.from(window, { region: { height: 40 }, maximize: true })

When a BrowserWindow is used, its webContents is automatically attached as a drag source (unless attachOnInit is set to false).

Using Draggable.create

Use create when you need multiple independent drag managers for the same window:

const drag = Draggable.create(window, options)

Each instance independently manages its own drag configuration and WebContents attachments. The instance is not cached on the window object.

Drag Options

Option Type Default Description
region Partial<Rectangle> Entire Window Restricts dragging to a region. Only specified bounds are checked (e.g., { height: 40 } for the top 40px).
selector string Disabled CSS selector that marks elements as drag handles. Can be combined with exclude.
exclude string Disabled CSS selector for elements that should NOT trigger drag. Can be combined with selector.
button 'left' | 'right' | 'middle' 'left' Mouse button that triggers the drag.
maximize boolean Disabled Enable double-click to maximize/unmaximize.
fps number Screen refresh rate Frames per second for drag position updates.
attachOnInit boolean Enabled If true, auto-attach the BrowserWindow.webContents on initialization.

Note: selector and exclude can be used together. Use selector to whitelist draggable areas, exclude to blacklist non-draggable elements, or both to whitelist a region and carve out exceptions within it.

Button

The button option specifies which mouse button initiates the drag. Accepted values are 'left', 'right', and 'middle'.

// Drag with right-click (useful when left-click is reserved for other interactions)
Draggable.from(window, { button: 'right' })

// Drag with middle-click
Draggable.from(window, { button: 'middle' })

Region

The region option defines draggable bounds using up to 4 independent thresholds. Only specified attributes are checked — omitted ones impose no constraint:

Attribute Constraint Default
x Drag only when cursor is at or after x px from the left 0 (no left bound)
y Drag only when cursor is at or after y px from the top 0 (no top bound)
width Drag only within width px after x (no right bound)
height Drag only within height px after y (no bottom bound)
        x     x + width
        ┬        ┬
 ┌──────┼────────┼──────┐
 │ 0,0  │        │      │
 │      │        │      │
 │      ░░░░░░░░░░──────┼─┤ y
 │      ░░ DRAG ░░      │
 │      ░░ ZONE ░░      │
 │      ░░░░░░░░░░──────┼─┤ y + height
 │                      │
 │                      │
 └──────────────────────┘

Examples:

// Title bar: top 40px
{ height: 40 }

// Sidebar handle: left 8px
{ width: 8 }

// Custom band: between 50px and 150px from top
{ y: 50, height: 100 }

// Scoped box: specific region
{ x: 100, y: 50, width: 300, height: 150 }

// Entire content
/* empty */

Attaching and Detaching WebContents

For BaseWindow setups with multiple WebContentsView children, you can manually attach and detach drag sources:

const drag = Draggable.from(baseWindow)

// Attach a view's webContents with per-view overrides
drag.attach(view.webContents, { exclude: '.no-drag, button' })

// Detach when no longer needed
drag.detach(view.webContents)

// Or detach all at once
drag.detachAll()

Each attached WebContents can have its own override options that are merged with the instance defaults.

Updating Options

You can update options at any time. Changes apply immediately to all subsequent drag interactions:

// Update options for ALL attached WebContents
drag.updateOptions({ fps: 120, maximize: true })

// Update options for a SPECIFIC WebContents
drag.updateOptions(view.webContents, { region: { height: 60 } })

Destroying Drag

To permanently disable drag and release all references:

drag.destroy()

After calling destroy, the instance is dead and must not be reused. The drag is also automatically disabled when the window is closed.

Retargeting a Window

If you need to move the drag instance to a new window:

drag.setWindow(newWindow)

This clears the old window reference and sets the new one, preserving all attached WebContents and their configurations.

Quick Examples

BrowserWindow (simplest setup)

const { app, BrowserWindow } = require('electron')
const { Draggable } = require('electron-draggable')

app.whenReady().then(() => {
  const window = new BrowserWindow({ frame: false })
  window.loadURL('https://github.com/ECRomaneli/electron-draggable')

  // Enable dragging on the top 40px, with double-click to maximize
  Draggable.from(window, { region: { height: 40 }, maximize: true })
})

BaseWindow with WebContentsView

const { app, BaseWindow, WebContentsView } = require('electron')
const { Draggable } = require('electron-draggable')

app.whenReady().then(() => {
  const window = new BaseWindow({ width: 800, height: 600, frame: false })

  const view = new WebContentsView()
  window.contentView.addChildView(view)
  view.setBounds({ x: 0, y: 0, width: 800, height: 600 })
  view.webContents.loadFile('index.html')

  // Create drag instance and attach the view
  const drag = Draggable.from(window, { region: { height: 100 } })
  drag.attach(view.webContents, { exclude: '.no-drag, button' })
})

How It Works

  1. The Draggable instance hooks into webContents.on('before-mouse-event') for each attached WebContents.
  2. On mouseDown, it checks whether the click position is within the drag region and matches the selector/exclude rules (using document.elementFromPoint via executeJavaScript).
  3. If the position is draggable, it records the offset between the cursor and the window position.
  4. On mouseMove, it starts an interval (at the configured FPS) that reads the cursor screen position and updates the window position accordingly.
  5. On mouseUp (or any non-move event), the interval is cleared and dragging stops.
  6. If maximize is enabled and a double-click is detected, the window is toggled between maximized and normal states.

API Reference

Static Methods

/**
 * Get or create a Draggable instance for the given window.
 * Returns a single cached instance per window.
 * @param {BaseWindow} window - The Electron window to enable dragging for.
 * @param {DragOptions} [options] - Optional drag behavior configuration.
 * @returns {Draggable} A unique Draggable instance for this window.
 */
Draggable.from(window, options)

/**
 * Create a new independent Draggable instance for the given window.
 * The instance is NOT cached on the window object.
 * @param {BaseWindow} window - The Electron window to enable dragging for.
 * @param {DragOptions} [options] - Optional drag behavior configuration.
 * @returns {Draggable} A new independent Draggable instance.
 */
Draggable.create(window, options)

Instance Methods

/**
 * Register a WebContents as a drag source for this window.
 * @param {WebContents} webContents - The WebContents to attach.
 * @param {DragOptions} [overrideOptions] - Optional per-WebContents drag options.
 */
attach(webContents, overrideOptions)

/**
 * Unregister a WebContents from dragging.
 * @param {WebContents} webContents - The WebContents to detach.
 */
detach(webContents)

/**
 * Unregister all WebContents from dragging.
 */
detachAll()

/**
 * Disable drag for all registered WebContents and release the window reference.
 * After calling this method, the instance is dead and must not be reused.
 */
destroy()

/**
 * Update drag options for all registered WebContents.
 * @param {Partial<DragOptions>} update - Partial options to merge.
 */
updateOptions(update)

/**
 * Update drag options for a specific WebContents.
 * @param {WebContents} webContents - The WebContents to update options for.
 * @param {Partial<DragOptions>} update - Partial options to merge.
 */
updateOptions(webContents, update)

/**
 * Retarget the instance to a new window.
 * @param {BaseWindow} newWindow - The new window to attach to.
 */
setWindow(newWindow)

Author

Created by Emerson Capuchi Romaneli (@ECRomaneli).

License

This project is licensed under the MIT License.

About

Multi-platform Electron window drag tool

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors