panime-widget
v0.1.1
Published
Load a Panime portable work (.panime.png) and render its live jiggle in the browser. Framework-agnostic core + <panime-widget> custom element + React wrapper.
Maintainers
Readme
panime-widget
Load a Panime portable work (*.panime.png) and render its live jiggle in the browser.
- Framework-agnostic core (vanilla TS + WebGL2) +
<panime-widget>custom element + React wrapper - Numerically faithful to the Panime app (physics/shader shared, golden-tested)
- ~9 KB gzipped (excluding the image data); falls back to the static photo when WebGL is unavailable
Live demo: https://panime.ft07.com
Install
npm i panime-widget # or: pnpm add panime-widget / yarn add panime-widgetReact is an optional peer dependency — only needed if you use panime-widget/react.
Usage
1. Web component (any framework, or plain HTML)
Importing the package registers the <panime-widget> element:
import 'panime-widget';<panime-widget
src="work.panime.png"
autoplay="idle" // loop | idle | off
pointer // pointer/parallax follow; pointer="off" disables
gyro="button"> // off | button | auto (mobile motion; iOS asks on tap)
</panime-widget>2. Plain <script> tag (no bundler)
<script src="https://unpkg.com/panime-widget/dist/panime-widget.min.js"></script>
<panime-widget src="work.panime.png" autoplay="idle" pointer></panime-widget>3. React
import { PanimeWidget } from 'panime-widget/react';
<PanimeWidget
src="/work.panime.png"
autoplay="idle" // loop | idle | off
pointer // boolean
gyro="button" // off | button | auto
style={{ width: 320, height: 560 }}
/>4. Direct engine (custom canvas / full control)
import { PanimeEngine, PARAM_TABLE } from 'panime-widget';
const engine = new PanimeEngine(canvas, PARAM_TABLE, { autoplay: 'loop', pointer: true });
await engine.load(new Uint8Array(await (await fetch(url)).arrayBuffer()));
// engine.inputDriver.setPointer(nx, ny) / .clearPointer() / .setGyro(tx, ty)Attributes / props
| name | values | default | notes |
|---|---|---|---|
| src | URL | — | the .panime.png file |
| autoplay | loop idle off | idle | autonomous motion; idle = gentle |
| pointer | boolean / off | on | mouse/pointer follow + parallax; eases back on leave |
| gyro | off button auto | off | device motion; iOS 13+ needs a tap to grant; cross-origin iframes need allow="gyroscope" on the parent |
Pointer response is smoothed and buffers back to rest when the pointer leaves (no snap).
Notes
- Self-contained file. A
.panime.pngis a normal photo that also embeds the motion data in a private PNG chunk. Any image viewer shows the photo; only Panime reads the motion. Re-encoding by other tools can strip the motion data — the widget detects this and falls back to the static photo. - No servers, no tracking. Everything runs client-side in WebGL.
Develop
pnpm install
pnpm test # vitest — physics/render/parse aligned to the app's Dart golden fixtures
pnpm typecheck
pnpm build # gen shader → tsup (ESM + d.ts) → esbuild (IIFE) into dist/
node scripts/gen-shader.mjs # regenerate the WebGL shader from app/shaders/jiggle.frag
node scripts/verify.mjs 0.02 0.45 # headless Chrome (SwiftShader) pose screenshotsGolden fixtures are produced app-side via flutter test test/tool/gen_*_test.dart; rerun after changing the physics or format.
