Skip to content

scumbleDeclarative GPU drawing for Lynx

The component API mirrors react-native-skia — Canvas, shapes, declarative paints, Path2D — so an RN-Skia scene ports to Lynx mostly by changing the import. Powered by the skity GPU backend: one C++ renderer drives Android (OpenGL ES / Vulkan) and iOS (Metal), with zero JS per frame animations on the render thread.

11shape components
4gradient types
28blend modes
16animatable properties
0JS per animated frame

Coming from react-native-skia? ​

That's the idea. scumble tracks the react-native-skia component API (baseline 2.11.0) — the same <Canvas> / shape / <Paint> / gradient / filter names and props, the Canvas-style Path2D, image and paragraph components — so an RN-Skia scene ports to Lynx mostly by changing the import. Feature-by-feature status lives in FEATURE_PARITY.md (geometry ~95%, paint ~90%, text ~85%).

Where it deliberately differs: rendering rides the skity GPU backend (not Skia) through a FlatBuffer command stream — no JSI imperative object surface — and animation is built in rather than delegated to reanimated. skity is also far leaner — ~90K lines of C++, roughly a tenth of Skia — which keeps scumble's whole native APK footprint under a third of react-native-skia's, at 90–95% feature parity. See the comparison.

An animation engine that lives on the render thread ​

Declare a track once; a per-vsync tick on the render thread interpolates every frame and a stop-on-idle driver powers down when nothing is playing. Playback control comes with it — pause, seek, and completion callbacks without a single extra frame of JS.

tsx
import { Canvas, Rect, createAnimation } from "@scumble/react";

// declarative track: keyframes, easing, iterations…
const spin = createAnimation({
  property: "rotate",
  from: 0,
  to: 360,
  duration: 3000,
  iterations: Infinity,
});

<Canvas style={{ width: "100%", height: 200 }}>
  <Rect x={60} y={60} width={80} height={80} color="#f59e0b" animate={spin} />
</Canvas>;

// imperative playback — no re-render involved:
spin.controller.pause();
spin.controller.seekTo(1500);
spin.controller.onFinish(() => done());

Read the animation guide and playback control, or browse the demos.

Released under the Apache License 2.0.