Documentation Index
Fetch the complete documentation index at: https://mintlify.com/shopify/react-native-skia/llms.txt
Use this file to discover all available pages before exploring further.
Skia provides a powerful shading language called SkSL that allows you to create custom shader effects. The syntax is very similar to GLSL, making it accessible if you have graphics programming experience.
Overview
SkSL (Skia Shading Language) lets you write custom shaders for unique visual effects. You can experiment with SkSL shaders in the online editor.
If you’re familiar with GLSL or looking to convert GLSL shaders to SkSL, check out the differences between GLSL and SkSL.
Creating a Runtime Effect
First, compile your shader code using RuntimeEffect.Make:
import { Skia } from "@shopify/react-native-skia";
const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
// The canvas is 256x256
vec2 canvas = vec2(256);
// normalized x,y values go from 0 to 1
vec2 normalized = pos/canvas;
return vec4(normalized.x, normalized.y, 0.5, 1);
}`);
if (!source) {
throw new Error("Couldn't compile the shader");
}
Shader Component
Use the Shader component to apply your custom shader:
Props
| Name | Type | Description |
|---|
| source | RuntimeEffect | Compiled shader from RuntimeEffect.Make |
| uniforms | { [name: string]: number | Vector | Vector[] | number[] | number[][] } | Uniform values to pass to the shader |
| children | Shader | Child shaders to use as uniforms |
Simple Shader Example
import { Canvas, Shader, Fill, Skia } from "@shopify/react-native-skia";
const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
// normalized x,y values go from 0 to 1, the canvas is 256x256
vec2 normalized = pos/vec2(256);
return vec4(normalized.x, normalized.y, 0.5, 1);
}`)!;
const SimpleShader = () => {
return (
<Canvas style={{ width: 256, height: 256 }}>
<Fill>
<Shader source={source} />
</Fill>
</Canvas>
);
};
Uniforms are variables that allow you to pass parameters to your shaders. They’re perfect for creating dynamic, interactive effects.
float, float2, float3, float4
float2x2, float3x3, float4x4
int, int2, int3, int4
- Arrays:
uniform float3 colors[12]
import { Canvas, Shader, Fill, Skia, vec } from "@shopify/react-native-skia";
const source = Skia.RuntimeEffect.Make(`
uniform vec2 c;
uniform float r;
uniform float blue;
vec4 main(vec2 pos) {
vec2 normalized = pos/vec2(2 * r);
return distance(pos, c) > r ? vec4(1) : vec4(normalized, blue, 1);
}`)!;
const UniformShader = () => {
const r = 128;
const c = vec(2 * r, r);
const blue = 1.0;
return (
<Canvas style={{ width: 256, height: 256 }}>
<Fill>
<Shader source={source} uniforms={{ c, r, blue }} />
</Fill>
</Canvas>
);
};
Combine with React Native Reanimated for dynamic effects:
import { Canvas, Shader, Fill, Skia, vec } from "@shopify/react-native-skia";
import { useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated";
import { useEffect } from "react";
const source = Skia.RuntimeEffect.Make(`
uniform float time;
uniform vec2 resolution;
vec4 main(vec2 pos) {
vec2 uv = pos / resolution;
float wave = sin(uv.x * 10.0 + time) * 0.5 + 0.5;
return vec4(wave, uv.y, 1.0 - wave, 1.0);
}`)!;
const AnimatedShader = () => {
const time = useSharedValue(0);
useEffect(() => {
time.value = withRepeat(
withTiming(Math.PI * 2, { duration: 3000 }),
-1,
false
);
}, []);
const uniforms = useDerivedValue(() => ({
time: time.value,
resolution: vec(256, 256),
}));
return (
<Canvas style={{ width: 256, height: 256 }}>
<Fill>
<Shader source={source} uniforms={uniforms} />
</Fill>
</Canvas>
);
};
Nested Shaders
Pass other shaders as inputs to your custom shader:
import { Canvas, Shader, Fill, ImageShader, Skia, useImage } from "@shopify/react-native-skia";
const source = Skia.RuntimeEffect.Make(`
uniform shader image;
half4 main(float2 xy) {
xy.x += sin(xy.y / 3) * 4;
return image.eval(xy);
}`)!;
const NestedShader = () => {
const image = useImage(require("./assets/oslo.jpg"));
if (!image) {
return null;
}
return (
<Canvas style={{ width: 256, height: 256 }}>
<Fill>
<Shader source={source}>
<ImageShader
image={image}
fit="cover"
rect={{ x: 0, y: 0, width: 256, height: 256 }}
/>
</Shader>
</Fill>
</Canvas>
);
};
Common Shader Patterns
Gradient Effect
const gradientShader = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform vec4 color1;
uniform vec4 color2;
vec4 main(vec2 pos) {
float t = pos.y / resolution.y;
return mix(color1, color2, t);
}`)!;
Checkerboard Pattern
const checkerboard = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform float size;
vec4 main(vec2 pos) {
vec2 cell = floor(pos / size);
float checker = mod(cell.x + cell.y, 2.0);
return vec4(vec3(checker), 1.0);
}`)!;
Circle Pattern
const circles = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform float radius;
vec4 main(vec2 pos) {
vec2 center = resolution / 2.0;
float dist = distance(pos, center);
float circle = step(dist, radius);
return vec4(vec3(circle), 1.0);
}`)!;
Ripple Effect
const ripple = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform float time;
uniform vec2 center;
vec4 main(vec2 pos) {
float dist = distance(pos, center);
float wave = sin(dist * 0.05 - time * 3.0) * 0.5 + 0.5;
return vec4(vec3(wave), 1.0);
}`)!;
Advanced Techniques
const source = Skia.RuntimeEffect.Make(`
uniform shader background;
uniform shader foreground;
uniform float blend;
half4 main(float2 xy) {
half4 bg = background.eval(xy);
half4 fg = foreground.eval(xy);
return mix(bg, fg, blend);
}`)!;
const MultiShader = () => {
const image1 = useImage(require("./assets/bg.jpg"));
const image2 = useImage(require("./assets/fg.jpg"));
const blend = useSharedValue(0.5);
return (
<Canvas style={{ width: 256, height: 256 }}>
<Fill>
<Shader source={source} uniforms={{ blend }}>
<ImageShader image={image1} fit="cover" rect={{ x: 0, y: 0, width: 256, height: 256 }} />
<ImageShader image={image2} fit="cover" rect={{ x: 0, y: 0, width: 256, height: 256 }} />
</Shader>
</Fill>
</Canvas>
);
};
Color Manipulation
const colorEffect = Skia.RuntimeEffect.Make(`
uniform shader input;
uniform float saturation;
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
half4 main(float2 xy) {
half4 color = input.eval(xy);
vec3 hsv = rgb2hsv(color.rgb);
hsv.y *= saturation;
return vec4(hsv2rgb(hsv), color.a);
}`)!;
- Keep shader code simple and efficient
- Avoid complex branching (if/else) when possible
- Use built-in functions (sin, cos, etc.) efficiently
- Minimize uniform updates
- Cache compiled RuntimeEffects
- Test on target devices for performance
Debugging Shaders
Compilation Errors
const source = Skia.RuntimeEffect.Make(shaderCode);
if (!source) {
console.error("Shader compilation failed");
// Handle error
}
Visual Debugging
Output debug values as colors:
const debug = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
// Visualize position as color
return vec4(pos.x / 256.0, pos.y / 256.0, 0, 1);
}`)!;
Built-in Functions
SkSL supports many built-in functions:
- Math:
sin, cos, tan, pow, sqrt, abs, floor, ceil, fract, mod
- Vector:
dot, cross, length, distance, normalize
- Interpolation:
mix, clamp, smoothstep, step
- Color:
mix for blending colors
See Also