React Native Skia для графики

03 ноября 2026
Автор

Антон Ларичев

Что такое React Native Skia

React Native Skia — это высокопроизводительный графический движок для React Native, основанный на библиотеке Skia от Google. Именно Skia отвечает за отрисовку интерфейса в Chrome, Android и Flutter. Официальный пакет @shopify/react-native-skia предоставляет декларативный React-API поверх этого движка.

Ключевые преимущества библиотеки:

  • Рендеринг выполняется на GPU, что исключает «дрожание» анимаций
  • Полная поддержка react-native-reanimated v3 — анимационные значения напрямую передаются в шейдеры
  • Единый API для iOS и Android без нативных бриджей в горячем пути
  • Возможность рисовать произвольные пути, градиенты, шрифты, изображения и эффекты

Типичные сценарии использования: кастомные графики, анимированные иллюстрации, игровые элементы, аватары с масками и сложные переходы между экранами.

Курс по теме

Пройти курс React и Redux Toolkit

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Установка и настройка

npx expo install @shopify/react-native-skia

Для bare React Native проекта потребуется дополнительная нативная сборка:

npm install @shopify/react-native-skia
cd ios && pod install

Esm-импорты и TypeScript-типы поставляются вместе с пакетом — дополнительно устанавливать @types/... не нужно.

Проверьте минимальные версии: React Native >= 0.71, iOS >= 13, Android API >= 21.

Основные примитивы

Все элементы Skia располагаются внутри компонента <Canvas>, который задаёт область отрисовки.

import { Canvas, Circle, Rect, Line, vec } from '@shopify/react-native-skia';

export function BasicShapes() {
  return (
    <Canvas style={{ width: 300, height: 300 }}>
      {/* Прямоугольник */}
      <Rect x={20} y={20} width={100} height={60} color="#6C63FF" />

      {/* Круг */}
      <Circle cx={200} cy={50} r={40} color="#FF6584" />

      {/* Линия */}
      <Line
        p1={vec(20, 150)}
        p2={vec(280, 150)}
        color="#43B89C"
        strokeWidth={2}
      />
    </Canvas>
  );
}

Каждый примитив принимает стандартные пропсы позиционирования и цвет. Цвета можно задавать строкой ("#hex", "rgba(...)") или через хелпер Skia.Color.

Скруглённые прямоугольники

import { Canvas, RoundedRect } from '@shopify/react-native-skia';

export function Card() {
  return (
    <Canvas style={{ width: 300, height: 160 }}>
      <RoundedRect
        x={16}
        y={16}
        width={268}
        height={128}
        r={16}
        color="#1E1E2E"
      />
    </Canvas>
  );
}

Работа с путями (Path)

Path — самый мощный примитив: он позволяет рисовать любые векторные фигуры, кривые Безье и сложные контуры.

import { Canvas, Path } from '@shopify/react-native-skia';

export function ArrowShape() {
  const path = 'M 10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80';

  return (
    <Canvas style={{ width: 200, height: 160 }}>
      <Path
        path={path}
        color="transparent"
        style="stroke"
        strokeWidth={3}
        strokeCap="round"
        color="#6C63FF"
      />
    </Canvas>
  );
}

Путь задаётся SVG-строкой — это удобно при экспорте иконок из Figma или Illustrator.

Для программного построения пути используйте Skia.Path.Make():

import { Canvas, Path, Skia } from '@shopify/react-native-skia';

export function StarShape() {
  const path = Skia.Path.Make();
  path.moveTo(100, 10);
  path.lineTo(120, 70);
  path.lineTo(190, 70);
  path.lineTo(135, 110);
  path.lineTo(160, 170);
  path.lineTo(100, 130);
  path.lineTo(40, 170);
  path.lineTo(65, 110);
  path.lineTo(10, 70);
  path.lineTo(80, 70);
  path.close();

  return (
    <Canvas style={{ width: 200, height: 200 }}>
      <Path path={path} color="#FFD700" />
    </Canvas>
  );
}

Градиенты и заливки

Skia поддерживает линейные, радиальные и конические градиенты. Они применяются как дочерние элементы любого примитива через концепцию «шейдера заливки».

import {
  Canvas,
  Rect,
  LinearGradient,
  RadialGradient,
  vec,
} from '@shopify/react-native-skia';

export function GradientShowcase() {
  return (
    <Canvas style={{ width: 300, height: 240 }}>
      {/* Линейный градиент */}
      <Rect x={16} y={16} width={268} height={90}>
        <LinearGradient
          start={vec(16, 16)}
          end={vec(284, 106)}
          colors={['#6C63FF', '#FF6584']}
        />
      </Rect>

      {/* Радиальный градиент */}
      <Rect x={16} y={130} width={268} height={90}>
        <RadialGradient
          c={vec(150, 175)}
          r={134}
          colors={['#43B89C', '#1E1E2E']}
        />
      </Rect>
    </Canvas>
  );
}

