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

@rippleflow/water-distortion

v0.1.4

Published

A responsive React SVG-filter water refraction layer for portfolio-style interfaces.

Readme

RippleFlow Water Distortion

A React + Vite + TypeScript package for cursor-driven water on portfolio-style interfaces. RippleFlow now uses a low-resolution heightfield simulation: pointer movement stamps local trough/ridge wakes, the wave equation propagates them outward, and a WebGL material turns the heightfield into refraction, specular highlights, crests, and experimental ripple-driven caustic light.

Foreground content stays sharp and interactive. Background content can be sampled directly by WebGL in texture / canvas mode for the strongest refraction, or rendered as live DOM in dom mode.

Install

npm install @rippleflow/water-distortion

React and React DOM are peer dependencies:

npm install react react-dom

Usage

import { WaterDistortion } from "@rippleflow/water-distortion";

export function Hero() {
  return (
    <WaterDistortion
      mode="texture"
      wakeStrength={0.54}
      interactionRadius={25}
      troughStrength={0.92}
      ridgeStrength={0.46}
      ridgeOffset={10}
      wakeLength={84}
      velocityScale={1}
      velocityClamp={1.35}
      cursorSmoothing={0.2}
      segmentSpacing={18}
      damping={0.98}
      waveSpeed={0.4}
      normalScale={20}
      refractionStrength={0.7}
      specularIntensity={1}
      crestIntensity={0.2}
      causticIntensity={0}
      simulationResolution={192}
      texture="/work-collage.jpg"
      underlay={
        <img
          src="/work-collage.jpg"
          alt=""
          style={{ width: "100%", height: "100%", objectFit: "cover" }}
        />
      }
      style={{ minHeight: 620 }}
    >
      <section>
        <h1>Portfolio title stays crisp</h1>
        <a href="/work">View work</a>
      </section>
    </WaterDistortion>
  );
}

For true shader refraction, provide a texture source:

<WaterDistortion
  mode="texture"
  texture="/hero-background.jpg"
  wakeStrength={0.8}
  underlay={<img src="/hero-background.jpg" alt="" />}
>
  <HeroCopy />
</WaterDistortion>

WaterLayer is exported as an alias of WaterDistortion.

React API

| Prop | Type | Default | Notes | | --- | --- | --- | --- | | underlay | ReactNode | undefined | Live DOM/background content behind the water. Recommended in dom mode and useful as a fallback in texture modes. | | children | ReactNode | undefined | Foreground content rendered above the water. | | foreground | ReactNode | undefined | Explicit foreground prop; takes precedence over children. | | mode | "dom" \| "texture" \| "canvas" | "texture" | Chooses live-DOM overlay mode or true WebGL background sampling. | | texture | TexImageSource \| string | undefined | Image/canvas/video source sampled by WebGL in texture or canvas mode. URL strings are loaded with crossOrigin="anonymous". | | interactionMode | "move" \| "click" \| "drag" \| "event" | "move" | Chooses pointer-movement wakes, one ripple per pointer down, press ripples plus held-drag wakes, or programmatic ripples only. | | runInBackground | boolean | false | Continues stepping while hidden or unfocused and catches up elapsed time when the browser throttles background callbacks. | | pauseKey | string | undefined | Toggles simulation pausing when this KeyboardEvent.code is pressed, such as Space or KeyP. Keyboard input in interactive controls is ignored. | | wakeStrength | number | 0.54 | Overall pointer wake strength. Slow drags stay subtle; fast movement is clamped before it reaches the shader. | | rippleStrength | number | undefined | Compatibility alias for wakeStrength. | | strength | number | undefined | Legacy alias mapped to wakeStrength as strength / 48. | | interactionRadius | number | 25 | Width in CSS pixels of the moving virtual finger/body that parts the water. | | troughStrength | number | 0.92 | Strength of the narrow negative trough along the pointer path. | | ridgeStrength | number | 0.46 | Strength of the paired side ridges beside the trough. | | ridgeOffset | number | 10 | Side-ridge offset from the path centerline in CSS pixels. | | wakeLength | number | 84 | Base trailing wake length in CSS pixels. Fast movement scales it longer. | | velocityScale | number | 1 | Multiplier applied before pointer speed is clamped. | | velocityClamp | number | 1.35 | Pointer speed in CSS pixels per millisecond that maps to full wake strength. | | cursorSmoothing | number | 0.2 | Low-pass smoothing for the virtual finger position and speed. | | segmentSpacing | number | 18 | Maximum CSS-pixel spacing before long pointer moves are subdivided into connected wake segments. | | damping | number | 0.98 | Per-step velocity damping. Higher values let waves travel longer. | | dissipation | number | undefined | Legacy alias for damping. | | waveSpeed | number | 0.4 | Stiffness of the damped wave equation. | | normalScale | number | 20 | Height-gradient scale used to compute visible water normals. | | refractionStrength | number | 0.7 | Amount of normal-driven texture displacement in texture/canvas modes. | | specularIntensity | number | 1 | Strength of fixed-light specular highlights. | | crestIntensity | number | 0.2 | Visibility of height-gradient crests and troughs. | | causticIntensity | number | 0 | Experimental ripple-driven caustic light that fades when the simulated surface is calm. | | simulationResolution | number | 192 | Longest side of the simulation texture. Clamped to 64...384. | | idleTimeout | number | 900 | Minimum active time after input before the renderer begins checking whether ripples have faded enough to sleep. | | className | string | undefined | Applied to the container. | | style | CSSProperties | undefined | Merged onto the container. The component enforces relative positioning, hidden overflow, and isolation. | | reducedMotion | "respect" \| "disable" \| "ignore" | "respect" | respect disables wave input when prefers-reduced-motion: reduce matches. |

