Imperative Surface
The imperative API exposes the same renderer through direct command submission. Use it when draw commands come from an external system, when command scheduling must be explicit, or when a reusable controller is easier to integrate than a declarative tree.
SkiaView is the host component. createSkiaSurface() creates a controller that binds to a host node and submits SkiaDrawCommand[] or complete frame objects.
Basic usage
import { SkiaView } from "@zynthjs/skia";
export function StaticSurface() {
return (
<SkiaView
clearColor="#020617"
style={{ width: 320, height: 180 }}
commands={[
{ type: "clear", color: "#020617" },
{
type: "rect",
x: 20,
y: 20,
width: 280,
height: 140,
color: "#1e293b",
},
{ type: "circle", cx: 160, cy: 90, r: 24, color: "#38bdf8" },
]}
/>
);
}
Advanced examples
Binding a controller
import { SkiaView, createSkiaSurface } from "@zynthjs/skia";
const surface = createSkiaSurface();
export function SurfaceWithController() {
return (
<SkiaView
ref={(node) => surface.bind(node)}
clearColor="#0f172a"
style={{ width: 320, height: 180 }}
/>
);
}
surface.submit([
{ type: "clear", color: "#0f172a" },
{
type: "path",
commands: [
{ type: "moveTo", x: 24, y: 120 },
{ type: "quadTo", cpx: 160, cpy: 36, x: 296, y: 120 },
],
color: "#22c55e",
style: "stroke",
strokeWidth: 4,
},
]);
Submitting a frame object
import { createSkiaSurface } from "@zynthjs/skia";
const surface = createSkiaSurface();
surface.submitFrame({
clear: "#111827",
commands: [
{ type: "clear", color: "#111827" },
{
type: "text",
text: "Frame ready",
x: 32,
y: 72,
color: "#f8fafc",
fontFamily: "System",
fontSize: 18,
},
],
});
Driving redraws manually
import { createSignal, createEffect, onCleanup } from "solid-js";
import { SkiaView, createSkiaSurface } from "@zynthjs/skia";
const surface = createSkiaSurface();
export function ManualFrameLoop() {
const [progress, setProgress] = createSignal(0);
createEffect(() => {
const t = progress();
surface.submit([
{ type: "clear", color: "#020617" },
{
type: "rect",
x: 24,
y: 72,
width: 272 * t,
height: 20,
color: "#0ea5e9",
},
]);
});
let frame = 0;
let handle = 0;
const tick = () => {
frame += 1;
setProgress((frame % 120) / 120);
handle = requestAnimationFrame(tick);
};
handle = requestAnimationFrame(tick);
onCleanup(() => cancelAnimationFrame(handle));
return (
<SkiaView
ref={(node) => surface.bind(node)}
style={{ width: 320, height: 180 }}
/>
);
}
Special cases and unusual features
SkiaViewcan be used directly with acommandsprop or indirectly through aSkiaSurfaceController.- When
commandsis passed as an accessor,SkiaViewschedules submission on the next animation frame rather than submitting synchronously on every reactive change. submitFrame()accepts aclearfield, but the draw list remains the main source of rendering work.frameLoopenables continuous rendering on the native surface. Leave it disabled for static or manually invalidated content.- The imperative and declarative APIs share the same native draw command format and resource types.
API Reference
SkiaView
style?: StyleclearColor?: stringframeLoop?: booleanallowFallback?: booleancommands?: SkiaDrawCommand[] | (() => SkiaDrawCommand[])ref?: (node: HostNode | null) => voidonNativeReady?: (event: { nativeEvent: { available: boolean } }) => voidonLayout?: (event: LayoutChangeEvent) => void
createSkiaSurface()
Returns SkiaSurfaceController:
bind(node: HostNode | null): voidcurrentNodeId(): number | nullsubmit(commands: SkiaDrawCommand[]): voidsubmitFrame(frame: SkiaFrameSpec): voidinvalidate(): voidsetFrameLoopEnabled(enabled: boolean): voiddispose(): void
Core draw command types
clearrectcirclelinepathtextimagesvgskottieruntimeShaderRectruntimeShaderCircleruntimeShaderPathsaveLayersaveLayerLuminanceMaskrestore