npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@smoovy/scroller

v2.2.1

Published

smooth scrolling for the world

Downloads

278

Readme

@smoovy/scroller

Version Size

Installation

npm i @smoovy/scroller

Usage

Smooth scrolling is split up into two different approaches: native and default. With "native", you get the best accessibility, since it just animates the scroll position of the window or container.

If you want to have more control and use a CSS transform-based scrolling, you can use the ElementScroller. It'll create necessary DOM elements (container and wrapper) and use those to move the content.

In order to use the native or default scroller you can just import it from the package and create a new instance. Here's how you can create both types:

Using the native scroller

import { NativeScroller } from '@smoovy/scroller/native';

const scroller = new NativeScroller();

// enable mouse pointer events for drag (all options at the bottom)
const scroller = new NativeScroller({
  pointerEvents: true
});

Configuration is optional. If the config is omitted, the window will be used as a container.

Using the default scroller

import { ElementScroller } from '@smoovy/scroller/element';

const scroller = new ElementScroller();

// make it slower (all options at the bottom)
const scroller = new ElementScroller({
  damping: 0.08
});

Configuration is optional. If the config is omitted, the body will be used as a container.

Using just the virtual scroller

Sometimes you just want to have the controls for scrolling. For that you can just create the a core scroller. This will give you the ability to customize the scrolling experience from scratch with all the controls taken care of (touch, wheel events, locking etc.).

import { Scroller } from '@smoovy/scroller/core';

const scroller = new Scroller();

Note: ElementScroller and NativeScroller are just variations of the Scroller.

Listening to events

There are two types of scroll positions: output and virtual. The output position is the current animated position. The virtual position is the animated position the output is animating towards. So the virtual is always in sync with the "native" scroll position, while output is where the animation is currently at. You can listen to both:

scroller.onScroll(({ x, y }) => {
  console.log('scrolled to', x, y);
});

scroller.onVirtual(({ x, y }) => {
  console.log('will scroll to', x, y);
});

If you want to listen to resize events you simply attach this listener:

scroller.onResize(() => {
  console.log('content has changed size or window resized');
});

Locking the scroller

All variations come with a locking mechanism, that allows for multiple contexts to lock the scroller. This is done to ensure that, on your website, you can allow multiple components to lock the same scroller and ensure it's locked as long as the component requests it.

// lock a scroller completely
scroller.lock();

// unlock a scroller
scroller.lock(false);

// lock the scroller with context
scroller.lock(true, 'menu');
scroller.lock(true, 'other-component');

scroller.lock(false, 'menu') // still locked because of other-component
scroller.lock(false, 'other-component'); // full unlocked now

// you can always go deepter and only lock a specific keyboard event
scroller.lock({ keyboard: { Space: true } });

// listening for locks
scroller.onLock(({ locked }) => {
  console.log('scroller has been ' + (locked ? 'locked' : 'unlocked'));
});

It's also possible to only lock certain functionalities of the scroller. Sometimes you want to disable user interaction, but still be able to control the scroller programmatically.

// disable user controls (touch, wheel etc. only)
scroller.lock({ controls: true }, 'menu');

// this still works
scroller.scrollTo({ y: 100 });

// enable user controls for the menu lock
scroller.lock({ controls: false }, 'menu');

Scroll to and tween to

Each scroller can be controlled programmatically with the scrollTo method. After the new position is set it will animate to that position. But you can also skip this animation and scroll to the position immediately.

// scroll to the position 500
scroller.scrollTo({ y: 500 });

// jump to the position 500 immediately
scroller.scrollTo({ y: 500 }, true);

There's no built-in tween engine or easing function for the animation. Since most of the time you're already using one. So in order to animate the position with a tween you could do the following.

tween.fromTo({ y: scroller.output.y }, { y: 500 }, {
  onUpdate: (pos) => scroller.scrollTo(pos, true);
})

Configuration

