Skip to content

About

A tiny, reversible WebGL iridescent sheen for DOM elements.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@softcopy/iridescence

CI

Pre-release GitHub test build. The API and visuals are still being exercised against real interfaces, and the package has intentionally not been published to npm yet.

A small, dependency-free library that gives existing DOM elements a moving iridescent sheen. It leaves the target's markup, inline styles, event handlers, focus behavior, and form semantics alone.

The effect is rendered in a shared, non-interactive WebGL overlay layer. Its rounded edge uses the portfolio bubble material's Fresnel, capped rim, specular, and anisotropic highlight equations on an analytic rounded rectangle. One pointer listener, one GPU context, and one animation loop serve every attached element in a document. The overlay follows scrolling, resizing, clipping, and the target's computed corner radii, and is removed completely on teardown. Native pointer, touch, Space, and Enter presses briefly suppress the hard white rim while preserving the iridescent edge, then recover with a short material release rather than mutating or restyling the target. Color phase is normalized to the viewport and damped independently from light position, so small controls do not cycle rapidly through the spectrum when the pointer crosses them. Pointer position can track immediately or trail with a controlled material delay using mouseLag.

Test from GitHub

npm install github:kimestelle/iridescence

Or clone the repository and run the full local checks:

git clone https://github.com/kimestelle/iridescence.git
cd iridescence
npm ci
npm test

Use

import { attachIridescence } from "@softcopy/iridescence";

const sheen = attachIridescence(document.querySelector("button")!, {
  movement: "pointer",
  roughness: 0.24,
  intensity: 0.72,
  mouseLag: 0.18,
  spectrum: "film",
});

sheen.update({ roughness: 0.5, intensity: 0.45 });
sheen.pause();
sheen.play();
sheen.destroy();

For a selector, NodeList, array, or other iterable, use the group helper:

import { iridescence } from "@softcopy/iridescence";

const cards = iridescence(".material-card", {
  movement: "ambient",
  speed: 0.18,
  spectrum: ["#8b72ff", "#4dcde2", "#78dc9a", "#e4cf69", "#d884b4"],
});

cards.update({ intensity: 0.5 });
cards.destroy();

Controls

Option Default Meaning
movement "pointer" Shared mouse/pointer light or autonomous ambient drift
roughness 0.18 0…1; broadens the light and reduces spectral coherence
intensity 0.52 0…4; reflected-light energy, with values above 1 reserved for vivid highlights
spectrum "film" film, pearl, oil, cool, chrome, or 2–12 CSS colors
spread 0.42 0…1; size of the virtual area light
bevel 0.82 0…1; strength and width of the rounded edge response
curvature 0 0…1; bevel depth from a shallow rim to a full spherical profile
rimThickness 1 0.5…2; multiplies the optical white and spectral rim width
angle 118 Fine surface-grain direction in degrees
speed 0.22 Ambient drift speed, from 0…4
mouseLag 0.18 0…1; pointer response from immediate to a heavy trailing delay
seed element-derived Stable variation for ambient movement
blendMode "screen" screen, soft-light, overlay, color-dodge, or plus-lighter
borderRadius computed Optional CSS radius override
respectReducedMotion true Holds ambient light still when reduced motion is requested

roughness, spread, curvature, and mouseLag are clamped to 0…1; intensity is clamped to 0…4, and rimThickness to 0.5…2. Invalid modes, speeds, spectra, and non-finite numbers throw early.

Framework cleanup

Create the effect after the element mounts and destroy it when the component unmounts. The package has no React dependency.

useEffect(() => {
  if (!ref.current) return;
  const sheen = attachIridescence(ref.current, options);
  return () => sheen.destroy();
}, []);

The library is browser-only at attachment time, but safe to import during SSR. Selector targets resolve against the current global document; passing an element uses that element's own document, including iframes.

DevTools test drive

The demo exposes both helpers globally. Select an element in the Elements panel ($0), then run:

const effect = attachIridescence($0, {
  intensity: 1.4,
  roughness: 0.12,
  bevel: 0.9,
  mouseLag: 0.18,
});

effect.destroy();

After a future npm release, the same test can be loaded from an ESM CDN:

const { attachIridescence } = await import(
  "https://esm.sh/@softcopy/iridescence@0.1.0"
);
window.testSheen = attachIridescence($0, { intensity: 1.4 });

Remote imports can be blocked by a site's Content Security Policy. The renderer adapts to ordinary rectangular, rounded, pill, and circular DOM boxes. Arbitrary SVG paths, rotated geometry, and continuously transformed elements are not automatic shape matches; call refresh() while authored geometry moves, or use a dedicated renderer for non-box silhouettes.

Physical reduction

The full materials-engine research evaluates complex-IOR Fresnel, thin-film interference, and rough microfacet response in a GPU compositor. This package keeps the perceptual relationship that matters at DOM scale: incidence moves the spectrum, roughness widens the reflected source and averages away color separation, and a neutral specular core anchors the sheen as reflected light. It deliberately drops material baking, geometry compilation, WebGPU, textures, and framework adapters.

Run the local instrument after building:

npm run build
python3 -m http.server 4173
# open http://localhost:4173/demo/

Cross-library stress lab

The repository also includes a live compatibility and load harness using real controls from Material Web, MUI, Bootstrap, and Shoelace. It applies one shared iridescence group to mixed buttons, switches, sliders, chips, tags, checkboxes, and inputs, then compares baseline and effect frame timing from 48 through 400 targets.

npm run stress
# open http://127.0.0.1:5173/stress/

Use Run full suite for the paired baseline/effect test. The destroy/attach control exercises complete teardown, and the synthetic pointer sweep makes the pointer-driven material reproducible without touching every specimen. These UI packages are development-only dependencies and are excluded from the published package contents.

Development

npm ci
npm test
npm run pack:check

CI runs the tests and package-content check on every push and pull request. Generated dist/ files are intentionally excluded from Git; prepare and prepack create them from the TypeScript source for GitHub installs and future npm packages.

The demo bundles Absans by Valerio Monopoli and Necto Mono by Marco Condello, both distributed by Collletttivo under the SIL Open Font License 1.1. The full font license is included beside the font files in demo/fonts/OFL.txt.

About

A tiny, reversible WebGL iridescent sheen for DOM elements.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages