React-Preact scroll viewport 0.2.0: Preact Component that renders homogeneous children only when visible

Latest Release: 0.2.0
  • Moved Preact to be a peer dependency, allowing use with any version and avoiding duplicates.
Source code(tar.gz)
Source code(zip)

<ScrollViewport /> for Preact

NPM Travis

A compositional component that renders its children based on the current viewport.

Useful for those super important business applications where one must show all million rows.

JSFiddle Demo


Usage Example

Simply wrap a large collection of children in this component, and they will be rendered based on the viewport. You can define a default row height (defaultRowHeight) to use prior to dimensions being available, or a static row height (rowHeight) to avoid style recalculation entirely. If rowHeight is not provided, the height of the first row will be calculated and extrapolated.

// create 100,000 children:
let children = [];
for (let i=1; i<100000; i++) {
	children.push(<div class="row">{i}</div>);

// ...but only render what is in-viewport:
	<ScrollViewport rowHeight={22}>


Prop Type Description
rowHeight Number Static height of a row (prevents style recalc)
defaultRowHeight Number Initial height of a row prior to dimensions being available
overscan Number Number of extra rows to render above and below visible list. Defaults to 10. *
sync Boolean If true, forces synchronous rendering **

* Why overscan? Rendering normalized blocks of rows reduces the number of DOM interactions by grouping all row renders into a single atomic update.

** About synchronous rendering: It's best to try without sync enabled first. If the normal async rendering behavior is fine, leave sync turned off. If you see flickering, enabling sync will ensure every update gets out to the screen without dropping renders, but does so at the expense of actual framerate.

Without Overscan With Overscan

Simple Example

View this example on JSFiddle

import ScrollViewport from 'preact-scroll-viewport';

class Demo extends Component {
    // 30px tall rows
    rowHeight = 30;

    render() {
		// Generate 100,000 rows of data
		let rows = [];
		for (let x=1e5; x--; ) rows[x] = `Item #${x+1}`;

        return (
            <ScrollViewport class="list" rowHeight={this.rowHeight}>
				{ row => (
					<div class="row">{row}</div>
				)) }

render(Demo, document.body);




  • Add support for variable row heights
    Add support for variable row heights

    Nov 17, 2016

    By passing an array of heights to rowHeight.

  • Do keys make sense?
    Do keys make sense?

    Jul 5, 2017

    Let's say you have unique IDs (such as tweet ids), would it make sense to pass them (regarding performance)? Like so:

    <ScrollViewport class="list" rowHeight={this.rowHeight}>
        { row => (
    	<div class="row" key={}>{row.entry}</div>
          )) }
  • Possible perf improvements?
    Possible perf improvements?

    Nov 25, 2017

    I was wondering why create the array of elements in the first place. Since we are already only rendering the children in the Viewport, why construct all the elements and stuff them in an array in the first place? This way we could save the computing power of reconstructing a huge list in the first place.

    Maybe we can pass down the data array directly to ScrollViewport and only construct those elements that does actually end up in the Viewport and get rendered?

  • filtering

    May 12, 2018

    I would like to filter the data based on a search string. How do we reset the list of children to the new filtered array?

  • Jumping with dynamically sized elements
    Jumping with dynamically sized elements

    Jun 18, 2018


    I'm using this plugin for a chat window, and I've got a list of elements (messages) that have all different sizes based on their content. When I scroll the list, the scroll position sometimes jumps around a few pixels. I've produced a repro here:

    Any idea on how to fix this?

  • Update dependencies to enable Greenkeeper 🌴
    Update dependencies to enable Greenkeeper 🌴

    May 9, 2017

    Let’s get started with automated dependency management for preact-scroll-viewport :muscle:

    This pull request updates all your dependencies to their latest version. Having them all up to date really is the best starting point. I will look out for further dependency updates and make sure to handle them in isolation and in real-time, as soon as you merge this pull request.

    I won’t start sending you further updates, unless you have merged this very pull request.


    💥 This branch failed. How to proceed

    I suggest you find out what dependency update is causing the problem. Adapt your code so things are working nicely together again. next-update is a really handy tool to help you with this.

    Push the changes to this branch and merge it.

    🏷 How to check the status of this repository

    There is a badge added to your README, indicating the status of this repository.

    This is how your badge looks like :point_right: Greenkeeper badge

    🙈 How to ignore certain dependencies

    In case you can not, or do not want to update a certain dependency right now, you can of course just change the package.json file back to your liking.

    Add a greenkeeper.ignore field to your package.json, containing a list of dependencies you don’t want to update right now.

    // package.json
      "greenkeeper": {
        "ignore": [
    👩‍💻 How to update this pull request
      # change into your repository’s directory
      git fetch
      git checkout greenkeeper/initial
      npm install-test
      # adapt your code, so it’s working again
      git commit -m 'chore: adapt code to updated dependencies'
      git push origin greenkeeper/initial
    ✨ How the updates will look like

    As soon as you merge this pull request I’ll create a branch for every dependency update, with the new version applied. The branch creation should trigger your testing services to check the new version. Using the results of these tests I’ll try to open meaningful and helpful pull requests and issues, so your dependencies remain working and up-to-date.

    -  "underscore": "^1.6.0"
    +  "underscore": "^1.7.0"

    In the above example you can see an in-range update. 1.7.0 is included in the old ^1.6.0 range, because of the caret ^ character. When the test services report success I’ll delete the branch again, because no action needs to be taken – everything is fine. When there is a failure however, I’ll create an issue so you know about the problem immediately.

    This way every single version update of your dependencies will either continue to work with your project, or you’ll get to know of potential problems immediately.

    -  "lodash": "^3.0.0"
    +  "lodash": "^4.0.0"

    In this example the new version 4.0.0 is not included in the old ^3.0.0 range. For version updates like these – let’s call them “out of range” updates – you’ll receive a pull request.

    Now you no longer need to check for exciting new versions by hand – I’ll just let you know automatically. And the pull request will not only serve as a reminder to update. In case it passes your decent test suite that’s a strong reason to merge right away :shipit:

    💁‍♂️ Not sure how things are going to work exactly?

    There is a collection of frequently asked questions and of course you may always ask my humans.

    Good luck with your project and see you soon :sparkles:

    Your Greenkeeper Bot :palm_tree:

  • Avoid paint by using transform?
    Avoid paint by using transform?

    Nov 20, 2017

  • Fix scroll events inside wrappers of fixed height
    Fix scroll events inside wrappers of fixed height

    Feb 4, 2017

    Hey, thanks for your work, loving preact. :)

    Noticed this component doesn't work for a list I have in a dialog box. I can reproduce the issue with this markup:

    <div style="max-height: 400px; border: 1px dashed; padding: 30px; overflow: scroll;">
    	<div style="height: 10000px;"></div>
    	window.addEventListener('scroll', () => console.log('scrolled'), false)

    The scroll event doesn't fire on window. Enabling the event capture phase fixes it.

  • Scrollable container as property via querySelector
    Scrollable container as property via querySelector

    Dec 23, 2016

    Hello, I had problems using this component inside a panel that had overflow set to scroll or auto. I made it so that I could pass in the panel's selector as "scroll" property to then bind the scroll event listener to. Using window smells a bit iffy but works for my use case. Happy to discuss?

  • moved resized and scrolled into constructor
    moved resized and scrolled into constructor

    Jul 27, 2018

    moved resized and scrolled into constructor so that the ScrollViewport-component is usable without class property transformation. It should not increase the final size since the transformation creates the same result