ankurrsinghal / svelte-legos

A framework for Svelte Utilities 💡 Current status: 87 utilities.
https://svelte-legos.surge.sh
MIT License
803 stars 29 forks source link

[ Feature Request ] useSmoothScrolling #98

Open waffleflopper opened 1 year ago

waffleflopper commented 1 year ago

For a project I was doing I wanted all my users to have a nice scrolling experience - like macOS users have when using native hardware (magic mouse, trackpad). It's always bothered me how rough it can feel on windows or using a 3rd party mouse. So I wrote this action and thought I would see if it's something people are interested in. I'm not very familiar with Svelte-Legos, so I didn't want to just fork and open a pull request out of nowhere. Was sent here from a different library project when I asked if they were interested in the action.

/* helper */
function clamp(num: number, min: number, max: number): number {
    return Math.min(Math.max(num, min), max);
}

/* useSmoothScrolling */
interface SmoothScrollOptions {
  friction?: number;
  maxSpeed?: number;
  deadZone?: number;
}

//friction around 0.94-0.95 feels the best, imo (behaves oddly outside of 0.9 to 0.96)
//lower max speed does weird things if users mouse wheel is set to scroll a lot of lines at once
//deadZone is to prevent run away scrolling from small scroll movements
export default function useSmoothScrolling(node: HTMLElement, params: SmoothScrollOptions) {
    const {friction = 0.94, maxSpeed = 60, deadZone = 0.5} = params;

    //todo: map 1-100 to 0.9 to 0.96 to make friction more readable
    let clampedFriction = clamp(friction, 0.9, 0.96);

    let velocity = 0;
    const wheelHandler = (e: WheelEvent) => {
        velocity += e.deltaY * 0.1;
        velocity = clamp(velocity, -maxSpeed, maxSpeed);
    };

    const animationHandler = () => {
            if (Math.abs(velocity) > deadZone) {
        velocity *= clampedFriction;
        node.scrollTop += velocity;
      } else {
        velocity = 0;
      }
      frameId = window.requestAnimationFrame(animationHandler);
    };

    node.addEventListener('wheel', wheelHandler, { passive: true });
    let frameId = window.requestAnimationFrame(animationHandler);

    return {
      destroy() {
        node.removeEventListener('wheel', wheelHandler);
        window.cancelAnimationFrame(frameId);
      }
    };
}

Link to Gist

If it's something people are interested in I'm happy to refactor it and make a more robust implementation.

ankurrsinghal commented 1 year ago

Hey @waffleflopper thanks for the idea. Totally if you can create a proper implementation and raise a PR it will be helpful, :).

waffleflopper commented 1 year ago

Been without internet for a while (Army stuff for upcoming deployment), but I'll get something put together. Thanks!