Skip to content
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

Add button Option for DragPan Enable or Map Configuration #5341

Open
gottesman opened this issue Jan 13, 2025 · 2 comments
Open

Add button Option for DragPan Enable or Map Configuration #5341

gottesman opened this issue Jan 13, 2025 · 2 comments
Labels
enhancement New feature or request PR is more than welcomed Extra attention is needed

Comments

@gottesman
Copy link

gottesman commented Jan 13, 2025

Why this Feature Request?

It would be beneficial to allow developers to customize the mouse button used for the dragPan interaction in MapLibre GL JS. Currently, the dragPan handler is tied to the left mouse button, with no built-in support for customizing this behavior. Adding a button option to the dragPan.enable() method or allowing configuration during map initialization would enable greater flexibility for developers.

This feature is already implemented in some other mapping libraries, making it an expected functionality for developers transitioning to MapLibre GL JS.

Proposed Solution

  1. Introduce an optional button parameter in the dragPan.enable() method, which specifies the mouse button to trigger the drag pan interaction. For example:
map.dragPan.enable({ button: 1 }); // Enables middle mouse button for dragPan
  1. Alternatively, add a configuration option in the map's initialization settings:
const map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [0, 0],
    zoom: 2,
    dragPanButton: 1 // Enables middle mouse button for dragPan
});

Use Cases

Applications requiring non-standard drag interactions, such as:

  • Using the middle mouse button for drag panning to avoid conflicts with other left-button interactions.
  • Customizing drag interactions to align with user preferences or specific application requirements.

Workaround Used Now

Currently, for my temporary implementation, I'm using custom event handling. Although not perfect, performant, or native, it works (for anyone trying to implement this unofficially):

First I created a variable for the handler:

const dragPan = {
    enabled: true,
    isDragging: false,
    startPos: null,
    lastPos: null,
    velocity: [0, 0],
    inertiaAnimation: null,
    dragButtons: { 0:true, 1: true },
    decay : 0.9,
    minVelocity : 0.1,
    minMovement: 5,
};

Then I disabled the dragPan functionality after initializing the map:

map.dragPan.disable();

Finally, I added the implementation:

const canvas = map.getCanvas();
canvas.addEventListener('mousedown', (e) => {
    if (e.button in dragPan.dragButtons && dragPan.enabled) {
        dragPan.isMousedown = true;
        dragPan.startPos = [e.clientX, e.clientY];
        dragPan.lastPos = dragPan.startPos;
        dragPan.velocity = [0, 0];
        canvas.style.cursor = 'grabbing';
        e.preventDefault();
        if (dragPan.inertiaAnimation) {
            cancelAnimationFrame(dragPan.inertiaAnimation);
            dragPan.inertiaAnimation = null;
        }
    }
});

canvas.addEventListener('mousemove', (e) => {
    if (dragPan.isMousedown && dragPan.startPos) {
        const deltaX = e.clientX - dragPan.startPos[0];
        const deltaY = e.clientY - dragPan.startPos[1];

        if (Math.abs(deltaX) > dragPan.minMovement || Math.abs(deltaY) > dragPan.minMovement) {
            dragPan.isDragging = true;
            const moveX = e.clientX - dragPan.lastPos[0];
            const moveY = e.clientY - dragPan.lastPos[1];

            map.panBy([-moveX, -moveY], { animate: false });

            dragPan.velocity = [moveX, moveY];
            dragPan.lastPos = [e.clientX, e.clientY];
        }
    }
});

canvas.addEventListener('mouseup', (e) => {
    if (dragPan.isMousedown && e.button in dragPan.dragButtons) {
        dragPan.isMousedown = false;
        dragPan.startPos = null;
        canvas.style.cursor = '';
        applyInertia();
    }
});

canvas.addEventListener('mouseout', (e) => {
    if (dragPan.isDragging) {
        dragPan.isDragging = false;
        dragPan.startPos = null;
        canvas.style.cursor = '';
        applyInertia();
    }
});

canvas.addEventListener('contextmenu', (e) => {
    if (e.button in dragPan.dragButtons) {
        e.preventDefault();
    }
});

function applyInertia() {
    if (Math.abs(dragPan.velocity[0]) > dragPan.minVelocity || Math.abs(dragPan.velocity[1]) > dragPan.minVelocity) {
        map.panBy([-dragPan.velocity[0], -dragPan.velocity[1]], { animate: false });

        dragPan.velocity[0] *= dragPan.decay;
        dragPan.velocity[1] *= dragPan.decay;

        dragPan.inertiaAnimation = requestAnimationFrame(applyInertia);
    } else {
        dragPan.inertiaAnimation = null;
    }
}

Edit: added mouseout interaction and minMovement in pixels on drag to start panning the map

@HarelM HarelM added enhancement New feature or request PR is more than welcomed Extra attention is needed labels Jan 13, 2025
@HarelM
Copy link
Collaborator

HarelM commented Jan 13, 2025

I think the infrastructure around this is hard to change but you are welcome to try...

@gottesman
Copy link
Author

gottesman commented Jan 14, 2025

I'm trying to implement it myself, but I'm not that good with typescript so it's probably going to be a non-optimal solution.
I'll share my advancements later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request PR is more than welcomed Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants