Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-infinite-grid-scroller

Package Overview
Dependencies
Maintainers
1
Versions
83
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-infinite-grid-scroller

Heavy-duty vertical or horizontal infinite scroller

  • 1.0.0-Beta-4.8
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
6
decreased by-80%
Maintainers
1
Weekly downloads
 
Created
Source

react-infinite-grid-scroller (RIGS)

Heavy-duty vertical or horizontal infinite scroller

npm licence

Key Features

  • vertical or horizontal scrolling
  • designed for both "heavy" and "light" cell content (React components)
  • supports both uniform and variable cell lengths (for both vertical and horizontal)
  • single or multiple rows or columns
  • dynamically variable virtual list size
  • limited sparse in-memory cache, to preserve content state, with an API
  • repositioning mode when rapidly scrolling (such as by using the scroll thumb)
  • dynamic pivot (horizontal/vertical back and forth) while maintaining position in list
  • automatic reconfiguration with viewport resize
  • dynamic recalibration with async content refresh
  • supports nested RIGS scrollers

Demo Site

See the demo site.

Key Technologies

RIGS uses these key technologies:

Therefore RIGS is best suited for modern browsers.

Architecture

Architecture

Notes: The Cradle is kept in view of the Viewport, such that the axis is always near the top or left of the Viewport (depending on vertical or horizontal orientation). There are two CSS grids in the Cradle, one on each side of the axis. As CellFrames are added to or removed from the grids, the grid on the left expands toward or contracts away from the top or left of the Scrollblock (depending on orientation), and the grid on the right expands toward or contracts away from the bottom or right of the Scrollblock.

CellFrames display individual user components. CellFrames are created and destroyed on a rolling basis as the Cradle re-configures and moves around the Scrollblock to stay in view, but user components are maintained in the internal cache until they go out of scope. New CellFrames fetch user components from the internal cache (portals in the React virtual DOM) or from the host through the user-supplied getItem function, as needed.

Not shown are two triggerlines (0 width or height divs, depending on orientation) which straddle the top or left edge of the Viewport. Whenever one of these triggerlines crosses the Viewport edge (through scrolling), an IntersectionObserver sends an interrupt to the Cradle to update its content and configuration. Triggerlines are located in the last CellFrame of the head grid, unless the scroller is at the very top of its list, in which case the triggerlines are located in the first CellFrame of the tail grid.

Usage

This is the minimum configuration.

import Scroller from 'react-infinite-grid-scroller'

// ...

<div style = { containerstyle }>
  <Scroller 
      cellHeight = { cellHeight }
      cellWidth = { cellWidth }
      startingListSize = { startingListSize } // this constitutes a virtual 0-based array
      getItem = { getItem } // a function called by RIGS to obtain a specified user component by index number
  />
</div>

The scroller's highest level component, the Viewport, is a div with position:absolute, and inset:0, so the host container should be styled accordingly.

Note that scroller-generated elements show a data-type attribute in browser inspectors (eg. 'viewport').

User components loaded to CellFrames are placed in a data-type = 'contentenvelope' div. In 'uniform' layout this has position:absolute and inset:0. In 'variable' layout it has width:100% and max-height = cellHeight for 'vertical' orientation, and height:100% and max-width = cellWidth for 'horizontal' orientation. In any case it has overflow:hidden.

Animated GIF

This is a random screenshare, showing nested scrollers in a resized browser (33% normal).

animation

Compatible browsers

RIGS works on Chrome, Microsoft Edge, Firefox and Safari.

chrome edge firefox safari

Scroller properties

propertyvaluenotes
[REQUIRED]
cellHeightinteger: number of pixels for cell heightrequired. Applied to height for 'uniform' layout, 'vertical' orientation. Applied to max-height for 'variable' layout, 'vertical' orientation. Approximate, used for fr (fractional allocation) for 'horizontal' orientation
cellWidthinteger: number of pixels for cell widthrequired. Applied to width for 'uniform' layout, 'horizontal' orientation. Applied to max-width for 'variable' layout, 'horizontal' orientation. Approximate, used for fr (fractional allocation) for 'vertical' orientation
startingListSizeinteger: the starting number of items in the virtual listrequired. Can be modified at runtime. Constitutes a 0-based virtual array
getItemhost-provided function. Parameters: index (integer, 0 based), and session itemID (integer) for tracking and matching. Arguments provided by systemrequired. Must return a React component or promise of a component (React.isValidElement), or undefined = unavailable, or null = end-of-list
[SCROLLER OPTIONS]
orientationstring: 'vertical' (default) or 'horizontal'direction of scroll
layoutstring: 'uniform' (default) or 'variable'specifies handling of the height or width of cells, depending on orientation. 'uniform' is fixed cellHeight/cellWidth. 'variable' is constrained by cellHeight/cellWidth (maximum) and cellMinHeight/cellMinWidth (minimum)
startingIndexinteger: starting index when the scroller first loadsdefault = 0
paddinginteger: number of pixels padding the Cradledefault = 0
[MORE CELL OPTIONS]
gapinteger: number of pixels between cellsthere is no gap at start or end of rows or columns; default = 0
cellMinHeightinteger: default = 25, minimum = 25, maximum = cellHeightused for 'variable' layout with 'vertical' orientation. Applied to min-height
cellMinWidthinteger: default = 25, minimum = 25, maximum = cellWidthused for 'variable' layout with 'horizontal' orientation. Applied to min-width
[SYSTEM SETTINGS]
runwaySizeinteger: number of rows in the Cradle just out of view at head and tail of listdefault = 1. minimum = 1. Gives time to assemble cellFrames before display
cachestring: 'cradle' (default), 'keepload', 'preload''cradle' matches the cache to the contents of the Cradle. 'keepload' keeps user components in the cache as loaded, up to cacheMax (and always Cradle user components). 'preload' loads user components up to cacheMax, then adjusts cache such that Cradle user components are always in the cache
cacheMaxinteger: at minimum (maintained by system) the number of user components in the Cradleallows optimization of cache size for memory limits and performance
useScrollTrackerboolean: default = trueallows suppression of system feedback on position within list while in reposition mode, if the host wants to provide alternative feedback based on data from callbacks
placeholdera lightweight React component for cellFrames to load while waiting for the intended cellFrame componentsoptional (replaces default placeholder). parameters are index, listsize, message, error. Arguments set by system
usePlaceholderboolean: default = trueallows suppression of use of default or custom placeholder. Placeholders show messages to the user while user components are fetched, and report errors
[ADVANCED OBJECTS]
stylesobject: collection of styles for scroller componentsoptional. These should be "passive" styles like backgroundColor. See below for details
placeholderMessagesobject: messages presented by the placeholderoptional, to replace default messages. See below for details
callbacksobject: collection of functions for feedback, and interactions with scroller componentsoptional. See below for details
technicalobject: collection of values used to control system behaviouruse with caution. optional. See below for details
scrollerPropertiesrequested by user components by being set to null by user, instantiated with an object by systemrequired for nested RIGS; available for all user components. Contains key scroller settings. See below for details

Notes: For explicit cache management capability, a unique session itemID (integer) is assigned to a user component as soon as it enters the cache. The itemID is retired as soon as the user component is removed from the cache. If the same component is re-introduced into the cache, it is assigned a new session-unique itemID.

The itemID for a user component is given to the host with the getItem call to obtain the component, so that the host can track the user component in the cache. If the user component is assigned to a new index number (see the returned function object cache management section below) the host will still be able to track the user component with the itemID.

The host can track removal of a user component and its itemID from the cache through tracking its associated index removal through the deleteListCallback return value, and the return values from cache management functions.

Most of the time the itemID can be ignored.

Also, note that the cache is reloaded with a new getItem function.

styles object

Create a style object for each of the elements you want to modify. The styles are not screened, though the RIGS essential styles pre-empt user styles. Be careful to only include "passive" styles (like color, backgroundColor) so as not to confuse the scroller. Do not add structural items like borders, padding etc.

styles = {
  viewport: {}, 
  scrollblock: {}, 
  cradle: {},
  scrolltracker: {},
  placeholderframe: {},
  placeholderliner: {},
  placeholdererrorframe: {},
  placeholdererrorliner: {},
}

You may want to add overscrollBehavior:'none' to the viewport styles to prevent inadvertent reloading of your app in certain browsers when users scroll past the top of a vertical list.

The scrolltracker is the small rectangular component that appears at the top left of the viewport when the list is being rapidly repositioned. The scrolltracker shows the user the current virtual index and total listsize during the repositioning process.

The placeholder styles are applied only to the default placeholder.

placeholderMessages object

Replace any of the default messages used by the placeholder.

const placeholderMessages = {
    loading:'(loading...)',
    retrieving:'(retrieving from cache)',
    null:'end of list', // is returned with itemExceptionCallback
    undefined:'host returned "undefined"', // displayed, and returned with itemExceptionCallback
    invalid:'invalid React element', // displayed, and returned with itemExceptionCallback
}

callbacks object

Callbacks are host-defined closure functions which the Cradle calls to provide data back to the host. Cradle returns data by setting the arguments of the callbacks. Include only the callbacks in the callbacks object that you want the Cradle to use. The following are recognized by the Cradle:

callbacks: {

     // called at setup...
     functionsCallback, // (functions) - get an object that has api functions
     
     // index tracking, called when triggered...
     referenceIndexCallback, // (index, location, cradleState) - change of index adjacent to the axis
     repositioningIndexCallback, // (index) - current virtual index number during rapid repositioning
     preloadIndexCallback, // (index) - current index being preloaded
     itemExceptionCallback, // (index, itemID, returnvalue, location, error) - details about failed getItem calls

     // operations tracking, called when triggered
     changeListsizeCallback, // (newlistsize) - triggered when the listsize changes for any reason
     deleteListCallback, // (reason, deleteList) - data about which items have been deleted from the cache
     repositioningFlagCallback, // (flag) - notification of start (true) or end (false) of rapid repositioning
     
}

An example of a callback closure (functionsCallback):

const scrollerFunctionsRef = useRef(null)

const functionsCallback = (functions) => {

    scrollerFunctionsRef.current = functions // assign the returned functions object to a local Ref

}

//...

scrollerFunctionsRef.current.scrollToIndex(targetIndex)

Details about the callbacks:

callbackparameters:datatypesnotes
[GET FUNCTIONS]
functionsCallbackfunctions: objectthe object returned contains Cradle functions that the host can call directly. This is the API. functionsCallback is called once at startup. See below for details
[TRACK INDEXES]
referenceIndexCallbackindex: integer, location: string, cradleState: stringlocation can be 'setCradleContent', 'updateCradleContent'. Keeps the host up to date on the index number adjacent to the Cradle axis, and the state change that triggered the update
repositioningIndexCallbackindex: integerthe current index during repositioning. Useful for feedback to user when host sets useScrollTracker property to false
preloadIndexCallbackindex: integerduring a preload operation, this stream gives the index number being preloaded
itemExceptionCallbackindex: integer, itemID: integer, returnvalue: any, location: string, error: Errortriggered whenever getItem does not return a valid React component
[TRACK OPERATIONS]
changeListsizeCallbacknewlistsize: integernotification of a change of list size. Could be from getItem returning null indicating end-of-list, or an API call that results in change of list size
deleteListCallbackreason: string, deleteList: arraygives an array of indexes that have been deleted from the cache, and text of the reason
repositioningFlagCallbackflag: booleancalled with true when repositioning starts, and false when repositioning ends. Useful for feedback to user when host sets useScrollTracker property to false

returned functions object

Details about the functions returned in an object by functionsCallback:

functionparameters: datatypesreturn value: datatypenotes
[OPERATIONS]
scrollToIndexindex:integervoidplaces the requested index item at the top visible row or left visible column of the scroller, depending on orientation
setListsizeindex:integervoidchanges the list size
reloadnonevoidclears the cache and reloads the Cradle at its current position in the virtual list
clearCachenonevoidclears the cache and the Cradle (leaving nothing to display)
[SNAPSHOTS]
getCacheIndexMapnonemap: Mapsnapshot of cache index (=key) to itemID (=value) map
getCacheItemMapnonemap: Mapsnapshot of cache itemID (=key) to object (=value) map. Object = {index, component} where component = user component
getCradleIndexMapnonemap: Mapsnapshot of Cradle index (=key) to itemID (=value) map
[CACHE MANAGEMENT]
insertIndexindex:integer, rangehighindex = integer or nullchangeList:array, replaceList:arraycan insert a range of indexes. Displaced indexes, and higher indexes, are renumbered. Changes the list size; synchronizes the Cradle
removeIndexindex:integer, rangehighindex = integer or nullchangeList:array, replaceList:arraya range of indexes can be removed. Higher indexes are renumbered. Changes the list size; synchronizes to the Cradle
moveIndextoindex:integer, fromindex:integer, fromhighrange = integer or nullprocessedIndexList:arraya range of indexes can be moved. Displaced and higher indexes are renumbered. Changes the list size; synchronizes to the Cradle
remapIndexeschangeMap:MapmodifiedIndexList: array,
processedIndexList: array,
deletedIndexList: array,
indexesOfReplacedItemsList: array,
deletedOrphanedItemIDList: array,
deletedOrphanedIndexList: array,
errorEntriesMap: Map,
changeMap: Map (same as input parameter)
changeMap is index (=key) to itemID (=value) map. indexes or itemIDs not in the cache are ignored. indexes with values set to null are deleted. indexes with values set to undefined have their component items replaced. itemIDs are assigned to the new indexes; synchronizes to the Cradle. List size is adjusted as necessary

Notes: cache management functions are provided to support drag-n-drop, sorting, and filtering operations.

Cache management functions operate on indexes and itemIDs in the cache, and generally ignore indexes and itemIDs that are not in the cache.

This is a sparse in-memory cache, and indexes in the cache are not guaranteed to be contiguous.

technical object

These properties would rarely be changed.

