Developer-Mike / obsidian-advanced-canvas

âš¡ Supercharge your Obsidian.md canvas experience! Create presentations, flowcharts and more!
GNU General Public License v3.0
260 stars 13 forks source link

[FR] Canvas Momentum Panning #98

Open cosmicobscura opened 2 weeks ago

cosmicobscura commented 2 weeks ago

Hi, @Developer-Mike! Hope you're doing well !

I have another UX feature request for my fav Canvas Developer :D As I'm sure you know, "momentum scrolling" is a neat UX feature that adds greatly to the sense of smoothness when navigating large space. I don't know why its not including with the core Canvas plugin . I mean .. freaking Apple had it since 2007 !! Anyway, having an exclusively Canvas-based vault, it's been getting increasingly harder to navigate my constantly growing canvases as the current "static" drag function requires multiple drags/zooms to move around.

My idea is that the same motion of momentum scrolling could be added to the function of dragging/panning the canvas using the RMB to make for a more dynamic navigation experience. I would really love for this to be implemented as soon as possible and that's why I took it upon myself to be of some help to you writing the code. disclaimer: I'm not a coder by any means but I have the spirit I guess 😅

I took a look into the dev console and found that .canvas-wrapper.is-dragging is being activated while using the drag function. I then tried, with the help of Gemini, to make a primitive JS code for the "momentum panning" function . Hopefully it could be a decent enough head start for you to implement this into Advanced Canvas :

const canvasWrapper = document.querySelector('.canvas-wrapper') as HTMLElement;

let startX: number = 0;
let startY: number = 0;
let prevX: number = 0;
let prevY: number = 0;
let velocityX: number = 0;
let velocityY: number = 0;
let isDragging: boolean = false;
let animationFrameId: number | null = null;

// Smoothing factor and deceleration factor for refined momentum
const smoothingFactor = 0.7;
const decelerationFactor = 0.95;

canvasWrapper.addEventListener('mousedown', (event: MouseEvent) => {
    if (event.button === 2) {
        startX = prevX = event.clientX;
        startY = prevY = event.clientY;
        isDragging = true;

        // Stop any ongoing momentum animation
        cancelAnimationFrame(animationFrameId!);
        animationFrameId = null;
    }
});

canvasWrapper.addEventListener('mousemove', (event: MouseEvent) => {
    if (event.buttons === 2 && isDragging) {
        const newVelocityX = event.clientX - prevX;
        const newVelocityY = event.clientY - prevY;

        // Apply smoothing to velocity for a more natural feel
        velocityX = velocityX * smoothingFactor + newVelocityX * (1 - smoothingFactor);
        velocityY = velocityY * smoothingFactor + newVelocityY * (1 - smoothingFactor);

        prevX = event.clientX;
        prevY = event.clientY;
    }
});

canvasWrapper.addEventListener('mouseup', (event: MouseEvent) => {
    if (event.button === 2) {
        isDragging = false;

        // Get the current transform style
        const currentTransform = window.getComputedStyle(canvasWrapper).getPropertyValue('transform');

        // Extract the original translate values (before the drag)
        const originalTranslateMatch = currentTransform.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/);
        const originalTranslateX = originalTranslateMatch ? parseFloat(originalTranslateMatch[1]) : 0;
        const originalTranslateY = originalTranslateMatch ? parseFloat(originalTranslateMatch[2]) : 0;

        // Set initial values for the momentum animation
        let currentTranslateX = originalTranslateX;
        let currentTranslateY = originalTranslateY;

        // Function to update the canvas position during the animation
        function animateMomentum() {
            currentTranslateX += velocityX;
            currentTranslateY += velocityY;

            // Apply the current transform values
            canvasWrapper.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(0.125) translate(-17105.1px, -988.825px)`;

            // Decelerate the velocity for the next frame
            velocityX *= decelerationFactor;
            velocityY *= decelerationFactor;

            // Continue the animation if velocity is still significant
            if (Math.abs(velocityX) > 0.5 || Math.abs(velocityY) > 0.5) { // Adjust threshold as needed
                animationFrameId = requestAnimationFrame(animateMomentum);
            }
        }

        // Start the momentum animation
        animationFrameId = requestAnimationFrame(animateMomentum);
    }
});

and that's it ! thanks in advance :)

Developer-Mike commented 1 week ago

Thank you so much for the feature request. While I really appreciate your effort and even providing a code snippet, I suspect that this feature will be difficult to implement. But this doesn't mean that it won't get added! I'll take a look at it when I have some spare time.