Skip to main content
Textures allow you to render React components (including React Native Skia components) as images that can be used in animations and effects. This enables powerful composition patterns and performance optimizations.

useTexture

Render a React element as a texture:
import { useTexture, Canvas, Image, Circle, Fill } from "@shopify/react-native-skia";
import { useSharedValue, useDerivedValue } from "react-native-reanimated";

const TextureDemo = () => {
  const rotation = useSharedValue(0);

  // Render a React element as a texture
  const texture = useTexture(
    <Circle cx={64} cy={64} r={40} color="blue">
      <Fill color="red" />
    </Circle>,
    { width: 128, height: 128 }
  );

  const transform = useDerivedValue(() => [
    { rotate: rotation.value }
  ]);

  if (!texture.value) return null;

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Image 
        image={texture} 
        x={64} 
        y={64} 
        width={128} 
        height={128}
        transform={transform}
      />
    </Canvas>
  );
};

Parameters

NameTypeDescription
elementReactElementReact element to render
sizeSkSizeTexture size { width, height }
depsDependencyListDependencies for re-rendering

Use Cases

Complex Graphics as Textures

Render complex graphics once and reuse as textures:
import { useTexture, Canvas, Image } from "@shopify/react-native-skia";

const ComplexGraphicTexture = () => {
  const complexGraphic = (
    <Group>
      <Circle cx={64} cy={64} r={40} color="#FF6B6B" />
      <Circle cx={80} cy={50} r={30} color="#4ECDC4" opacity={0.7} />
      <Circle cx={50} cy={80} r={30} color="#45B7D1" opacity={0.7} />
      <Text text="Hello" x={32} y={70} font={font} color="white" />
    </Group>
  );

  const texture = useTexture(complexGraphic, { width: 128, height: 128 });

  if (!texture.value) return null;

  return (
    <Canvas style={{ flex: 1 }}>
      {/* Render the texture multiple times efficiently */}
      <Image image={texture} x={0} y={0} width={128} height={128} />
      <Image image={texture} x={128} y={0} width={128} height={128} />
      <Image image={texture} x={0} y={128} width={128} height={128} />
      <Image image={texture} x={128} y={128} width={128} height={128} />
    </Canvas>
  );
};

Animated Textures

Animate textures with transforms:
import { useTexture, Canvas, Image } from "@shopify/react-native-skia";
import { useSharedValue, withRepeat, withTiming, useDerivedValue } from "react-native-reanimated";
import { useEffect } from "react";

const AnimatedTextureDemo = () => {
  const rotation = useSharedValue(0);
  const scale = useSharedValue(1);

  useEffect(() => {
    rotation.value = withRepeat(
      withTiming(Math.PI * 2, { duration: 3000 }),
      -1,
      false
    );
    scale.value = withRepeat(
      withSequence(
        withTiming(1.2, { duration: 1000 }),
        withTiming(0.8, { duration: 1000 })
      ),
      -1,
      true
    );
  }, []);

  const graphic = (
    <RoundedRect x={0} y={0} width={100} height={100} r={20} color="purple" />
  );

  const texture = useTexture(graphic, { width: 100, height: 100 });

  const transform = useDerivedValue(() => [
    { rotate: rotation.value },
    { scale: scale.value }
  ]);

  if (!texture.value) return null;

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Image 
        image={texture} 
        x={78} 
        y={78} 
        width={100} 
        height={100}
        transform={transform}
        origin={{ x: 128, y: 128 }}
      />
    </Canvas>
  );
};

Texture with Dependencies

Re-render texture when dependencies change:
import { useTexture } from "@shopify/react-native-skia";
import { useState } from "react";

const DynamicTextureDemo = () => {
  const [color, setColor] = useState("red");
  const [text, setText] = useState("Hello");

  // Texture re-renders when color or text changes
  const texture = useTexture(
    <Group>
      <Circle cx={64} cy={64} r={50} color={color} />
      <Text text={text} x={32} y={70} font={font} color="white" />
    </Group>,
    { width: 128, height: 128 },
    [color, text] // Dependencies
  );

  if (!texture.value) return null;

  return (
    <View>
      <Canvas style={{ width: 128, height: 128 }}>
        <Image image={texture} x={0} y={0} width={128} height={128} />
      </Canvas>
      <Button title="Red" onPress={() => setColor("red")} />
      <Button title="Blue" onPress={() => setColor("blue")} />
    </View>
  );
};

usePictureAsTexture

Convert a SkPicture to a texture:
import { usePictureAsTexture, Skia } from "@shopify/react-native-skia";
import { useEffect, useState } from "react";

const PictureTextureDemo = () => {
  const [picture, setPicture] = useState<SkPicture | null>(null);

  useEffect(() => {
    const pic = Skia.Picture.MakePicture((canvas) => {
      const paint = Skia.Paint();
      paint.setColor(Skia.Color("blue"));
      canvas.drawCircle(64, 64, 50, paint);
    }, { x: 0, y: 0, width: 128, height: 128 });
    setPicture(pic);
  }, []);

  const texture = usePictureAsTexture(picture, { width: 128, height: 128 });

  if (!texture.value) return null;

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Image image={texture} x={64} y={64} width={128} height={128} />
    </Canvas>
  );
};

useImageAsTexture

Use an image as a texture with automatic conversion:
import { useImageAsTexture, Canvas, Image } from "@shopify/react-native-skia";

const ImageTextureDemo = () => {
  const texture = useImageAsTexture(require("./photo.jpg"));

  if (!texture.value) return null;

  return (
    <Canvas style={{ flex: 1 }}>
      <Image 
        image={texture} 
        x={0} 
        y={0} 
        width={256} 
        height={256} 
        fit="cover"
      />
    </Canvas>
  );
};

Performance Benefits

Rendering Complex Graphics Once

Textures are rendered once and cached:
// Bad: Re-renders complex graphic every frame
const BadExample = () => {
  const rotation = useSharedValue(0);

  return (
    <Canvas style={{ flex: 1 }}>
      <Group transform={[{ rotate: rotation }]}>
        {/* Complex graphic re-rendered every frame */}
        <Circle cx={64} cy={64} r={40} color="red" />
        <Circle cx={80} cy={50} r={30} color="blue" />
        <Circle cx={50} cy={80} r={30} color="green" />
        {/* ... many more shapes ... */}
      </Group>
    </Canvas>
  );
};

// Good: Renders once, animates as texture
const GoodExample = () => {
  const rotation = useSharedValue(0);

  const graphic = (
    <Group>
      <Circle cx={64} cy={64} r={40} color="red" />
      <Circle cx={80} cy={50} r={30} color="blue" />
      <Circle cx={50} cy={80} r={30} color="green" />
      {/* ... many more shapes ... */}
    </Group>
  );

  const texture = useTexture(graphic, { width: 128, height: 128 });

  if (!texture.value) return null;

  return (
    <Canvas style={{ flex: 1 }}>
      <Image 
        image={texture} 
        x={0} 
        y={0} 
        width={128} 
        height={128}
        transform={[{ rotate: rotation }]}
      />
    </Canvas>
  );
};

Reusing Graphics

Textures can be reused multiple times efficiently:
const ParticleSystem = () => {
  const particleTexture = useTexture(
    <Circle cx={8} cy={8} r={8} color="white">
      <BlurMask blur={4} style="solid" />
    </Circle>,
    { width: 16, height: 16 }
  );

  if (!particleTexture.value) return null;

  return (
    <Canvas style={{ flex: 1 }}>
      {/* Render 100 particles using the same texture */}
      {Array.from({ length: 100 }).map((_, i) => (
        <Image
          key={i}
          image={particleTexture}
          x={Math.random() * 256}
          y={Math.random() * 256}
          width={16}
          height={16}
          opacity={Math.random()}
        />
      ))}
    </Canvas>
  );
};

Texture Filters

Apply filters to textures:
import { useTexture, Canvas, Image, Blur } from "@shopify/react-native-skia";

const FilteredTextureDemo = () => {
  const graphic = (
    <Circle cx={64} cy={64} r={50} color="cyan" />
  );

  const texture = useTexture(graphic, { width: 128, height: 128 });

  if (!texture.value) return null;

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Image image={texture} x={0} y={0} width={128} height={128}>
        <Blur blur={10} />
      </Image>
    </Canvas>
  );
};

Platform Considerations

Web Platform

On web, textures are automatically converted to non-texture images for compatibility:
// Handled automatically by React Native Skia
if (Platform.OS === "web") {
  texture.value = texture.value.makeNonTextureImage();
}

Memory Management

Textures consume GPU memory. Clean up when no longer needed:
import { useEffect } from "react";

const TextureCleanupDemo = () => {
  const texture = useTexture(myElement, { width: 256, height: 256 });

  useEffect(() => {
    return () => {
      // Texture is automatically cleaned up
    };
  }, []);

  // ...
};

Common Patterns

Texture Atlas

Combine multiple graphics into a single texture:
const AtlasDemo = () => {
  const atlas = useTexture(
    <Group>
      <Circle cx={32} cy={32} r={20} color="red" />
      <Circle cx={96} cy={32} r={20} color="green" />
      <Circle cx={32} cy={96} r={20} color="blue" />
      <Circle cx={96} cy={96} r={20} color="yellow" />
    </Group>,
    { width: 128, height: 128 }
  );

  if (!atlas.value) return null;

  return (
    <Canvas style={{ flex: 1 }}>
      {/* Use different parts of the atlas */}
      <Image 
        image={atlas} 
        x={0} 
        y={0} 
        width={64} 
        height={64}
        rect={{ x: 0, y: 0, width: 64, height: 64 }}
      />
    </Canvas>
  );
};

Cached Rendering

Cache expensive rendering operations:
const CachedChartDemo = () => {
  const [data, setData] = useState([...]);

  const chartTexture = useTexture(
    <LineChart data={data} />,
    { width: 300, height: 200 },
    [data] // Only re-render when data changes
  );

  if (!chartTexture.value) return null;

  return (
    <Canvas style={{ width: 300, height: 200 }}>
      <Image image={chartTexture} x={0} y={0} width={300} height={200} />
    </Canvas>
  );
};

Limitations

  • Textures are rendered at a fixed size
  • Re-rendering requires re-creating the texture
  • GPU memory usage increases with texture count
  • Not suitable for frequently changing content

Best Practices

  • Use textures for complex, static graphics
  • Reuse textures when rendering the same graphic multiple times
  • Specify dependencies to control when textures update
  • Keep texture sizes reasonable to manage memory
  • Prefer direct rendering for simple shapes
  • Clean up textures when components unmount

Build docs developers (and LLMs) love