propertyvaluenotes
showAxisboolean, default = falseaxis can be made visible for debug
triggerlineOffsetinteger, default = 10distance from cell head or tail for content shifts above/below axis
VIEWPORT_RESIZE_TIMEOUTinteger, default = 250milliseconds before the Viewport resizing state is cleared
ONAFTERSCROLL_TIMEOUTinteger, default = 100milliseconds after last scroll event before onAfter scroll event is fired
IDLECALLBACK_TIMEOUTinteger, default = 175milliseconds timeout for requestIdleCallback
VARIABLE_MEASUREMENTS_TIMEOUTinteger, default = 250milliseconds to allow setCradleContent changes to render before being measured for 'variable' layout
CACHE_PARTITION_SIZEinteger, default = 30the cache is partitioned for performance reasons
MAX_CACHE_OVER_RUNnumber, default = 1.5max streaming cache size over-run (while scrolling) as ratio to cacheMax

scrollerProperties object

Cell components can get access to current RIGS properties, by requesting the scrollerProperties object.

The scrollerProperties object is requested by user components by initializing a scrollerProperties component property to null. The property is then recognized by RIGS and set to the scrollerProperties object by the system on loading of the component to a CellFrame.

the scrollerProperties object contains three properties:

{
  cellFrameDataRef,
  scrollerPropertiesRef
}

Each of these is a reference object, with values found in propertyRef.current.

The cellFrameDataRef.current object contains two properties:

{
  itemID, // session cache itemID
  index // place in virtual list
}

The scrollerPropertiesRef.current object contains the following properties, which are identical to the properties set for the scroller (they are passed through):

orientation, gap, padding, cellHeight, cellWidth, cellMinHeight, cellMinWidth, layout, cache, cacheMax, startingIndex

It also contains the crosscount property, which is calculated interrnally, and the following properties, which may have been altered from the source values by the scroller:

runwayRowcount, listsize

Restoring scroll positions coming out of cache

This is only of concern if your cell components support scrolling.

RIGS loads components into a cache (React portals), and into Cradle cells from there. Moreover RIGS moves components from one side of the (hidden) axis to the other through the cache during scrolling. Plus caching can be extended (by RIGS property settings) beyond the Cradle. So going into and out of cache happens a lot for components. While in cache, the component elements have their scrollTop, scrollLeft, width, and height values set to 0 by browsers. width and height values are restored by browsers when moved back into the visible DOM area, but scroll positions have to be manually restored.

Here is one way of restoring scroll positions. Basically, save scroll positions on an ongoing basis, detect going into cache when width and height values are both zero, and detect coming out of cache when width and height are no longer zero. When coming out of cache, restore the saved scroll positions.

This code is Typescript, in a function component.

    // ------------------------[ handle scroll position recovery ]---------------------

    // define required data repo
    const scrollerElementRef = useRef<any>(null),
        scrollPositionsRef = useRef({scrollTop:0, scrollLeft:0}),
        wasCachedRef = useRef(false)

    // define the scroll event handler - save scroll positions
    const scrollerEventHandler = (event:React.UIEvent<HTMLElement>) => {

        const scrollerElement = event.currentTarget

        // save scroll positions if the scroller element is not cached
        if (!(!scrollerElement.offsetHeight && !scrollerElement.offsetWidth)) {

            const scrollPositions = scrollPositionsRef.current

            scrollPositions.scrollTop = scrollerElement.scrollTop
            scrollPositions.scrollLeft = scrollerElement.scrollLeft

        }

    }

    // register the scroll event handler
    useEffect( () => {

        const scrollerElement = scrollerElementRef.current

        scrollerElement.addEventListener('scroll', scrollerEventHandler)

        // unmount
        return () => {
            scrollerElement.removeEventListener('scroll', scrollerEventHandler)
        }

    },[])

    // define the cache sentinel - restore scroll positions
    const cacheSentinel = () => {
        const scrollerElement = scrollerElementRef.current

        if (!scrollerElement) return // first iteration

        const isCached = (!scrollerElement.offsetWidth && !scrollerElement.offsetHeight) // zero values == cached

        if (isCached != wasCachedRef.current) { // there's been a change

            wasCachedRef.current = isCached

            if (!isCached) { // restore scroll positions

                const {scrollTop, scrollLeft} = scrollPositionsRef.current

                scrollerElement.scrollTop = scrollTop
                scrollerElement.scrollLeft = scrollLeft

            }

        }

    }

    // run the cache sentinel on every iteration
    cacheSentinel()

    // register the scroller element through the ref attribute
    return <div ref = {scrollerElementRef} style = {scrollerstyles}>
        {scrollercontent}
    </div>

Licence

MIT © 2020-2022 Henrik Bechmann

Keywords

FAQs

Package last updated on 04 Dec 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc