npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lp-labs/react-smooth-view

v0.1.0

Published

Lightweight React animations with viewport triggers and zero runtime dependencies beyond React.

Readme

@lp-labs/react-smooth-view

A small React animation library inspired by react-smooth, with viewport triggers built in. No runtime dependencies other than the application's React peer dependency. No CSS bundle, animation framework, observer polyfill, or wrapper elements required.

Supports React 18+; ships tree-shakeable ESM, CommonJS, source maps, and TypeScript declarations.

Installation

npm install @lp-labs/react-smooth-view

React 18 or later must be installed in your application. No additional runtime dependencies or stylesheet imports are required.

Animate On Screen

import Animate from '@lp-labs/react-smooth-view';

export function Section() {
  return (
    <Animate
      from={{ opacity: 0, transform: 'translateY(32px)' }}
      to={{ opacity: 1, transform: 'translateY(0px)' }}
      duration={600}
      easing="ease-out"
      viewport={{ amount: 0.2, once: true }}
    >
      <section>Section content</section>
    </Animate>
  );
}

viewport starts playback when any part of the target intersects the viewport. Use viewport={{ amount: 'all' }} to wait until the entire element is visible. An element taller than the viewport cannot become fully visible: use 'some' or a numeric ratio for large sections.

once: true is the default. Set once: false to reset to the initial style when the element leaves the visibility threshold and replay when it reenters. root accepts a scroll container; rootMargin expands or contracts the observer boundary, for example '0px 0px -80px 0px'.

The observed target is the animated element, including its transform. To observe an untransformed section instead, combine the hooks below. IntersectionObserver measures geometric intersection, not whether another element visually covers the target.

Animate API

| Prop | Default | Behavior | | --- | --- | --- | | from, to | {} | Initial and final style objects, or scalar values with attributeName | | attributeName | none | Style property for scalar values, e.g. opacity | | duration | 1000 | Duration in milliseconds; also the default duration for each keyframe segment | | begin | 0 | Delay in milliseconds, measured after playback becomes eligible | | easing | 'ease' | CSS timing keyword, cubic-bezier(...), steps(n, start/end), or a progress function | | steps | [] | Keyframes; replaces to when nonempty | | isActive | true | When false, cancel playback and immediately show final styles | | canBegin | true | When false, cancel playback and reset to initial styles | | viewport | false | true or { once, amount, root, rootMargin } | | respectReducedMotion | true | Respect prefers-reduced-motion; show final styles without animated frames | | onAnimationStart | none | Called when the delay ends and playback starts | | onAnimationEnd | none | Called once after successful completion, not cancellation | | children | required | One ref-capable React element, or a render function |

Existing child styles and refs are preserved; animated properties take precedence. No wrapper is inserted. Custom components must forward their ref to a DOM element and apply the supplied style. Fragments are not animation targets.

Changing styles, easing, timing, steps, or activation gates restarts playback from the configured initial style. Structurally equivalent inline style objects and callback changes do not restart playback. This implementation deliberately restarts instead of automatically retargeting from a partially animated value.

Keyframes

<Animate
  viewport
  steps={[
    { style: { opacity: 0, transform: 'translateX(0px)' } },
    { style: { opacity: 1 }, duration: 300, easing: 'ease-out' },
    {
      style: { transform: 'translateX(120px)' },
      duration: 600,
      onAnimationEnd: () => console.log('Movement completed'),
    },
  ]}
>
  <div>Animated item</div>
</Animate>

The first step defines the initial style and has no duration or completion event. Each later step merges its style into the preceding step, then animates for its own duration. Set explicit initial values for properties you want interpolated. Zero-duration segments apply immediately; negative durations and delays are clamped to zero.

Render Props

<Animate from={{ opacity: 0 }} to={{ opacity: 1 }} viewport>
  {(style, { ref, replay, cancel, isAnimating }) => (
    <article ref={ref} style={style}>
      <button onClick={replay}>Replay</button>
      <button onClick={cancel} disabled={!isAnimating}>Stop</button>
    </article>
  )}
</Animate>

The first argument is the interpolated style, as in react-smooth. The optional second argument exposes ref, inView, style, isAnimating, replay(), and cancel(). Attach ref to the target when using viewport or a forwarded ref. cancel() freezes the current style without firing completion; replay() restarts with the configured delay and gates. isAnimating is false during the initial delay.

