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

@animatereactnative/skia-loaders

v0.1.0

Published

Mathematical curve loading animations for React Native, powered by Skia and Reanimated. 20 particle-trail loaders driven entirely on the UI thread.

Readme

NPM Version runs with expo npm npm

20 mathematical curve loaders for React Native — roses, spirographs, lemniscates, butterflies, hearts — each tracing a parametric curve with a fading comet tail and a slow breathing pulse, powered by Skia and Reanimated:

  • 🔋 Powered by Skia & Reanimated 4
  • 📱 Works with Expo, including Expo Go
  • ✅ Cross-platform (iOS, Android, Web)
  • ⚡️ 60-120fps, animated entirely on the UI thread
  • 🌀 20 built-in curves, or bring your own
  • 🎨 Any color, any size, composable into your own Skia canvas
  • ⌨️ Written in TypeScript

Installation

npm install @animatereactnative/skia-loaders

Also, you need to install @shopify/react-native-skia and react-native-reanimated, and follow their installation instructions. Both ship with Expo Go, so there is nothing else to configure there.

npx expo install @shopify/react-native-skia react-native-reanimated

Usage

import { CurveLoader } from '@animatereactnative/skia-loaders';

// ...

export function Example() {
  return <CurveLoader curve="roseOrbit" size={120} color="#bada55" />;
}

Props

Both CurveLoader and CurveLoaderContents accept:

| name | description | required | type | default | | --------------- | -------------------------------------------------------------------------------------- | -------- | --------------------------- | ----------- | | curve | A built-in curve name, or your own curve config | YES | CurveName \| CurveConfig | | | size | Side of the square the loader draws into, in pixels | YES | number | | | color | Particle and outline color. Any Skia-parseable color string | NO | string | '#ffffff' | | particleScale | Multiplies particle radius. Useful to bulk up the trail at large sizes | NO | number | 1 | | phaseOffset | Shifts this loader along its own timeline (0…1). Stagger siblings so they don't sync | NO | number | 0 | | paused | Freezes the animation. A paused loader schedules no frames at all | NO | boolean | false | | style | View style applied to the underlying Skia <Canvas>. CurveLoader only | NO | StyleProp<ViewStyle> | |

Curves

import { CurveLoader, curveNames, curves } from '@animatereactnative/skia-loaders';

curveNames.map((name, i) => (
  <View key={name}>
    <CurveLoader curve={name} size={140} phaseOffset={i / curveNames.length} />
    <Text>{curves[name].name}</Text>
  </View>
));

| | | | | | ------------------ | ------------------ | ------------------ | ---------------- | | originalThinking | thinkingFive | thinkingNine | roseOrbit | | roseCurve | roseTwo | roseThree | lissajousDrift | | lemniscateBloom | hypotrochoidLoop | threePetalSpiral | fourPetalSpiral| | fivePetalSpiral | sixPetalSpiral | butterflyPhase | cardioidGlow | | cardioidHeart | heartWave | spiralSearch | fourierFlow |

Every entry in curves carries a name and tag for labelling, plus its timings and particle count. Prose descriptions live behind a separate entry point so they don't ship with the library by default:

import { curveDescriptions } from '@animatereactnative/skia-loaders/descriptions';

curveDescriptions.roseOrbit.en; // "Radius expands and contracts with cos(7t)…"
curveDescriptions.roseOrbit.zh; // "半径随 cos(7t) 起伏…"

Composing into an existing canvas

<CurveLoader /> mounts its own <Canvas>. If you already have one — a loader inside a larger Skia scene — use <CurveLoaderContents /> and position it with a <Group>. One canvas holding many loaders is meaningfully cheaper than many canvases, so prefer this for grids.

import { Canvas, Group } from '@shopify/react-native-skia';
import { CurveLoaderContents } from '@animatereactnative/skia-loaders';

// ...

<Canvas style={{ flex: 1 }}>
  <Group transform={[{ translateX: 40 }, { translateY: 120 }]}>
    <CurveLoaderContents curve="roseOrbit" size={80} />
  </Group>
</Canvas>;

Custom curves

A curve is a point worklet mapping progress 0…1 onto a [0,100]² box, plus its parameters and timings. detailScale (~0.52…1) is the breathing factor.

import type { CurveConfig } from '@animatereactnative/skia-loaders';

// Define at module scope — a new object identity each render rebuilds geometry.
const wobble: CurveConfig<{ lobes: number }> = {
  name: 'Wobble',
  tag: 'custom',
  particleCount: 70,
  trailSpan: 0.35,
  durationMs: 5000,
  rotationDurationMs: 24000,
  pulseDurationMs: 4200,
  strokeWidth: 4.5,
  rotate: true,
  params: { lobes: 6 },
  point(progress, detailScale, params) {
    'worklet';
    const t = progress * Math.PI * 2;
    const r = 30 + Math.cos(params.lobes * t) * 8 * detailScale;
    return { x: 50 + Math.cos(t) * r, y: 50 + Math.sin(t) * r };
  },
};

<CurveLoader curve={wobble} size={140} />;

The point function must carry the 'worklet' directive — it runs on the UI thread. The built-in point functions (rosePoint, spiroPoint, lissajousPoint, …) are exported too, if you want to reuse one with your own parameters and timings.

Performance

The loaders are built to run many at once:

  • The faint outline is baked once per quantized breathing step at mount, so no path geometry is rebuilt per frame.
  • Per-particle size, opacity and trail position are precomputed into typed arrays; the per-frame work per particle is one curve evaluation and one transform write.
  • Particles draw through a single <Atlas> against a sprite sheet cached per color, so there are no per-frame allocations for color or blending.
  • Rotation only costs anything for curves that actually rotate.
  • paused fully detaches the frame callback, so an off-screen loader is free.

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT