Skip to content
React Native Skia ガイドブック
Esc
navigateopen⌘Jpreview
On this page

Canvasの基本要素

`<Canvas>`が持つprops、サイズの取得方法、そして子要素をまとめて扱う`<Group>`によるスタイル継承・変形・クリッピングを解説します。

Canvasの座標系、Groupの変形、クリッピング、描画順序を順に示した図

座標を決め、Groupをまとめて変形し、必要な範囲だけを見せ、最後に描画順で重なりを作ります。

前の章では最小構成の<Canvas>を動かしましたが、<Canvas>自身が持つpropsにはまだ触れていませんでした。この章では<Canvas>本体の機能と、複数の図形をまとめて扱うための<Group>を見ていきます。

Canvasのprops

<Canvas>は通常のViewと同じ感覚で扱えますが、Skia特有のpropsもいくつか持っています。

Name Type Description
style? ViewStyle Viewのスタイル
ref? Ref<SkiaView> SkiaViewオブジェクトへの参照
onSize? SharedValue<Size> Canvasのサイズが変わるたびに更新される共有値(Shared Value)
highBitDepth? boolean 8bitより高い精度のサーフェスに描画する
androidWarmup? boolean Androidのコンポジターに最初のフレームを直接描画する

Canvasのサイズを取得する

Canvasのサイズはレイアウト完了後でないと確定しません。UIスレッド・JSスレッドそれぞれで取得する方法が用意されています。

UIスレッドで取得する: onSize

onSizeにReanimatedの共有値を渡すと、Canvasのサイズが変わるたびにその値が更新されます。UIスレッド上で完結するため、リサイズに合わせて図形を追従させたい場合に向いています。

import { useDerivedValue, useSharedValue } from "react-native-reanimated";
import { Canvas, Rect } from "@shopify/react-native-skia";

const SizeDemo = () => {
  const size = useSharedValue({ width: 0, height: 0 });
  const rect = useDerivedValue(() => {
    const { width, height } = size.value;
    return { x: 0, y: 0, width, height };
  });
  return (
    <Canvas style={{ flex: 1 }} onSize={size}>
      <Rect color="cyan" rect={rect} />
    </Canvas>
  );
};

JSスレッドで取得する: useCanvasSize

JSスレッド側でサイズが欲しい場合はuseCanvasSizeフックが使えます。refmeasure()を組み合わせてuseLayoutEffectで取得する定番パターンを、フック1つにまとめたものです。

import { Canvas, Rect, useCanvasSize } from "@shopify/react-native-skia";

const SizeDemo = () => {
  const {
    ref,
    size: { width, height },
  } = useCanvasSize();
  return (
    <Canvas style={{ flex: 1 }} ref={ref}>
      <Rect color="cyan" rect={{ x: 0, y: 0, width, height }} />
    </Canvas>
  );
};

高ビット深度で描画する

デフォルトのCanvasは8bit(チャンネルあたり256階調)のサーフェスに描画します。グラデーションのように色が滑らかに変化する表現では、この階調不足がバンディング(縞模様)として見えてしまうことがあります。

highBitDepthを有効にすると、iOSでは16bit浮動小数点、Androidでは10bitのサーフェスに描画されます。色の値そのものは変わらず、精度だけが上がる点に注意してください。HDR対応ではなく、あくまでビット深度の話です。

<Canvas style={{ flex: 1 }} opaque highBitDepth>
  {/* グラデーションなど、階調の滑らかさが重要な描画 */}
</Canvas>

AndroidではhighBitDepthはGraphiteバックエンドを必要とし、デフォルトのOpenGLバックエンドでは8bitにフォールバックします。

描画結果をスナップショットとして保存する

makeImageSnapshot(同期)またはmakeImageSnapshotAsync(非同期)を使うと、Canvasの描画結果をImageとして取得できます。取得したImageencodeToBytes()でバイト列に変換し、ファイル保存や共有に使えます。

import { Canvas, Circle, useCanvasRef } from "@shopify/react-native-skia";

const SnapshotDemo = () => {
  const ref = useCanvasRef();
  const handleSnapshot = () => {
    const image = ref.current?.makeImageSnapshot();
    const bytes = image?.encodeToBytes();
    // bytesをファイル保存や共有に利用する
  };
  return (
    <Canvas style={{ flex: 1 }} ref={ref}>
      <Circle r={128} cx={128} cy={128} color="red" />
    </Canvas>
  );
};

テクスチャを含む描画を確実に取得したい場合は非同期のmakeImageSnapshotAsyncを使ってください。UIスレッド上で実行され、画面表示中のCanvasと同じSkiaコンテキストにアクセスできます。

以下のデモでボタンを押すと、右側にその瞬間のCanvasのスナップショット(<Image>として表示)が撮影されます。

Skiaを読み込み中…

以下のデモではmethodを切り替えてonSize(UIスレッド)とuseCanvasSize(JSスレッド)の両方を試せます。widthのスライダーでCanvasの幅を変えると、どちらの方法でも矩形が追従することが確認できます。

Skiaを読み込み中…

Group: 子要素をまとめて扱う

<Group>は複数の図形をまとめて扱うための基本的な構成要素です。深くネストでき、子要素に対して次の4つの操作をまとめて適用できます。

  • Paintのプロパティ(色・スタイルなど)
  • 変形(transform)
  • クリッピング(clip)
  • レイヤー効果(layer)

Paintの継承

<Group>に指定した色やスタイルは、子孫要素すべてに継承されます。個々の図形に同じpropsを繰り返し書かずに済みます。

import { Canvas, Circle, Group } from "@shopify/react-native-skia";

const PaintGroupDemo = () => {
  const r = 100;
  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={r} cy={r} r={r} color="#51afed" />
      {/* Group配下は色・スタイルが継承される */}
      <Group color="#f5c451" style="stroke" strokeWidth={10}>
        <Circle cx={r} cy={r} r={r / 2} />
        <Circle cx={r} cy={r} r={r / 3} color="white" />
      </Group>
    </Canvas>
  );
};

Skiaを読み込み中…

色以外にも、<LinearGradient>のようなシェーダーやフィルターも子として渡せば同様に継承されます。Paintまわりの詳細は次の章(Paintを理解する)で扱います。

変形(Transformations)

transformpropはReact Native本体の同名プロパティとほぼ同じ書き方ですが、2点異なります。変形の原点が要素の左上(RN Viewでは中心)であること、回転はラジアン指定であることです。

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

const SkewDemo = () => {
  return (
    <Canvas style={{ flex: 1 }}>
      <Group color="lightblue" transform={[{ skewX: Math.PI / 6 }]}>
        <RoundedRect x={64} y={64} width={128} height={128} r={10} />
      </Group>
    </Canvas>
  );
};

原点を変えたいときはoriginpropを使います。originは子要素には継承されません。

クリッピング(Clipping)

clippropに矩形・角丸矩形・パスを渡すと、その範囲だけが表示されます。invertClipを付けると内外の表示が反転します。

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

const star = Skia.Path.MakeFromSVGString(
  "M 128 0 L 168 80 L 256 93 L 192 155 L 207 244 L 128 202 L 49 244 L 64 155 L 0 93 L 88 80 L 128 0 Z"
)!;

const ClipDemo = () => {
  return (
    <Canvas style={{ flex: 1 }}>
      <Group clip={star}>
        <Rect x={0} y={0} width={256} height={256} color="#51afed" />
        <Circle cx={128} cy={128} r={80} color="#f5c451" />
      </Group>
    </Canvas>
  );
};

矩形の代わりにrrect()で作った角丸矩形を渡せば同じ要領で角丸クリップになります。パスの作り方は多角形とパスの章で扱います。

レイヤー効果(layer)

layerpropに<Paint>を渡すと、子要素はいったんビットマップとして描画されてからそのPaintの効果が適用されます。個々の図形にではなく、グループ全体にまとめてぼかしや色変換をかけたいときに使います。

import { Blur, Canvas, Circle, ColorMatrix, Group, Paint } from "@shopify/react-native-skia";

const GooDemo = () => {
  return (
    <Canvas style={{ flex: 1 }}>
      <Group
        color="lightblue"
        layer={
          <Paint>
            <Blur blur={20} />
            <ColorMatrix
              matrix={[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 18, -7]}
            />
          </Paint>
        }
      >
        <Circle cx={0} cy={128} r={128 * 0.95} />
        <Circle cx={256} cy={128} r={128 * 0.95} />
      </Group>
    </Canvas>
  );
};

ぼかした円をビットマップ化したうえでColorMatrixのアルファ強調をかけているため、2つの円が滑らかに融合した「メタボール」のような見た目になります。個別に<Blur>をかけただけでは、円が重なった部分に境界線が残りこの効果は得られません。

描画順序(zIndex)

<Group>の子要素は基本的に書かれた順に描画されますが、zIndexpropを指定すると順序を上書きできます。値が大きいほど後(上)に描画され、デフォルトは0、負の値も使えます。zIndexはその<Group>内の兄弟要素にのみ影響するスコープを持ちます。

import { BlurMask, Canvas, Circle, Group } from "@shopify/react-native-skia";

const ZIndexDemo = () => {
  const r = 80;
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Group>
        <BlurMask style="solid" blur={10} />
        <Circle cx={r} cy={r} r={r} color="cyan" zIndex={2} />
        <Circle cx={256 - r} cy={r} r={r} color="magenta" zIndex={1} />
        <Circle cx={128} cy={256 - r} r={r} color="yellow" zIndex={0} />
      </Group>
    </Canvas>
  );
};

この例ではシアンの円(zIndex={2})が最前面、マゼンタ(zIndex={1})、イエロー(zIndex={0})の順に重なります。

FitBox: 描画を自動的にスケールする

<FitBox><Group>をベースにした特殊なコンポーネントで、元のサイズの矩形(src)を目的の矩形(dst)にフィットするよう自動でスケールしてくれます。SVGからエクスポートしたパスなど、元のサイズが分かっている描画をCanvasのサイズに合わせたいときに便利です。

<FitBox src={rect(0, 0, 664, 308)} dst={rect(0, 0, 256, 256)}>
  <Path path={svgPath} style="stroke" strokeWidth={30} />
</FitBox>

fitpropでcontain(デフォルト)、fillcoverなどのフィット方法を選べます。詳細は公式ドキュメントのGroupを参照してください。

以下のデモでfeatureを切り替えると、ここまで見てきたtransformcliplayerzIndexFitBoxをそれぞれ確認できます。

Skiaを読み込み中…