Hooks

import { useAnimate, useInView } from '@lp-labs/react-smooth-view';

function Reveal() {
  const { ref, inView } = useInView({ amount: 0.3, once: false });
  const { style, replay, cancel } = useAnimate({
    from: { opacity: 0, transform: 'translateY(24px)' },
    to: { opacity: 1, transform: 'translateY(0px)' },
    duration: 500,
    canBegin: inView,
  });

  return (
    <section ref={ref}>
      <div style={style}>Animated content</div>
      <button onClick={replay}>Replay</button>
      <button onClick={cancel}>Stop</button>
    </section>
  );
}

useAnimate(options) accepts animation options, not viewport or children. It returns { style, isAnimating, replay, cancel }. useInView(options) returns { ref, inView, entry } and additionally supports enabled: false. useReducedMotion() exposes the user's current motion preference.

Easing Helpers

import { configureBezier, configureSpring } from '@lp-labs/react-smooth-view';

const bezier = configureBezier(0.2, 0.8, 0.2, 1);
const spring = configureSpring({ stiff: 170, damping: 20, mass: 1 });

<Animate from={{ opacity: 0 }} to={{ opacity: 1 }} easing={bezier}>
  <div />
</Animate>;

Define custom easing functions outside the component, or retain their identity between renders. A spring is a duration-based analytical easing curve, not a velocity-preserving physics simulation: it may overshoot and is forced to the exact target at completion. duration determines total playback time. Supported string timings are linear, ease, ease-in, ease-out, ease-in-out, cubic-bezier(...), and steps(n, start/end/jump-start/jump-end). Unsupported easing strings throw a descriptive error.

Enter And Leave

import { AnimateGroup } from '@lp-labs/react-smooth-view';

<AnimateGroup
  component="ul"
  appear={{ from: { opacity: 0 }, to: { opacity: 1 }, duration: 250 }}
  enter={{ from: { opacity: 0 }, to: { opacity: 1 }, duration: 250 }}
  leave={{ from: { opacity: 1 }, to: { opacity: 0 }, duration: 200 }}
>
  {items.map((item) => <li key={item.id}>{item.label}</li>)}
</AnimateGroup>;

Children must have unique, explicit keys and accept styles/refs. Removed items remain mounted until leave completes. A key that reenters cancels its pending removal. Omitted transitions apply immediately. The default container is a Fragment; set component for a real container and pass className, style, or id. Supply from explicitly for exit transitions; this version does not capture computed DOM styles. Avoid viewport gating or canBegin: false on leave transitions, since an off-screen item would otherwise wait indefinitely for completion.

Scope And Performance

  • Uses native requestAnimationFrame and IntersectionObserver; no production dependencies are bundled. React is external and required from the host application.
  • Interpolates numbers, matching numeric CSS strings such as transforms and unit values, hex colors, and comma-separated rgb()/rgba() colors. CSS custom properties work too.
  • String units and structure must match. Named colors, auto, unrelated transform lists, gradients with incompatible structures, and other incompatible values switch only at the segment's end. No computed-style or unit-conversion engine is included.
  • Animates CSS styles, not arbitrary SVG DOM attributes. Render props can map numeric values onto SVG attributes themselves.
  • This is a similar API, not a guaranteed drop-in replacement. Legacy vendor-prefix helpers are omitted because current target browsers support unprefixed animation properties.
  • Prefer opacity and transform; layout properties such as height cost more browser work. Render-prop support uses React updates per frame, so this is not intended for thousands of simultaneous animated nodes.
  • SSR emits initial styles without accessing browser globals; observation and playback start after mount. Viewport content can remain hidden before hydration. Without IntersectionObserver, playback starts immediately after mounting.
  • Reduced motion skips the delay and animated frames, then invokes start, step-completion, and end callbacks. Unmounting cleans up animation frames, observers, and media-query listeners.
  • The complete minified ESM entry is approximately 3.6 KiB gzipped, excluding React. Tree shaking can reduce consumer bundles further; size may vary between releases.

Support

If this library is useful to you, consider supporting its maintenance with a donation via PayPal.

Author

Created by Libin Prasanth. Connect on LinkedIn for updates and discussion.