Градиент живёт строго внутри родительской фигуры — он не рендерится сам по себе.

Текст и шрифты

Для работы с текстом нужно загрузить шрифт через хук useFont.

import { Canvas, Text, useFont } from '@shopify/react-native-skia';

export function SkiaText() {
  // Путь к шрифту в ассетах проекта
  const font = useFont(require('./assets/fonts/Inter-Bold.ttf'), 24);

  if (!font) {
    return null;
  }

  return (
    <Canvas style={{ width: 300, height: 80 }}>
      <Text
        x={20}
        y={50}
        text="React Native Skia"
        font={font}
        color="#FFFFFF"
      />
    </Canvas>
  );
}

Для многострочного текста используйте Paragraph — он поддерживает выравнивание, переносы и смешанные стили.

Анимация с Reanimated

Skia нативно интегрируется с react-native-reanimated. Анимированные значения передаются напрямую в пропсы — без перерисовки React-дерева.

import { useEffect } from 'react';
import { Canvas, Circle } from '@shopify/react-native-skia';
import {
  useSharedValue,
  withRepeat,
  withTiming,
  Easing,
} from 'react-native-reanimated';

export function PulsingCircle() {
  const radius = useSharedValue(40);
  const opacity = useSharedValue(1);

  useEffect(() => {
    radius.value = withRepeat(
      withTiming(80, { duration: 1000, easing: Easing.out(Easing.ease) }),
      -1,
      true
    );
    opacity.value = withRepeat(
      withTiming(0.2, { duration: 1000, easing: Easing.out(Easing.ease) }),
      -1,
      true
    );
  }, []);

  return (
    <Canvas style={{ width: 200, height: 200 }}>
      <Circle cx={100} cy={100} r={radius} color="#6C63FF" opacity={opacity} />
    </Canvas>
  );
}

Анимация выполняется полностью в UI-потоке — React JS-поток не задействован на каждом кадре.

Анимированный путь

Один из самых эффектных сценариев — анимация end у пути, создающая эффект «рисования».

import { useEffect } from 'react';
import { Canvas, Path } from '@shopify/react-native-skia';
import { useSharedValue, withTiming } from 'react-native-reanimated';

export function DrawingPath() {
  const progress = useSharedValue(0);

  useEffect(() => {
    progress.value = withTiming(1, { duration: 2000 });
  }, []);

  return (
    <Canvas style={{ width: 300, height: 200 }}>
      <Path
        path="M 20 100 Q 150 20 280 100"
        color="#6C63FF"
        style="stroke"
        strokeWidth={4}
        strokeCap="round"
        start={0}
        end={progress}
      />
    </Canvas>
  );
}

Практический пример: мини-график

Соберём компонент графика-спарклайна, типичный для финансовых приложений.

import { useMemo } from 'react';
import { Canvas, Path, Circle, Skia, LinearGradient, vec } from '@shopify/react-native-skia';

interface SparklineProps {
  data: number[];
  width?: number;
  height?: number;
  color?: string;
}

export function Sparkline({
  data,
  width = 200,
  height = 80,
  color = '#6C63FF',
}: SparklineProps) {
  const { linePath, areaPath, lastPoint } = useMemo(() => {
    if (data.length < 2) return { linePath: null, areaPath: null, lastPoint: null };

    const paddingX = 8;
    const paddingY = 8;
    const minVal = Math.min(...data);
    const maxVal = Math.max(...data);
    const range = maxVal - minVal || 1;

    const toX = (i: number) =>
      paddingX + (i / (data.length - 1)) * (width - paddingX * 2);
    const toY = (v: number) =>
      height - paddingY - ((v - minVal) / range) * (height - paddingY * 2);

    const line = Skia.Path.Make();
    const area = Skia.Path.Make();

    line.moveTo(toX(0), toY(data[0]));
    area.moveTo(toX(0), height);
    area.lineTo(toX(0), toY(data[0]));

    for (let i = 1; i < data.length; i++) {
      const x = toX(i);
      const y = toY(data[i]);
      const prevX = toX(i - 1);
      const prevY = toY(data[i - 1]);
      const cpX = (prevX + x) / 2;

      line.cubicTo(cpX, prevY, cpX, y, x, y);
      area.cubicTo(cpX, prevY, cpX, y, x, y);
    }

    const lastIdx = data.length - 1;
    area.lineTo(toX(lastIdx), height);
    area.close();

    return {
      linePath: line,
      areaPath: area,
      lastPoint: { x: toX(lastIdx), y: toY(data[lastIdx]) },
    };
  }, [data, width, height]);

  if (!linePath || !lastPoint) return null;

  return (
    <Canvas style={{ width, height }}>
      {/* Заливка под линией */}
      <Path path={areaPath} opacity={0.15} color={color}>
        <LinearGradient
          start={vec(0, 0)}
          end={vec(0, height)}
          colors={[color, 'transparent']}
        />
      </Path>

      {/* Линия графика */}
      <Path
        path={linePath}
        style="stroke"
        strokeWidth={2}
        strokeCap="round"
        strokeJoin="round"
        color={color}
      />

      {/* Последняя точка */}
      <Circle cx={lastPoint.x} cy={lastPoint.y} r={4} color={color} />
      <Circle cx={lastPoint.x} cy={lastPoint.y} r={2} color="#FFFFFF" />
    </Canvas>
  );
}