Programmatic ripples use normalized container coordinates through the component ref:

import { useEffect, useRef } from "react";
import {
  WaterDistortion,
  type WaterDistortionHandle
} from "@rippleflow/water-distortion";

function EventDrivenWater() {
  const waterRef = useRef<WaterDistortionHandle>(null);

  useEffect(() => {
    waterRef.current?.triggerRipple({ x: 0.5, y: 0.5 });
  }, []);

  return <WaterDistortion ref={waterRef} interactionMode="event" />;
}

The older blur, maxTrailPoints, maxStamps, and trailSpacing props remain accepted for source compatibility, but the heightfield renderer does not use the old trail-stamp engine.

Modes

dom

Use dom when the background is arbitrary live React/DOM content. The DOM underlay is rendered normally, foreground content stays sharp, and the WebGL canvas draws transparent water highlights derived from the heightfield.

Browsers do not provide an efficient, general API for sampling arbitrary live DOM into WebGL every frame. RippleFlow therefore does not pretend to perform perfect DOM refraction. The visible water material comes from heightfield normals, specular, crest, and caustic lighting, so ripples remain readable even when the DOM itself is not displaced.

texture / canvas

Use texture or canvas when you can provide an image, video, canvas, or URL that WebGL can sample. This path gives the best visual quality because the shader offsets the sampled background with normal.xy from the water heightfield.

The canvas remains transparent when no texture is supplied, while a URL is loading, or if loading or WebGL upload fails, allowing the underlay to remain visible. A successful URL load automatically requests a fresh render.

How It Works

  • A WebGL renderer maintains two ping-pong floating-point simulation textures.
  • Each texel stores height and velocity.
  • Every fixed step samples neighboring heights, accelerates velocity toward the neighbor average, applies damping, and integrates height.
  • Pointer input uses pointermove plus getCoalescedEvents() where available, then low-pass filters samples into a virtual cursor.
  • Pointer movement is stamped as continuous SDF/capsule wake segments, not repeated timed circles.
  • Each segment uses a directional profile: center trough, paired side ridges, a trailing wake mask, and subtle behind-only wavelets.
  • The material pass computes normals from height gradients, then combines refraction, specular highlights, crest/trough visibility, and projected caustic light that fades with ripple activity.
  • The animation loop runs while input is active, then sleeps after ripples measure as visually quiet.

Performance Notes

  • The simulation resolution defaults to 192, while the visible canvas still matches the container size.
  • Keep simulationResolution around 128...256 for most portfolio hero sections.
  • The renderer uses refs and an imperative animation loop; React state is not updated per frame.
  • ResizeObserver keeps the canvas responsive.
  • IntersectionObserver and document.visibilityState pause offscreen or hidden work when available.
  • toDataURL() is not used in the primary render path.
  • Dynamic canvas/video texture sources may be uploaded each rendered frame in canvas/texture mode.
  • prefers-reduced-motion is respected by default.

Browser Notes

The primary path uses WebGL2 with floating-point render targets. A WebGL1 path is attempted when float textures and float color buffers are available. Current Chrome, Safari, and Firefox support the WebGL2 path on modern desktop systems; older browsers or restricted GPU environments may fall back to showing only the underlay and foreground.

Useful APIs:

  • WebGL2: https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext
  • PointerEvent.getCoalescedEvents(): https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/getCoalescedEvents
  • ResizeObserver: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
  • IntersectionObserver: https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver

Demo

npm install
npm run dev

Open the local Vite URL and move across the hero. The /playground controls adjust wake shape, smoothing, segment spacing, damping, wave speed, normal scale, refraction, specular, crest/caustic intensity, and simulation resolution.

Quality

npm test
npm run typecheck
npm run build