/asscroll

Ash's Smooth Scroll 🍑

Primary LanguageJavaScriptMIT LicenseMIT

NPM Version npm bundle size jsDelivr hits (npm)

ASScroll is a Javascript hybrid smooth scroll setup that combines the
performance gains of virtual scroll with the reliability of native scroll.

This setup aims to be a lightweight solution that provides a consistent smooth scrolling experience across all platforms, devices and hardware.


Advantages over pure virtual scroll:

  • Accessible and keyboard friendly (without overriding the browser's native scroll)
  • No special cases to cater for when handling manual key detection (i.e. pressing space in a form input)
  • Doesn't stop working when hovering over an iframe
  • Handles hardware that doesn't fire the 'wheel' event i.e. Windows trackpads in Legacy Edge
  • No lag between DOM and WebGL elements on mobile, whilst retaining native inertia scroll

Other Features:

  • Horizontal scroll
  • Custom scroll bar
  • Use your own external RAF loop and resize events
  • Consistent lerp speeds on high refresh rate displays

No animation features are included as there are other libraries that can be used with ASScroll. GreenSock's ScrollTrigger is a great example and a demo is included below

Demos

Full showcase site coming soon

Sites using ASScroll

Table of contents


Install

npm i --save @ashthornton/asscroll or yarn add @ashthornton/asscroll

Also available via JSDelivr CDN which you can then include with a <script> tag.

Zero Config Setup

  1. Add the attribute asscroll-container to the parent element of the content to be smooth scrolled. By default, the first child found within will be scrolled. Both of these selectors be changed in the options.
<body>
    <div asscroll-container>
        <div><!-- The Y translation will be applied to this element --></div>
    </div>
</body>
  1. Import and initialise in Javascript
import ASScroll from '@ashthornton/asscroll'

const asscroll = new ASScroll()

window.addEventListener('load', () => {
    asscroll.enable()
})

ASScroll

Ash's Smooth Scroll 🍑

new ASScroll([parameters])

Creates an ASScroll instance

Param Type Default Description
[parameters] object
[parameters.containerElement] string | HTMLElement "[asscroll-container]" The selector string for the outer container element, or the element itself
[parameters.scrollElements] string | HTMLElement | NodeList "[asscroll]" The selector string for the elements to scroll, or the elements themselves
[parameters.ease] number 0.075 The ease amount for the transform lerp
[parameters.touchEase] number 1 The ease amount for the transform lerp on touch devices
[parameters.touchScrollType] string "none" Set the scrolling method on touch devices. Other options are 'transform' and 'scrollTop'. See the Touch Devices section for more info
[parameters.scrollbarEl] string ".asscrollbar" The selector string for the custom scrollbar element
[parameters.scrollbarHandleEl] string ".asscrollbar__handle" The selector string for the custom scrollbar handle element
[parameters.customScrollbar] boolean true Toggle the custom scrollbar
[parameters.scrollbarStyles] boolean true Include the scrollbar CSS via Javascript
[parameters.disableNativeScrollbar] boolean true Disable the native browser scrollbar
[parameters.disableRaf] boolean false Disable internal requestAnimationFrame loop in order to use an external one
[parameters.disableResize] boolean false Disable internal resize event on the window in order to use an external one
[parameters.limitLerpRate] boolean true Match lerp speed on >60Hz displays to that of a 60Hz display
[parameters.blockScrollClass] string ".asscroll-block" The class to add to elements that should block ASScroll when hovered

asscroll.targetPos ⇒ number

Returns the target scroll position.

Returns: number - Target scroll position

asscroll.currentPos ⇒ number

Gets or sets the current scroll position.

Returns: number - Current scroll position

Param Type Description
scrollPos number The desired scroll position

Example (Sets the scroll position to 200, bypassing any lerps)

asscroll.currentPos = 200

asscroll.maxScroll ⇒ number

Returns the maximum scroll height of the page.

Returns: number - Maxmium scroll height

asscroll.containerElement ⇒ HTMLElement

Returns the outer element that ASScroll is attached to.

Returns: HTMLElement - The outer element

asscroll.scrollElements ⇒ Array

Returns the the element(s) that ASScroll is scrolling.

Returns: Array - An array of elements ASScroll is scrolling

asscroll.isHorizontal ⇒ boolean

Returns whether or not ASScroll is in horizontal scroll mode

Returns: boolean - The status of horizontal scroll

asscroll.isScrollJacking ⇒ boolean

Returns whether or not ASScroll is actively transforming the page element(s). For example, would return false if running on a touch device and touchScrollType !== 'transform', or if ASScroll was currently disabled via the .disable() method.

Returns: boolean - The status of actively controlling the page scroll

asscroll.scrollPos

Deprecated

See: targetPos

asscroll.smoothScrollPos

Deprecated

See: currentPos

asscroll.enable([parameters])

Enable ASScroll.

Param Type Default Description
[parameters] object
[parameters.newScrollElements] boolean | NodeList | HTMLElement false Specify the new element(s) that should be scrolled
[parameters.reset] boolean false Reset the scroll position to 0
[parameters.restore] boolean false Restore the scroll position to where it was when disable() was called
[parameters.horizontalScroll] boolean false Toggle horizontal scrolling

Example (Enables ASScroll on the '.page' element and resets the scroll position to 0)

asscroll.enable({ newScrollElements: document.querySelector('.page'), reset: true })

Example (Enables ASScroll and restores to the previous position before ASScroll.disable() was called)

asscroll.enable({ restore: true })

asscroll.disable([parameters])

Disable ASScroll.

Param Type Default Description
[parameters] object
[parameters.inputOnly] boolean false Only disable the ability to manually scroll (still allow transforms)

Example (Disables the ability to manually scroll whilst still allowing position updates to be made via asscroll.currentPos, for example)

asscroll.disable({ inputOnly: true })

asscroll.update()

Call the internal animation frame request callback.

asscroll.resize([parameters])

Call the internal resize callback.

Param Type Description
[parameters] object
[parameters.width] number Window width
[parameters.height] number Window height

asscroll.on(eventName, callback)

Add an event listener.

Param Type Description
eventName string Name of the event you wish to listen for
callback function Callback function that should be executed when the event fires

Example (Logs out the scroll position when the 'scroll' event is fired)

asscroll.on('scroll', scrollPos => console.log(scrollPos))

Example (Returns the target scroll position and current scroll position during the internal update loop)

asscroll.on('update', ({ targetPos, currentPos }) => console.log(targetPos, currentPos))

Example (Fires when the lerped scroll position has reached the target position)

asscroll.on('scrollEnd', scrollPos => console.log(scrollPos))

asscroll.off(eventName, callback)

Remove an event listener.

Param Type Description
eventName string Name of the event
callback function Callback function

asscroll.scrollTo(targetPos, [emitEvent])

Scroll to a given position on the page.

Param Type Default Description
targetPos number Target scroll position
[emitEvent] boolean true Whether to emit the external scroll events or not

asscroll.onRaf()

Deprecated

See: update

asscroll.onResize()

Deprecated

See: resize


Custom Scrollbar

When customScrollbar is enabled in the options, a default set of styles will get added to the page that match the following HTML:

<div class="asscrollbar"><div class="asscrollbar__handle"><div></div></div></div>

This HTML will get added to your page automatically if you don't add it yourself.

You can change the classes that are used by changing the scrollbarEl and scrollbarHandleEl options.

You can use your own styles by setting the scrollbarStyles option to false.


Note: These styles are added via JS so will only be applied after JS has loaded. This may cause the native scrollbar to show for a moment before the styles are added resulting in a jump of the page.

You can include the styles necessary to hide the native scrollbar in your CSS by copy and pasting the contents of /css/index.css or simply by importing them into your CSS setup. For example:

@import '~@ashthornton/asscroll/css'

Usage with external requestAnimationFrame

Multiple requestAnimationFrame loops causes a significant impact on performance, so ASScroll provides the option to disable its internal rAF loop in order for you to update it yourself externally.

const asscroll = new ASScroll({
    disableRaf: true
})

// Regular RAF loop
requestAnimationFrame(onRaf)
function onRaf() {
    asscroll.update()
    requestAnimationFrame(onRaf)
}

// Or use with GSAP's internal RAF
gsap.ticker.add(asscroll.update)

Usage with external window resize

You may already have a window resize listener in your project where you get the window size for other components. Rather than let ASScroll access these window properties during its internal resize event, which causes extra browser calculations, you can pass your own values.

const asscroll = new ASScroll({
    disableResize: true
})

window.addEventListener('resize', () => {
    // trigger other resize logic
    const width = window.innerWidth
    const height = window.innerHeight
    asscroll.resize({ width, height })
})

Touch Devices

ASScroll provides three options for handling itself on touch devices via the touchScrollType option:

  • 'none': Disabled completely, falling back to native scroll whilst still providing scroll position properties
  • 'transform': Continue to transform the scroll elements with an ease setting specifically to touch devices
  • 'scrollTop': Utilise the scrollTop value of the container element when used in conjunction with CSS that prevents resizes and browser UI from toggling

The scrollTop option is primarily used for syncing WebGL content to the scroll position on mobile, without overriding the native inertia scroll. Some CSS will be injected automatically when this option is enabled. This CSS can be found here.

touchScrollType: transform will also achieve DOM and WebGL content sync but is more expensive.

Changelog

View Changelog