Использование компонента:

export function Dashboard() {
  const priceHistory = [120, 132, 118, 145, 139, 158, 171, 163, 180, 175, 192];

  return (
    <Sparkline
      data={priceHistory}
      width={280}
      height={100}
      color="#43B89C"
    />
  );
}

Изображения и маски

Skia умеет применять маски к изображениям — удобно для аватаров с кастомными формами.

import { Canvas, Image, useImage, Circle, mask } from '@shopify/react-native-skia';

export function MaskedAvatar() {
  const image = useImage(require('./assets/avatar.jpg'));

  if (!image) return null;

  return (
    <Canvas style={{ width: 100, height: 100 }}>
      <mask>
        <Circle cx={50} cy={50} r={48} color="white" />
      </mask>
      <Image
        image={image}
        x={0}
        y={0}
        width={100}
        height={100}
        fit="cover"
      />
    </Canvas>
  );
}

Производительность и ограничения

Несколько практических правил для высокой производительности:

Мемоизируйте объекты Skia. Пути, краски и шрифты, созданные без useMemo, будут пересоздаваться на каждом рендере.

const path = useMemo(() => {
  const p = Skia.Path.Make();
  // построение пути...
  return p;
}, [data]);

Не вкладывайте <Canvas> друг в друга. Каждый Canvas — отдельный GPU-контекст. Располагайте все элементы одной сцены в одном Canvas.

Используйте Group для трансформаций. Применять матрицу к группе элементов дешевле, чем считать координаты каждого по отдельности.

import { Canvas, Group, Rect } from '@shopify/react-native-skia';

<Canvas style={{ width: 200, height: 200 }}>
  <Group transform={[{ rotate: Math.PI / 4 }, { translateX: 50 }]}>
    <Rect x={0} y={0} width={60} height={60} color="#6C63FF" />
    <Rect x={70} y={0} width={60} height={60} color="#FF6584" />
  </Group>
</Canvas>

Избегайте opacity на группах с перекрывающимися фигурами. Skia рендерит группу в off-screen буфер — это дополнительный проход по GPU.

Итог

React Native Skia даёт инструменты для создания полноценных графических интерфейсов, недоступных средствами стандартных нативных компонентов. Библиотека особенно ценна там, где важна плавность: финансовые графики, игровые элементы, сложные анимированные иллюстрации.

Основные концепции, которые нужно освоить: примитивы (Rect, Circle, Path), заливки (LinearGradient, RadialGradient), интеграция с Reanimated через useSharedValue и мемоизация Skia-объектов.

Для более глубокого погружения в разработку React Native приложений, включая анимации и работу с нативными возможностями, смотрите курс на PurpleSchool:

Курс по React Native на PurpleSchool

Стрелочка влевоReact Three Fiber — 3D-графика в ReactReact key — как React использует ключи для согласованияСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

React тестирование с Vitest
Zustand: продвинутые паттерны, persist middleware и slicesUncontrolled Components: когда DOM управляет даннымиRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиБезопасность в React: защита от XSS, CSRF и утечек данныхReact с WebSocket — реалтайм в компонентахReact Native New Architecture: Fabric и JSIПрофилирование React: как найти и устранить узкие местаReact Native приложение с нуля до публикации в сторахЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыОшибки гидратации в React и способы их устраненияHOC в React: мастерство композиции компонентовАрхитектура больших React приложений: Feature-Sliced DesignuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactYup - валидация схемZod - валидация с TypeScriptXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIМикрофронтенды с React (micro-frontends)MobX — реактивное управление состоянием в ReactЗагрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
Valtio: атомарный state management в ReactuseState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19React Stale Closures — устаревшие замыкания в хукахОптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияflushSync — синхронные обновления состояния в ReactuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий