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.
The Atlas component is used for efficient rendering of multiple instances of the same texture or image. It’s especially useful for drawing a large number of similar objects, like sprites or tiles, with varying transformations.
Atlas transforms can be animated with near-zero cost using worklets, making it ideal for tile-based maps, sprite animations, and scenarios with many instances of similar textures.
Properties
| Name | Type | Description |
|---|
| image | SkImage or null | Image containing the sprites |
| sprites | SkRect[] | Locations of sprites in the atlas |
| transforms | RSXform[] | Rotation/scale transforms for each sprite |
| colors? | SkColor[] | Optional colors to blend with sprites |
| blendMode? | BlendMode | Blend mode for sprites and colors |
| sampling? | Sampling | Sampling method for the image |
The RSXform object is a compressed transformation matrix: [fSCos -fSSin fTx, fSSin fSCos fTy, 0, 0, 1].
Useful transformations:
import { Skia } from "@shopify/react-native-skia";
// 1. Identity (no transformation)
let rsxForm = Skia.RSXform(1, 0, 0, 0);
// 2. Scale by 2 and translate by (50, 100)
rsxForm = Skia.RSXform(2, 0, 50, 100);
// 3. Rotate by PI/4, translate by (50, 100)
const r = Math.PI/4;
rsxForm = Skia.RSXform(Math.cos(r), Math.sin(r), 50, 100);
// 4. Scale by 2, rotate by PI/4 with pivot (25, 25)
rsxForm = Skia.RSXformFromRadians(2, r, 0, 0, 25, 25);
// 5. Translate by (125, 0), rotate by PI/4 with pivot (125, 25)
rsxForm = Skia.RSXformFromRadians(1, r, 100, 0, 125, 25);
Hello World
In this example, we draw a rectangle as an image, then display it 150 times with transformations:
import {
Skia,
drawAsImage,
Group,
Rect,
Canvas,
Atlas,
rect
} from "@shopify/react-native-skia";
const size = { width: 25, height: 11.25 };
const strokeWidth = 2;
const imageSize = {
width: size.width + strokeWidth,
height: size.height + strokeWidth,
};
const image = await drawAsImage(
<Group>
<Rect
rect={rect(
strokeWidth / 2,
strokeWidth / 2,
size.width,
size.height
)}
color="cyan"
/>
<Rect
rect={rect(
strokeWidth / 2,
strokeWidth / 2,
size.width,
size.height
)}
color="blue"
style="stroke"
strokeWidth={strokeWidth}
/>
</Group>,
imageSize
);
export const Demo = () => {
const numberOfBoxes = 150;
const pos = { x: 128, y: 128 };
const width = 256;
const sprites = new Array(numberOfBoxes)
.fill(0)
.map(() => rect(0, 0, imageSize.width, imageSize.height));
const transforms = new Array(numberOfBoxes).fill(0).map((_, i) => {
const tx = 5 + ((i * size.width) % width);
const ty = 25 + Math.floor(i / (width / size.width)) * size.width;
const r = Math.atan2(pos.y - ty, pos.x - tx);
return Skia.RSXform(Math.cos(r), Math.sin(r), tx, ty);
});
return (
<Canvas style={{ flex: 1 }}>
<Atlas image={image} sprites={sprites} transforms={transforms} />
</Canvas>
);
};
Animations
The Atlas component should typically be used with Reanimated. Use useTexture to create textures on the UI thread and hooks like useRectBuffer and useRSXformBuffer to efficiently animate sprites and transformations.
import {
Skia,
drawAsImage,
Group,
Rect,
Canvas,
Atlas,
rect,
useTexture,
useRSXformBuffer,
} from "@shopify/react-native-skia";
import { useSharedValue } from "react-native-reanimated";
import { GestureDetector, Gesture } from "react-native-gesture-handler";
const size = { width: 25, height: 11.25 };
const strokeWidth = 2;
const textureSize = {
width: size.width + strokeWidth,
height: size.height + strokeWidth,
};
export const Demo = () => {
const pos = useSharedValue({ x: 0, y: 0 });
const texture = useTexture(
<Group>
<Rect
rect={rect(
strokeWidth / 2,
strokeWidth / 2,
size.width,
size.height
)}
color="cyan"
/>
<Rect
rect={rect(
strokeWidth / 2,
strokeWidth / 2,
size.width,
size.height
)}
color="blue"
style="stroke"
strokeWidth={strokeWidth}
/>
</Group>,
textureSize
);
const gesture = Gesture.Pan().onChange((e) => (pos.value = e));
const numberOfBoxes = 150;
const width = 256;
const sprites = new Array(numberOfBoxes)
.fill(0)
.map(() => rect(0, 0, textureSize.width, textureSize.height));
const transforms = useRSXformBuffer(numberOfBoxes, (val, i) => {
"worklet";
const tx = 5 + ((i * size.width) % width);
const ty = 25 + Math.floor(i / (width / size.width)) * size.width;
const r = Math.atan2(pos.value.y - ty, pos.value.x - tx);
val.set(Math.cos(r), Math.sin(r), tx, ty);
});
return (
<GestureDetector gesture={gesture}>
<Canvas style={{ flex: 1 }}>
<Atlas image={texture} sprites={sprites} transforms={transforms} />
</Canvas>
</GestureDetector>
);
};
- Single draw call - Renders all instances in one pass
- GPU acceleration - Takes full advantage of hardware
- Minimal FFI cost - Worklet-based animations run entirely on UI thread
- Efficient memory - Shares texture memory across instances
Use Cases
- Tile-based maps
- Sprite animations for games
- Particle systems
- Repeated UI elements
- Icon grids