Skip to content

Add middle mouse button scrolling support #572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './keyboard';
export * from './middle-button';
export * from './mouse';
export * from './resize';
export * from './select';
Expand Down
120 changes: 120 additions & 0 deletions src/events/middle-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as I from '../interfaces/';
import { eventScope, getPosition } from '../utils/';

export function middleButtonHandler(scrollbar: I.Scrollbar) {
// Skip initialization if middle mouse scrolling is disabled
if (scrollbar.options.enableMiddleMouseScroll === false) {
return;
}

const addEvent = eventScope(scrollbar);
const container = scrollbar.containerEl;

let isAutoScrolling = false;
let clickPosition = { x: 0, y: 0 };
let animationID = 0;

function calculateScroll(currentPos: { x: number, y: number }): I.Data2d {
// Calculate distance from original click
const deltaX = currentPos.x - clickPosition.x;
const deltaY = currentPos.y - clickPosition.y;

// Dead zone radius
const deadZone = 5;

// Calculate distance
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

// If inside dead zone, don't scroll
if (distance < deadZone) {
return { x: 0, y: 0 };
}

// Simple scale factor for scroll speed
const factor = 0.2;

return {
x: deltaX * factor,
y: deltaY * factor,
};
}

function startAutoScrolling(pos: { x: number, y: number }) {
isAutoScrolling = true;
clickPosition = pos;

// Change cursor style to indicate auto-scrolling mode
document.body.style.cursor = 'all-scroll';

// Start scrolling loop
animationID = requestAnimationFrame(updateScroll);
}

function stopAutoScrolling() {
if (!isAutoScrolling) return;

isAutoScrolling = false;
cancelAnimationFrame(animationID);

// Restore default cursor
document.body.style.cursor = '';
}

function updateScroll() {
if (!isAutoScrolling) return;

// Calculate scroll amount
const { x, y } = calculateScroll(currentMousePos);

// Apply scroll with boundary checking
// Create a simple event to pass to the transformable momentum method
const mockEvent = new Event('middlemousescroll');
scrollbar.addTransformableMomentum(x, y, mockEvent, () => {
// No additional logic needed here
});

// Continue scrolling
animationID = requestAnimationFrame(updateScroll);
}

// Store current mouse position
let currentMousePos = { x: 0, y: 0 };

// Track mouse position
addEvent(window, 'mousemove', (evt: MouseEvent) => {
currentMousePos = getPosition(evt);
});

// Handle mouse down
addEvent(container, 'mousedown', (evt: MouseEvent) => {
// If middle button
if (evt.button === 1) {
// Prevent default (which might be opening links in new tab)
evt.preventDefault();

// Toggle auto-scrolling
if (isAutoScrolling) {
stopAutoScrolling();
} else {
startAutoScrolling(getPosition(evt));
}
} else if (isAutoScrolling) {
// Any other mouse button stops auto-scrolling
stopAutoScrolling();
}
});

// Handle window blur
addEvent(window, 'blur', () => {
if (isAutoScrolling) {
stopAutoScrolling();
}
});

// Handle ESC key
addEvent(window, 'keydown', (evt: KeyboardEvent) => {
if (evt.key === 'Escape' && isAutoScrolling) {
stopAutoScrolling();
}
});
}
5 changes: 5 additions & 0 deletions src/interfaces/scrollbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export type ScrollbarOptions = {
* @default true
*/
continuousScrolling: boolean,
/**
* Delegate wheel events and touch events to the given element. By default, the container element is used. This option will be useful for dealing with fixed elements.
* @default null
*/
enableMiddleMouseScroll: boolean,
/**
* Delegate wheel events and touch events to the given element. By default, the container element is used. This option will be useful for dealing with fixed elements.
* @default null
Expand Down
6 changes: 6 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export class Options {
@boolean
continuousScrolling = true;

/**
* Enable middle mouse button scrolling
*/
@boolean
enableMiddleMouseScroll = true;

/**
* Delegate wheel events and touch events to the given element.
* By default, the container element is used.
Expand Down