自分の作ったアプリを初めて実機で起動したとき、ロゴが出るより先に白い画面が一瞬だけよぎるのが気になって、その0.5秒をどう埋めるかに半日を費やしたことがあります。スプラッシュスクリーンは、ユーザーがアプリと最初に交わす視線です。
アプリを起動したとき、最初の0.5〜2秒の間に表示される画面を**スプラッシュスクリーン(Splash Screen)**といいます。iOS ではローンチスクリーンとも呼ばれ、アプリが初期化を完了するまでの間、ユーザーに表示されます。
デフォルトのままにしておくと白い画面や無機質な画面が表示されますが、ここを丁寧に整えるだけで、アプリ全体の印象が大きく変わります。スプラッシュスクリーンは、フォントの読み込みや認証チェックといった起動直後の準備を済ませる絶好のタイミングでもあります。Rork で生成した Expo アプリのスプラッシュ画面を、app.json の基本設定からフェードアウトの演出まで、順を追って整えていきましょう。
expo-splash-screen とは
Expo が公式に提供しているライブラリ expo-splash-screen を使うと、JavaScript の初期化が完了するまでネイティブのスプラッシュスクリーンを表示し続けることができます。
主なメリットは以下の通りです。
- 白い画面のちらつき防止: デフォルトでは JS バンドルのロード中に白い画面が一瞬表示されますが、
expo-splash-screenがこれを防ぎます - 非同期処理の完了を待てる: フォントの読み込みや認証トークンの確認など、アプリに必要な準備が整うまでスプラッシュ画面を保持できます
- iOS・Android・Web に対応: 1つの設定で全プラットフォームに対応できます
app.json でスプラッシュ画像と背景色を設定する
まず、プロジェクトルートの app.json(または app.config.js)でスプラッシュの基本設定を行います。
{
"expo": {
"name": "MyApp",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#FFFFFF"
},
"ios": {
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#FFFFFF",
"tabletImage": "./assets/splash-tablet.png"
}
},
"android": {
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#FFFFFF",
"mdpi": "./assets/splash-mdpi.png",
"hdpi": "./assets/splash-hdpi.png",
"xhdpi": "./assets/splash-xhdpi.png",
"xxhdpi": "./assets/splash-xxhdpi.png",
"xxxhdpi": "./assets/splash-xxxhdpi.png"
}
}
}
}設定のポイントをまとめます。
image: スプラッシュ画面に表示する画像。PNG 形式で、サイズは 1242×2688px 以上を推奨します(高解像度 iPhone に対応するため)resizeMode:"contain"(画像全体を収める)または"cover"(画面いっぱいに表示)を選びます。ロゴならcontain、フルスクリーンの背景画像ならcoverが適していますbackgroundColor: 画像の余白部分や画像が読み込まれる前に表示される背景色。ロゴの背景色と統一しましょう
expo-splash-screen を使って非同期処理を待つ
app.json の設定だけでもスプラッシュは表示されますが、アプリが起動直後に必要な処理(フォントの読み込み、認証チェックなど)が終わる前にスプラッシュが消えてしまうと、ちらつきが発生します。expo-splash-screen を使って適切なタイミングで非表示にしましょう。
// app/_layout.tsx
import { useEffect, useState } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
import { View } from 'react-native';
// アプリの準備が整うまでスプラッシュスクリーンを非表示にしない
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// フォントの読み込みや初期データ取得など、必要な準備をここで行う
await Font.loadAsync({
'CustomFont-Regular': require('../assets/fonts/CustomFont-Regular.ttf'),
});
// 必要に応じてAPIリクエストや認証チェックも追加できる
// await checkAuthStatus();
// 意図的に少し待たせたい場合(例:アニメーションを見せたい)
// await new Promise(resolve => setTimeout(resolve, 1000));
} catch (e) {
// 準備中のエラーは警告として記録し、アプリは起動させる
console.warn('アプリ起動準備中にエラーが発生しました:', e);
} finally {
// 準備完了フラグをセット
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = async () => {
if (appIsReady) {
// ルートViewが描画されたタイミングでスプラッシュを非表示にする
await SplashScreen.hideAsync();
}
};
if (!appIsReady) {
// アプリの準備ができるまで何も描画しない(スプラッシュが表示し続ける)
return null;
}
return (
<View style={{ flex: 1 }} onLayout={onLayoutRootView}>
{/* ここにアプリのルートナビゲーションを配置 */}
</View>
);
}このコードの流れを整理すると次のようになります。
SplashScreen.preventAutoHideAsync()を呼び出し、自動的に非表示になるのを防ぐuseEffect内でフォント読み込みや認証チェックなどの準備処理を実行する- 準備が完了したら
setAppIsReady(true)を呼ぶ - ルートの
Viewが描画(onLayoutが発火)したタイミングでSplashScreen.hideAsync()を呼び、スプラッシュを非表示にする
カスタムアニメーションでスプラッシュを演出する
デフォルトの SplashScreen.hideAsync() はスパッと消えるだけですが、Animated API と組み合わせることで、フェードアウトや縮小アニメーションを付けられます。
import { useEffect, useRef, useState } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import { Animated, Image, StyleSheet, View } from 'react-native';
SplashScreen.preventAutoHideAsync();
export default function AnimatedSplash({ children }: { children: React.ReactNode }) {
const [isAppReady, setIsAppReady] = useState(false);
const [isSplashAnimationComplete, setIsSplashAnimationComplete] = useState(false);
const animation = useRef(new Animated.Value(1)).current; // 不透明度 1 からスタート
useEffect(() => {
async function prepare() {
// 必要な準備処理
await new Promise(resolve => setTimeout(resolve, 500));
setIsAppReady(true);
}
prepare();
}, []);
useEffect(() => {
if (isAppReady) {
// アプリ準備完了後、フェードアウトアニメーションを開始
Animated.timing(animation, {
toValue: 0, // 不透明度を 0 にする
duration: 500, // 0.5秒かけてフェードアウト
useNativeDriver: true,
}).start(async () => {
// アニメーション完了後にスプラッシュを非表示
await SplashScreen.hideAsync();
setIsSplashAnimationComplete(true);
});
}
}, [isAppReady]);
return (
<View style={{ flex: 1 }}>
{/* アプリ本体(スプラッシュの下に先に描画しておく)*/}
{isAppReady && children}
{/* カスタムスプラッシュオーバーレイ */}
{!isSplashAnimationComplete && (
<Animated.View
pointerEvents="none"
style={[StyleSheet.absoluteFill, { backgroundColor: '#FFFFFF', opacity: animation }]}
>
<Image
source={require('../assets/splash.png')}
style={{ flex: 1, resizeMode: 'contain' }}
/>
</Animated.View>
)}
</View>
);
}このパターンでは、スプラッシュ画面の下にアプリ本体を先に描画し始めることで、アニメーション完了後に素早くメイン画面が表示される効果があります。
Rork でのプロンプト例
Rork の AI チャットでこのような指示をすると、スプラッシュスクリーンの設定を反映したコードを生成してもらえます。
アプリ起動時にスプラッシュスクリーンを表示したい。
・expo-splash-screen を使って、フォント読み込みが完了するまでスプラッシュを表示し続ける
・スプラッシュが消えるときにフェードアウトアニメーションを付ける
・app.json のスプラッシュ設定も教えてほしい
生成されたコードを元に、画像パスや背景色を自分のブランドに合わせて調整していきましょう。
実際に運用してみて気づいた、見落としがちな勘所
ドキュメント通りに設定しても、実機で起動すると微妙な違和感が残ることがあります。私自身、個人開発でアプリを公開したとき、ここで二つの小さな落とし穴にはまりました。
一つは背景色です。backgroundColor をメイン画面の背景と完全に揃えていないと、スプラッシュが消えた瞬間に色がパッと切り替わり、まるで画面が点滅したように見えてしまいます。ネイビーのスプラッシュから白い画面へ飛んだとき、自分でも「今ちらついた」と感じて、ようやく原因に気づきました。スプラッシュの背景色と、最初に描画される画面の背景色は、同じ値に統一しておくと安心です。
もう一つは表示時間です。準備処理が一瞬で終わると、スプラッシュが速く消えすぎて「光が一度よぎっただけ」のように見えてしまいます。逆に長く保持しすぎると、起動が重いアプリだと誤解されます。私の体感では、最低表示時間を 600〜800ミリ秒ほど確保すると、速すぎず遅すぎず、落ち着いて見えました。私はこの下限値を、アプリが起動時に読み込むデータ量に合わせて少しずつ調整してきました。setTimeout で意図的に下限を設けるかどうかは、扱うデータ量に合わせて調整してみてください。
よくあるエラーと対処法
スプラッシュ画像が引き伸ばされて表示される
resizeMode が "cover" になっている場合に起こりやすいです。ロゴ画像なら "contain" に変更してください。また、画像ファイルのサイズが小さすぎると荒くなるため、1242×2688px 以上を用意しましょう。
iOS でスプラッシュが表示されない
app.json の ios.splash セクションが正しく設定されているか確認してください。EAS Build でビルドしている場合は、eas build を再実行して設定を反映させる必要があります。
preventAutoHideAsync を呼んでも自動で消えてしまう
コンポーネントのアンマウントや不要な再レンダリングによって hideAsync が意図せず呼ばれている可能性があります。SplashScreen.hideAsync() を呼んでいる箇所が1つだけであることを確認してください。
まとめ
ここではRork で作成した Expo アプリのスプラッシュスクリーンをカスタマイズする方法を解説しました。
app.jsonでスプラッシュ画像・背景色・表示モードを設定するexpo-splash-screenのpreventAutoHideAsyncとhideAsyncを使って、非同期処理が完了するまでスプラッシュを保持するAnimatedAPI と組み合わせることで、フェードアウトなどのカスタムアニメーションを実装できる
スプラッシュスクリーンはアプリの顔です。ほんの少しの工夫で、ユーザーが最初に受け取る印象は大きく変わります。UIデザイン全般をさらに磨きたい方は、Rork アプリの UX デザインパターン完全ガイド もあわせてご覧ください。画面設計からインタラクション、マイクロアニメーションまで実践的に学べます。
Expo のスプラッシュ機能の網羅的な仕様は、Expo 公式ドキュメント が一次情報として確実です。