/React-stroller

🚶‍♂️Scroll as you Stroll - the most native custom scrollbars ever made

Primary LanguageTypeScriptMIT LicenseMIT

React-Sctroller 📜🏃‍




The right page scroller - browser friendly custom draggable scrollbars. Demo

Capabilities

  • ⛓display any custom scroll bar, even, well, nyan-cat🐈🏳️‍🌈🏳️‍🌈🏳️‍🌈 scroll bar
  • 📜 vertical and horizontal, as well as not matching main scroll axis - like displaying horizontal "reading indicator" for the vertical scroll.
  • 👨‍🔬display scrollbar 1) inside 2) outside 3) at window the target scrollable
  • 🤓support for "ScrollIndicators", and actually any other "custom" 🤹‍♀️ effects
  • 🚀support for passive scroll observation (🥳 performance)
  • 🧸easy to use out of the box/fully customizable.

API

Stroller provides 4 components - to create Scrollable container, to draw a scroll bar and to combine all together. The 4th component is a magic one - StrollCaptor.

Written in TypeScript. IDE should provide 100% prop competition.

Provides friction-less expirience, as long stroller does not hook into onwheel event, observing browser scroll silently, keeping all animations smooth.

Could be used inside and outside scrollable node, autodetecting nearest scrollable parent.

import {StrollableContainer} from 'react-stroller';

<BlockWithHeightSet>
  <StrollableContainer draggable>
    <UL/>
  </StrollableContainer>
</BlockWithHeightSet>

React-stroller consists from 3 parts:

  • Strollable - "scrollable" container. It will remove native browser scroll.
  • Stroller - the main component, containing all the logic
  • StrollCaptor - component, which bypasses scrollable elements, finding the nodes to control.

StrollableContainer - just combines them all in the right order.

Strollable

Is a scrollable, but scroll-bar-less container. It uses padding-hack to hide browser scrollbars on any system.

Read more about scroll bars here - Scroll to the future

import {Strollable} from 'react-stroller';

<div className="styleWithSizeDefined">
    <Strollable axis="horizontal | vertical" [gap]={gapFromScroll}>
     Strollable will consume 100% width/height - all the possible space 
     setup `position:relative` to the child
     and display any content inside
    </Strollable> 
</div>

Stroller

Stroller is a React-scrollbar. It observes scroll event, and position itself where it should be. Stroller likes to be placed inside Strollable.

Meanwhile could be used to scroll "unscrollable"(unwheelable) containers.

import {Stroller} from 'react-stroller';

<div style={{ position:'relative', overflow: 'hidden'}}>
  <Stroller
    // drop drop Scroller anywhere inside scrollable node 
    // all props are optional
    axis="horizontal | vertical"
    bar={() =><div>Your Own scroll _bar_ implementation</div>}
    scrollBar={() => <div>Your Own __scroll bar__ implementation</div>}
    oppositePosition /* if you want scroll bar on left, or top */
    draggable /* should it be draggable? */     
    barHeight={(height, scrollHeight, {dragging}) => dragging ? 42 : 24} /* you can override scroll element height */
    scrollKey={any} // key to indicate that stroller should update data (scroll height)
    passive={true} // enable passive scroll observation. Better for perf, worse for scroll synchronization
    onScroll={() => {}} // handle scroll event
  />
</div>

Stroller will find nearest scrollable parent, and set a scroll bar. bar, you can override is just a view, an internal node for a real Bar Stroller will draw itself. bar should fill 100% height and 100% width, and be just style.

scrollBar property currently is not documented, and used only by react-nyan-scroll.

StrollableContainer

Just combine all Components together in the right order

import {StrollableContainer} from 'react-stroller';

<div style={{height:'100500px'}}>
    <StrollableContainer [gap]={marginFromScroller} [scrollKey]={any}>
      any content
    </StrollableContainer>
</div>
  • Do not set overflow property for StrollableContainer's parent, as long it will set it for itself.
  • Always set height, you can change it via flex-grow, or flex-shrink, but it has to be set.

StrollCaptor - the secret sauce

By default Stroller could be not super smooth, as long it will be first "scrolled" as a part of scrollable node content, and then will be moved to a new position.

It is natural to have some visual glitches and jumps, if you are not controlling wheel and emulating scroll event as any other "custom-scroll-bar" does.

StrollCaptor is a fix - place it inside scrollable node, while placing Stroller outside. As result - on component scroll Strolled will not be moved, removing any possible jumps.

<div style={{position:'relative'}}>
  <Stroller>
     <div style={{height:500}}>
       <StrollableContainer> // this is optional
         <StrollCaptor />
          // StrollCaptor will report to Stroller about his scrollable parent
          // which is a child for Stroller, and invisible by default.
       </StrollableContainer>
     </div>
  </Stroller>
</div>

StrollerState

It's possible to create "Virtual Container", similar to react-window, using React-Strollable.

Stroller will expose internal state via StrollerState component, you can consume and then react to scroll or resize.

<StrollableContainer>
  <StrollerState>
   {({
     scrollWidth: number,
     scrollHeight: number,
   
     clientWidth: number,
     clientHeight: number,
   
     scrollLeft: number,
     scrollTop: number,
   }) => (
     // render elements based on visibility.
   )}
  </StrollerState>
</StrollableContainer>

ScrollIndicators

Just read stroller state and display them

import {StrollerState} from 'react-stroller';
export const VerticalScrollIndicator= () => (
  <StrollerState>
    {({scrollTop, scrollHeight, clientHeight}) => (
      <>
        <span          
          className={cx(
            styles['indicator--top'],            
            scrollTop === 0 && styles['indicator--hidden']
          )}
        />
        <span
          className={cx(
            styles['indicator--bottom'],
            scrollTop + clientHeight >= scrollHeight && styles['indicator--hidden']
          )}
        />
      </>
    )}
  </StrollerState>
);

Separated scrollbar

You might display scrollbar in the parent, while observing scroll at the children

<Stroller> // "Bar" would be displayed on this level
  any code
  <DivWithKnownHeight> // we are "observing" scroll in this node
    <Strollable> 
      <StrollCaptor/>
        {children}
    </Strollable>
    <VerticalScrollIndicator/>
  </DivWithKnownHeight>
  any code
</Stroller>            

Testing

React-stroller is a library, which could not be unit tested. Things like smooth scroll, right overflows and touch-n-feel experience are not something robot could test.
Tested manually and carefully by a human being.

Uses TypeScript and a finite state machine(Faste) underneath, for a better confidence.

See also

react-custom-scrollbars - another great package for custom scrollbars

React-Locky - gather a full control under your scroll.

React-focus-lock - scope your focus in browser friendly way.

Licence

MIT