Library / SDK
Stanko/react-plx avatar
Stanko/react-plx

Plx gives React a data driven parallax effect and a shared scroll manager

React parallax component, powerful and lightweight

687 stars49 forksTypeScriptMIT

At a glance

What is it?
Stanko/react-plx is an MIT licensed React component for on scroll effects that describes its animation as an array of segments rather than as imperative style code, and it replaces scroll listeners with a single polling interval shared by every instance through a scroll manager singleton. The README is unusually candid about the performance rules you are expected to follow and about a known iOS bug in viewport detection.
Who is it for?
Adopt react-plx if you are building a marketing or landing page in React where elements need to react to scroll position, and you want the effect described as data rather than as a pile of style mutations in a hook.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 133 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The effect is described as data, not as style code

Plx is a React component for creating on scroll effects, aka parallax, and its defining idea is that the animation is described by a plain array rather than written as imperative style updates. The quick start shows the shape of that data:

javascript
import React, { Component } from "react";
import Plx from "react-plx";

// An array of parallax effects to be applied - see below for detail
const parallaxData = [
  {
    start: 0,
    end: 500,
    properties: [
      {
        startValue: 1,
        endValue: 2,
        property: "scale",
      },
    ],
  },
];

Each item is a segment with a start and an end scroll position, and a list of properties, each with a startValue, an endValue and the CSS property to change. The component is then used as a wrapper around whatever content you want to move:

javascript
class Example extends Component {
  render() {
    return (
      <Plx className="MyAwesomeParallax" parallaxData={parallaxData}>
        /* Your content */
      </Plx>
    );
  }
}

The practical value of that shape is that an effect becomes reviewable data. It can be generated, stored, composed, and passed around as a prop, which is not something you can easily do with a hook that mutates style attributes.

A shared manager polls every 16ms instead of listening to scroll

The mechanism behind the component is the part that makes it different from a hand written effect. The README says that as listening to scroll event is not performant, this component uses a different approach. An interval is set, every 16ms to get 60fps, to check if the scroll position is changed, and if it is, it broadcasts a custom event. All of the Plx components are sharing the scroll manager singleton. The interval is set when the first component is created and cleared when the last one is unmounted, so a page with no Plx components has no timer running at all. The interval time can be changed through the props, but it is shared across the components, which is the trade-off: one timer for the whole page, one rate for everybody. That design also means the component is not independent of its siblings, which matters if you expected to control one instance in isolation.

Viewport detection leans on getBoundingClientRect, and iOS has a bug

Elements outside of the viewport are not animated, and the README says this is done by using getBoundingClientRect. That is the source of a documented limitation, because there is a known bug in iOS with getBoundingClientRect and position fixed. The component works around it with a prop. If set to true, element will be animated even when it is not in the viewport, and the README describes this as helpful with fixed elements in iOS due to the known bug. The iOS radar entry is linked from the README, so the failure is documented upstream rather than folklore. Two things follow for an adopter. First, if you have a sticky or fixed header with a parallax effect on iOS, test it there rather than assuming the desktop behaviour. Second, the workaround is a blunt instrument: animateWhenNotInViewport turns the optimisation off for that element, so an element that is off screen still gets updated on every scroll tick, which is exactly the cost the viewport check was there to avoid.

parallaxData has to be sorted, and start accepts several forms

The props section is where the constraints live. The start property is required and marks the scroll position where the effect should start, and it accepts a number in pixels, a string in px, vh or percent with the percentage calculated out of max page scroll, a CSS selector resolved with document.querySelector, the literal string self to use the component's own element, or an HTMLElement. The end property marks where the effect ends and it has higher priority than duration. For an element, a selector or self, the animation starts when that element enters the viewport, and the startOffset prop can offset the start position. The example the README gives is short:

js
  start: 100; // starts when scroll hits 100px
  start: "self"; // starts when plx's element enters the viewport
  start: ".start-element"; // starts when .start-element enters the viewport

And then the line that will bite you if you skip it, in capitals: parallaxData should be sorted by start value. The component resolves the current segment by position in that ordered list, so an unsorted array does not throw, it just animates the wrong segment.

Freeze, disable and animation state callbacks cover the awkward cases

The remaining props handle the situations that come up once a page is live. The disabled boolean, when true, disables animation completely. The freeze prop, default false, stops the animation at its current state when the condition is met, which is the way to hold an element in place during a transition. Two callbacks report state changes: onPlxStart is called each time the animation state changes to active, and onPlxEnd each time it changes from active to another state, both of which refer to the animation state CSS classes section of the documentation. The rest of the props are ordinary pass-throughs. className is applied along with the Plx class name, style is a CSS style object with the note that properties used in parallax will be overridden by the component, tagName defaults to div and can be an HTML or SVG element, and any other prop is passed to the component, which the README says is useful for aria-* props. That last point is worth internalising, because a parallax wrapper that swallows your accessibility attributes is a common bug.

