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

lenticular-fx

v1.0.0

Published

Animated lenticular print effect for React

Readme

lenticular-fx

Animated lenticular print effect for React. Two children, one wrapper, alternating vertical slices on mousemove. Pure CSS clip-path, RAF-driven, zero runtime dependencies.

bun add lenticular-fx
# or: npm install lenticular-fx / pnpm add lenticular-fx

Quick start

import { Lenticular } from 'lenticular-fx'

export function Card() {
  return (
    <Lenticular
      front={<img src="day.jpg" alt="Day" />}
      back={<img src="night.jpg" alt="Night" />}
      slices={12}
    />
  )
}

That is the whole API. Move the cursor across the wrapper to drag the offset — the back face restripes into view through interleaved vertical columns, exactly like a lenticular print.

Props

| Prop | Type | Default | Description | | ---------------- | -------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | front | ReactNode | — | Content shown when the cursor is on the left edge (offset = 0). Required. | | back | ReactNode | — | Content shown when the cursor is on the right edge (offset = 1). Required. | | slices | number | 10 | Number of interleaved vertical columns. Clamped to [2, 40]. | | ease | number | 0.12 | Lerp factor applied per animation frame. 1 is instant, 0.01 is molasses. Clamped to (0, 1]. | | defaultSide | 'front' \| 'back' | 'front' | Which face shows at rest, before any interaction and after the cursor leaves. | | trackWindow | boolean | false | Track the cursor across the whole viewport instead of just the wrapper. Cursor X is still normalized to the wrapper. | | hitMargin | number | 24 | Extra hit-area, in pixels, added on every side of the wrapper. Lets the cursor overshoot the visible edge and still hold the back fully revealed. Set 0 to disable. | | tilt | number | 0 | 3D perspective tilt in degrees, driven by the same offset. 0 disables. Typical 6–20: the card rotates around its Y axis to follow the cursor while slicing happens. | | perspective | number | 900 | CSS perspective in pixels used for the tilt. Lower = stronger 3D effect. Only applied when tilt > 0. | | gyroscope | boolean | false | Use deviceorientation gamma (-45° → +45°) instead of mouse, on supported devices. Falls back to mouse if DeviceOrientationEvent is missing. | | paused | boolean | false | Freeze the animation loop on the current frame. Cancels the RAF tick until set back to false. | | onOffsetChange | (offset: number) => void | — | Called each frame with the current lerped offset (01). Useful for live debugging or driving external UI. Do not call setState in this callback — use a ref. | | className | string | — | Extra class names applied to the wrapper <div>. | | style | CSSProperties | — | Extra inline styles applied to the wrapper <div>. |

All standard HTMLDivElement attributes are forwarded to the wrapper. forwardRef returns the wrapper <div>.

How it works

A physical lenticular print is alternating columns of two images placed behind a ribbed lens that redirects light by viewing angle — one eye sees columns from image A, the other from image B, and as you tilt the print the visible strips swap. We simulate that with two stacked overlay layers above an invisible layout base, each cut to a set of vertical rectangles by clip-path: path().

On every mousemove the cursor X is normalized to [0, 1] relative to the wrapper. A requestAnimationFrame loop lerps a currentOffset ref toward that target — React never re-renders inside the animation loop. Each frame we recompute two clip-path strings (one per layer) where each slice is a rectangle: the front layer's rectangle spans the left (1 − offset) of the slice, the back layer's the remaining right portion. When the stripes fill the slice or shrink below half a pixel we collapse to a single rectangle or hide entirely — that kills the sub-pixel anti-aliasing seams you would otherwise see at the extremes.

A hitMargin prop (default 24 px) pads the wrapper's invisible interaction zone so the cursor doesn't have to land on the exact visible edge to fully reveal the back. A single IntersectionObserver cancels the RAF when the element scrolls out of view; a RAF-debounced ResizeObserver keeps the slice width in sync with layout changes. prefers-reduced-motion: reduce short-circuits the lerp to a snap. Touch and deviceorientation (gamma → 0–1) are wired through the same target-offset pipeline as the mouse.

Examples

// Card — product to specs
<Lenticular slices={10} ease={0.12}
  front={<ProductFront />}
  back={<ProductSpecs />}
/>

// Avatar — fine grain, slow ease
<Lenticular slices={20} ease={0.08}
  front={<DayFace />}
  back={<NightFace />}
/>

// CTA Button — small element, chunky slices
<Lenticular slices={6} ease={0.18}
  front={<button>Get started →</button>}
  back={<button>Free for 14 days →</button>}
/>

// 3D perspective tilt — card follows cursor while slicing
<Lenticular slices={18} ease={0.12} tilt={14} hitMargin={40}
  front={<DayPostcard />}
  back={<NightPostcard />}
/>

// Track the cursor across the whole page
<Lenticular trackWindow slices={20} front={<Day />} back={<Night />} />

// Snap, no lerp
<Lenticular slices={2} ease={1} front={<Before />} back={<After />} />

// Drive external UI from the offset
const barRef = useRef<HTMLDivElement>(null)
<Lenticular
  onOffsetChange={(o) => { barRef.current!.style.width = `${o * 100}%` }}
  front={...}
  back={...}
/>

Browser support

The effect relies on clip-path: path() with multiple sub-paths and the standard ResizeObserver / IntersectionObserver APIs.

  • Chrome / Edge 88+
  • Safari 14.1+
  • Firefox 63+

@property --lenticular-offset is registered for compatibility with future styling hooks but is not required — clip paths are computed in JS each frame.

Credits

Built by Subhadeep Roy with Claude — pair-programmed end-to-end, from the slice maths and the clip-path engine to the demo gallery, the 3D-tilt example, and the SEO/AEO wiring.

License

MIT © Subhadeep Roy