クロスプラットフォームの描画戦略
同じコードでiOSとAndroid両対応、を実現する仕組みは実は三者三様。React Native・Flutter・PWAの描画原理を押さえれば性能劣化の原因を切り分けられる。
- 1.React Nativeはネイティブのビュー階層をJSブリッジ経由で合成する方式。見た目とプラットフォーム挙動は本物のネイティブUIそのもの。
- 2.FlutterはSkia/Impellerという自前の描画エンジンで画面全体をピクセル単位に描く方式。ネイティブウィジェットを一切使わず一貫した見た目を保証する。
- 3.PWAはブラウザエンジンのWebViewでHTML/CSSを描画する方式。実装コストは最小だがネイティブAPIアクセスと性能に制約が残る。
「同じコードで両OS対応」の中身は一様ではない
クロスプラットフォーム開発と一括りにされがちですが、画面をどう描くかという一点だけを見ても、React Native・Flutter・PWAは根本的に異なる設計を選んでいます。違いは見た目の些細な差ではなく、誰がレイアウト計算をするか、誰がピクセルを塗るか、OSのアクセシビリティ機構とどう繋がるかという、アーキテクチャ上の分岐点です。この分岐を理解すると、なぜあるアプリはスクロールが滑らかで別のアプリはカクつくのか、なぜあるアプリはOSのアップデート直後にネイティブの見た目だけ変わるのか、といった現象の原因を切り分けられるようになります。
React Native:ネイティブビューを組み立てて委任する
React Nativeの本質は「JavaScriptで書いた宣言を、実際のネイティブUIコンポーネントの生成・配置命令に変換する」ことです。開発者が書くのはReactのコンポーネントツリーですが、画面に最終的に現れるのはiOSならUIView系、AndroidならView系の本物のネイティブオブジェクトです。描画そのもの(ラスタライズ)はOS標準のレンダリングパイプラインが担当し、React Native側はそれを組み立てて指示するだけです。
処理の流れは擬似コードで整理するとこうなります。
1. JS側: コンポーネントツリーを差分計算(reconciliation)
2. JS側: 「ViewをX,Yに配置、色はC」などの命令列を生成
3. ブリッジ/JSIを通じて命令をネイティブ側へ転送
4. ネイティブ側: 実際のUIView/Viewインスタンスを生成・更新
5. OS標準のレンダラーがそのネイティブビュー階層をラスタライズ
旧アーキテクチャでは3の転送が非同期・シリアライズ必須の「ブリッジ」でボトルネックになりやすく、リストの高速スクロール中などにJSスレッドの遅延がそのままフレーム落ちに直結しました。新アーキテクチャ(Fabric、JSI)はこの橋渡しを同期呼び出し可能な直接参照に置き換え、レイテンシーを縮めています。ただし原理としては今も「JS側が指示し、ネイティブ側が実体を持つ」という委任構造は変わりません。
最終的な描画がOS標準コンポーネントなので、テキスト入力のIME挙動、スクロールの慣性、アクセシビリティ読み上げなど、OSが長年チューニングしてきた挙動をそのまま継承できます。半面、iOSとAndroidでネイティブコンポーネントの挙動差が出れば、それはそのままアプリの見た目・挙動差として表面化します。「一度書けばどこでも同じ」ではなく「一度書けばOSごとに正しく振る舞う」が正確な理解です。
Flutter:エンジンが画面全体を自分で描く
FlutterはReact Nativeと正反対の設計判断をしています。ネイティブのUIコンポーネントを一切介さず、Skia(近年はImpellerへ移行中)という自前の2D描画エンジンが、画面全体を単一のCanvas上にピクセル単位で描画します。OSが提供するのは、GPUへの描画命令を通すためのまっさらな描画サーフェス(iOSのMetal、AndroidのSurface相当)だけで、ボタン一つ、テキスト一文字の形状までFlutterのウィジェットツリーとレンダリングパイプラインが決定します。
1. Widgetツリー(構成の宣言)
↓ ビルド
2. Elementツリー(Widgetのインスタンス、状態を保持)
↓
3. RenderObjectツリー(レイアウト計算・ペイントを担当)
↓ レイアウト+ペイント
4. Layerツリー(GPU向けの描画コマンド列)
↓ ラスタライズ
5. Skia/Impellerエンジンがピクセルを生成しGPUへ
この方式の最大の利点は、OSやOSバージョンによる描画結果のばらつきが原理的に発生しないことです。iOS版とAndroid版でボタンの角丸半径やシャドウの落ち方まで完全に一致させられます。デザインシステムを厳密に統一したいアプリに向く一方で、OS標準のUIコンポーネントに自動追随する恩恵(OSがアクセシビリティ機構やIMEを改良しても自動では反映されない)は失われ、Flutter側が個別に追いつく必要があります。
| 観点 | React Native | Flutter | PWA |
|---|---|---|---|
| 描画の実体 | ネイティブUIコンポーネント(OS委任) | 自前エンジンによる自己描画(Skia/Impeller) | ブラウザエンジンのレンダリングツリー |
| プラットフォーム一貫性 | OSごとに差が出やすい(ネイティブ準拠) | OS間で完全に同一の見た目 | ブラウザ実装差に依存 |
| 性能特性 | ブリッジ越しの命令コストが残るが描画自体はネイティブ級 | JS橋渡しがなく滑らかだがGPU/CPU負荷は自前分担 | DOM/CSSレイアウト計算のオーバーヘッドが大きい |
| ネイティブAPIアクセス | ブリッジ経由でほぼフルアクセス | プラグイン経由でほぼフルアクセス | ブラウザが公開するWeb APIの範囲に限定 |
| アクセシビリティ継承 | OS標準機構をそのまま利用 | 独自実装で追随が必要 | ブラウザのアクセシビリティツリーを利用 |
PWA:WebViewというもう一つの委任先
PWA(Progressive Web App)はReact Nativeと同じ「委任」型ですが、委任先がネイティブUIコンポーネントではなくブラウザエンジンである点が異なります。HTML/CSSで記述した内容を、iOSならWebKit系、AndroidならBlink系のレンダリングエンジンがDOMツリーからレイアウトツリーを構築し、ラスタライズします。
1. HTML/CSSをパースしDOMツリー・CSSOMツリーを構築
2. 両者を合成しレンダーツリーを構築
3. レイアウト計算(リフロー)で各要素の位置・サイズを確定
4. ペイントでピクセルを生成
5. コンポジット(層合成)でGPUに転送し画面表示
利点は実装コストの低さと配布の容易さ(アプリストア審査が不要、URLだけで到達可能)ですが、原理的な制約が二つあります。第一に、DOM/CSSのレイアウト計算は汎用文書表示のために設計されており、React Nativeのネイティブレイアウトや Flutter のRenderObjectツリーに比べて複雑な動的レイアウトでの再計算コストが高くなりがちです。第二に、Bluetooth・バックグラウンド常駐・高度なセンサーアクセスなど、ブラウザがWeb APIとして公開していない機能には原理的に到達できません。
PWAとひとくちに言っても、AndroidのChrome Custom Tabs・WebViewとiOSのWKWebViewではJavaScriptエンジンの最適化レベルやAPI対応状況が異なります。特にiOSではApp Store外のPWAで使えるWeb APIがSafari本体より制限される場合があり、「ブラウザで動くから両OSで同一」という前提は成立しません。
三方式をどう選ぶか
三者の違いを一段抽象化すると、「誰が最終的な画素の決定権を持つか」という一点に集約されます。React Nativeは決定権をOSのネイティブレンダラーに委ね、Flutterは自前のエンジンが決定権を握り、PWAは決定権をブラウザエンジンに委ねます。決定権を手放すほどプラットフォームとの一貫性(OSアップデートへの自動追随、アクセシビリティの継承)は上がりますが、見た目の完全な統一は犠牲になります。逆に自前で描くほど見た目は統一できますが、OS側の改善に自動で追随できず、実装側が追いかける負債を背負います。
出題・面接で問われやすいのは「性能の優劣」ではなく「描画の主体がどこにあるか」です。React Native=ネイティブビュー合成(OS委任)、Flutter=自前エンジンによる全画面自己描画、PWA=ブラウザエンジンへの委任、という三分類と、それぞれがトレードオフにするもの(プラットフォーム一貫性 対 見た目の統一 対 実装コスト)をセットで説明できるようにしておきます。
まとめ
クロスプラットフォーム戦略の違いは「同じコードから両OS向けバイナリを作る」という結果の同一性の裏で、描画の主体をどこに置くかという設計思想が三者三様に分かれていることに由来します。React Nativeはネイティブコンポーネントへの委任でOSの成熟した挙動を継承し、Flutterは自前エンジンでピクセル単位の一貫性を獲得し、PWAはブラウザエンジンへの委任で配布と実装の手軽さを取ります。どれが優れているかではなく、プラットフォーム一貫性・性能特性・ネイティブAPIアクセスのどれを優先するプロダクトかによって、選ぶべき描画戦略は変わります。低レイヤーのグラフィックスパイプラインそのものについては /graphics/ と合わせて理解すると、GPUに至るまでの全体像がより立体的になります。
モバイル開発 Article
クロスプラットフォームの描画戦略を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
モバイル開発
比較で見る軸
難易度: advanced / カテゴリ: モバイル開発 / タグ数: 6
導入後に効く点
FlutterはSkia/Impellerという自前の描画エンジンで画面全体をピクセル単位に描く方式。ネイティブウィジェットを一切使わず一貫した見た目を保証する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- モバイル開発
- タグ数
- 6
判断チェックリスト
- 自社の用途が「モバイル開発 / React Native」に近いか確認する。
- 強みである「React Nativeはネイティブのビュー階層をJSブリッジ経由で合成する方式。見た目とプラットフォーム挙動は本物のネイティブUIそのもの。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。