The performance rules are in the README, which is rare

The README spends a paragraph on what it calls common don't dos when making a parallax page, and it is the most useful thing in the document. Avoid background-size: cover. Don't animate massive images or dramatically resize them. Avoid animating 100 things at once. And only use properties that are cheap for browsers to animate, namely opacity and transform, with translate, rotate, skew and scale listed. The component is written as an ES module, so it works with webpack and other module bundlers, and the README says it was tested with create-react-app and the author's own boilerplate. It also links a blog post called parallax done right, which the author says is where the initial inspiration came from, and another post explaining how the component works. The author's own advice is to break the rules if you need to, but to test for performance when you do.

Two runtime dependencies and a dual ESM and CommonJS build

The package manifest is small and tells you what changed in version 2. The component is published as react-plx, with the current version at 2.1.2, and the entry points are declared twice, with main pointing at the CommonJS build in dist/cjs/index.js and module at the ES module build in dist/esm/index.js, plus an exports map that routes import and require to the same two files. The build scripts run tsc twice, once per tsconfig, and each pass writes a small package.json into the output directory to mark the module type, which is the standard trick for dual packages. The docs and the demo are built with esbuild, and prepublishOnly runs the full build so a published package is always built from source. The runtime dependencies are two: bezier-easing, which is the package the README notes was added in version 2, and window-scroll-manager, the shared scroll listener the singleton is built on. React and react-dom are peer dependencies pinned to 18.2 and above, and the published files are the dist directory and the changelog.

Version 2 changed the unit contract, and the branch is still v2

The migration note is short and worth reading before you upgrade. In the v2.0.0 breaking changes, px units are not assumed and need to be provided manually, which is a real behaviour change for anyone upgrading from a v1 component that assumed pixels, and the tagName prop was removed, then returned in v2.1.0. The README also says that version 2 brings even more performance improvements and TypeScript support, and the source tree is TypeScript with the type declarations emitted into the ESM build. The default branch of the repository is v2, and the changelog lives in CHANGELOG.md at the root alongside a docs directory that holds the demo. There are no GitHub releases, so npm is the release channel, and the last push was on 2026-06-14, so read the changelog to see what is in the version you install rather than assuming the branch tip is what your lock file has.

Editorial conclusion

Adopt react-plx if you are building a marketing or landing page in React where elements need to react to scroll position, and you want the effect described as data rather than as a pile of style mutations in a hook. Do not adopt it if your page animates a lot of elements at once or animates properties that browsers handle expensively, because the README tells you to avoid exactly that, and do not assume the viewport detection is reliable on iOS with fixed positioning without reading the known bug note. Before you ship, read the animation state CSS classes, keep parallaxData sorted by start value, and check the version you install, since the component is still on the v2 branch with the px unit change arriving in 2.0.0.

Frequently asked questions

How do I install and use react-plx?

Install it with npm install --save react-plx, import Plx from react-plx, and pass a parallaxData array to the component. Each item in that array has a start and an end scroll position and a list of properties with startValue, endValue and the property name.

Does parallaxData have to be sorted in react-plx?

Yes. The README states in capitals that parallaxData should be sorted by start value, and gives examples of start as a pixel number, the string self, and a CSS selector, each of which begins the animation at a different kind of trigger.

Why does my parallax effect not animate on iOS?

The README points to a known iOS bug with getBoundingClientRect and position fixed, which the component uses to detect whether an element is in the viewport. The workaround is to set the animateWhenNotInViewport prop to true, which animates the element even when it is off screen.

What are the dependencies of react-plx?

Two runtime dependencies, bezier-easing and window-scroll-manager, with react and react-dom as peer dependencies at 18.2 and above. The version 2 line added the small bezier-easing package after version 1 had no dependencies beyond react, react-dom and prop-types.

How does react-plx handle scroll events efficiently?

Instead of listening to scroll events, the component sets an interval, every 16ms to get 60fps, that checks whether the scroll position changed and broadcasts a custom event when it has. All Plx components share the scroll manager singleton, and the interval starts with the first component and is cleared when the last one unmounts.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. Stanko/react-plx on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/stanko-react-plx.svg)](https://hysenlabs.com/projects/stanko-react-plx)

Was this useful?