| Name | Type | Default | Description | |-------------------|-----------------------|---------|-------------------------------------------------------------------------------------------------------------------------| | damping | number | 0.1 | The damping value used to align the current position with the new position. | | autoStart | boolean | true | Whether to start the ticker immediately. | | threshold | number | 0.001 | The threshold used to determine when the scroll animation has settled (ended). | | frequency | number | 60 | The refresh rate that's being simulated to achieve frame-independent damping. | | keyboardEvents | boolean | true | Whether to allow keyboard events to simulate the native behavior of the browser. | | lineHeight | number | 16 | The default line height of the browser used for legacy delta calculations and keyboard events. | | wheelMultiplier | number | 1 | A multiplier for the wheel delta value to make scrolling go faster, usually resulting in less natural feeling. | | pointerEvents | boolean | false | Whether to allow the pointer to drag the content and simulate touch events with the mouse, including a slight inertia. | | pointerMultiplier | number | 1 | A multiplier for the pointer drag effect, altering the pivot point during drag. | | pointerVelocity | number | 25 | A multiplier for the pointer drag effect on velocity before the user releases the button. | | touchMultiplier | number | 1 | A multiplier for the touch drag effect, altering the pivot point during drag. | | touchVelocity | number | 20 | A multiplier for the touch drag effect on velocity before the user removes his finger. | | touchEvents | boolean | true | Whether to enable touch events and simulate the mobile touch experience. | | inertiaTarget | Window | HTMLElement | Window | The target element or window used to track events for the inertia and touch simulation. Optional. | | wheelTarget | Window | HTMLElement | Window | The target element or window used to track events for the mouse wheel events. Optional. |

Default scroller config

| Name | Type | Default | Description | |----------|----------------------------|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | container| HTMLElement | document.body | The container element used as a viewport for the scrollable area, containing the content. Optional. | | wrapper | HTMLElement | None | The wrapper element that contains the content, defining the scroll limit. Transforms are set on this element. Optional. | | focus | boolean | true | Whether to enable focus bypass, scrolling to the element that has been focused. | | styles | Partial| { width: '100%', height: '100%', overflow: 'hidden' } | Styles set on the container element to define its appearance and behavior. Optional. |

All core configurations apply to this too

Native scroller config

| Name | Type | Default | Description | |------------|---------------------------|-----------------|---------------------------------------------------------------------------------------------------------------------------| | container | HTMLElement | Window | document.body | The container element used as a viewport for the scrollable area, within which the content sits. Optional. | | wrapper | HTMLElement | None | The wrapper element that contains the content and defines the scroll limit. Transforms are set on this. Optional. | | focus | boolean | true | Enables focus bypass, which scrolls to the focused element during scroll animation, preserving default behavior. Optional. |

All core configurations apply to this too

Accessibility breakdown

You should always be aware of the limitations when using scrolljacking to not worsen the user experience. Especially when optimizing for a lot of different devices. Sometimes it's better to remove smooth scrolling alltogether (e.g. on mobile) to ensure the user gets the same scrolling expericence he's familiar with.

| Function | Native | Element | -------- | ------ | ------- | Scroll to focus element | ✅ | ✅ | Keybard scroll controls | ✅ | ✅  | Simulate touch events | ✅ | ✅ | Pointer touch events | ✅ | ✅ | Lock scroll position | ✅ | ✅ | Scroll to search result | ✅ | ❌ | Native scrollbar control | ✅ | ❌ | Native sticky elements | ✅ | ❌ | Native CSS scroll-snap | ❌ | ❌

Also if you want to make sure to give the user the best experience, I suggest to use a detection for prefers-reduced-motion and disable smooth scrolling when the user prefers low-animated websites.

if ( ! window.matchMedia('(prefers-reduced-motion)').matches) {
  // initialize smooth scrolling
} else {
  // use native scrolling
}

Experience it in action

Development commands

// Serve with parcel
npm run serve

// Build with rollup
npm run build

// Run Jest unit tests
npm run test

// Run TSLinter
npm run lint

License

See the LICENSE file for license rights and limitations (MIT).