A feature-rich NativePHP plugin for animated mobile splash screens. Supports Lottie animations, gradient backgrounds, dark mode, seasonal scheduling, exit transitions, app icon overlay, and NativePHP event dispatching — all driven by a single config file and .env variables.
splashscreen_animation.mp4
splashscreen_text.mp4
- PHP 8.2+
ext-zip(for automatic dotLottie v2→v1 conversion)- NativePHP Mobile 3.0+
- Lottie 4.6.0+ (iOS, via SPM — already bundled by NativePHP)
- Lottie Compose 6.7.1+ (Android — declared automatically via
nativephp.json)
composer require s2br/nativephp-mobile-splashscreenPublish the config:
php artisan vendor:publish --tag=mobile-splashscreen-configRegister the plugin in app/Providers/NativeServiceProvider.php:
use S2BR\MobileSplashscreen\MobileSplashscreenServiceProvider;
public function plugins(): array
{
return [
MobileSplashscreenServiceProvider::class,
];
}Configure in .env:
MOBILE_SPLASHSCREEN_ANIMATION_PATH="resources/animations/splash.lottie"
MOBILE_SPLASHSCREEN_BG_TYPE=gradient
MOBILE_SPLASHSCREEN_GRADIENT_COLORS="#079F3D,#046B28"
MOBILE_SPLASHSCREEN_SIZE=0.8
MOBILE_SPLASHSCREEN_LOOP=falseTip: Place your
.lottiefiles inresources/animations/— this is the conventional location and is correctly resolved viabase_path()at build time.
The plugin hooks into NativePHP's build pipeline at two stages:
-
copy_assets— Copies your.lottiefiles to the native project. If a file is in dotLottie v2 format (the default LottieFiles Creator export), it automatically converts it to v1 forlottie-spmcompatibility on iOS. Also copies scheduled and dark-mode animations if configured. -
pre_compile— Generates platform-specific splash screen code:- iOS: Replaces
SplashView.swiftwith generated SwiftUI. - Android: Replaces the
SplashScreen()composable inMainActivity.kt. - Updates
LaunchScreen.storyboard(iOS) andthemes.xml(Android) to match the background color.
- iOS: Replaces
Everything is config-driven. Re-running the build picks up any change.
All options can be set via .env or config/mobile-splashscreen.php.
MOBILE_SPLASHSCREEN_CONTENT=animation # animation | text| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_ANIMATION_PATH |
null |
Relative path to .lottie file (from project root) |
MOBILE_SPLASHSCREEN_LOOP |
true |
true = loop forever, false = play once |
MOBILE_SPLASHSCREEN_SIZE |
0.8 |
Width as fraction of screen width (0.1–1.0) |
MOBILE_SPLASHSCREEN_POSITION |
center |
center / top / bottom |
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_BG_TYPE |
color |
color or gradient |
MOBILE_SPLASHSCREEN_BG_COLOR |
#FFFFFF |
Hex color for solid backgrounds |
MOBILE_SPLASHSCREEN_GRADIENT_COLORS |
#079F3D,#046B28 |
Comma-separated hex colors (min 2) |
MOBILE_SPLASHSCREEN_GRADIENT_DIRECTION |
vertical |
vertical / horizontal / diagonal |
When using a gradient, the first color is also applied to
LaunchScreen.storyboard(iOS) andthemes.xml(Android) so the OS-level launch matches your animation background.
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_TEXT |
'' |
The text to display |
MOBILE_SPLASHSCREEN_TEXT_COLOR |
#FFFFFF |
Text color (hex) |
MOBILE_SPLASHSCREEN_TEXT_SIZE |
32 |
Font size in points/sp |
MOBILE_SPLASHSCREEN_TEXT_WEIGHT |
bold |
thin / light / regular / medium / semibold / bold / heavy / black |
Displays the app icon alongside the animation or text.
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_SHOW_ICON |
false |
Show the icon |
MOBILE_SPLASHSCREEN_ICON_SIZE |
0.2 |
Width as fraction of screen width (0.1–0.5) |
MOBILE_SPLASHSCREEN_ICON_POSITION |
bottom |
top or bottom relative to main content |
MOBILE_SPLASHSCREEN_ICON_RADIUS |
0.22 |
Corner radius as fraction of icon width (0.0–0.5, where 0.5 = circle) |
iOS uses UIImage(named: "AppIcon") from the asset catalog. Android uses R.mipmap.ic_launcher.
All values in milliseconds.
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_DELAY_BEFORE |
0 |
Wait before the splash fades in |
MOBILE_SPLASHSCREEN_FADE_IN |
600 |
Fade-in duration |
MOBILE_SPLASHSCREEN_DELAY_AFTER |
0 |
Hold after single-run animation ends (no transition) |
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_EVENT_COMPLETE |
true |
Dispatch SplashscreenCompleted when a single-run animation ends |
MOBILE_SPLASHSCREEN_EVENT_LOOP |
false |
Dispatch SplashscreenLoopCompleted after each loop iteration |
The OS renders LaunchScreen.storyboard (iOS) and themes.xml (Android) before the app process starts — typically for 100–300 ms. This color is baked into the binary at compile time and cannot be changed at runtime.
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_LAUNCH_COLOR |
null |
Solid hex color for the OS-level launch screen. When null, defaults to the first background/gradient color. |
Tip: Use this when your animation background varies at runtime (e.g. seasonal schedule) and you want the OS launch color to stay consistent instead of matching whichever animation is currently active.
MOBILE_SPLASHSCREEN_LAUNCH_COLOR="#18181B"Displays a subtle loading indicator while a single-run animation (loop = false) has finished but the app is still loading. It fills asymptotically toward ~88%, then completes to 100% the moment the app is ready. For looping animations this option has no effect.
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_PROGRESS_BAR |
false |
Show the progress bar |
MOBILE_SPLASHSCREEN_PROGRESS_BAR_COLOR |
#FFFFFF |
Bar color (track at 15% opacity, fill at 60% opacity) |
The bar is centered, takes ~50% of the screen width, is 3dp/pt tall with rounded ends, and exits as part of the splash transition — for circle_expand it gets swept away by the expanding circle; for fade/slide/scale it exits with everything else.
MOBILE_SPLASHSCREEN_PROGRESS_BAR sets the default — it can always be overridden per entry in the dynamic or static schedule using "progress_bar": true/false. The same applies to progress_bar_color.
MOBILE_SPLASHSCREEN_LOOP=false
MOBILE_SPLASHSCREEN_PROGRESS_BAR=false
MOBILE_SPLASHSCREEN_PROGRESS_BAR_COLOR="#FFFFFF"The exit animation played within the SplashView before the completion event is dispatched. Works with both loop = false (single-run) and loop = true — in loop mode the transition fires as soon as the WebView is ready, regardless of where in the loop the animation is.
| ENV variable | Default | Options |
|---|---|---|
MOBILE_SPLASHSCREEN_TRANSITION_OUT |
none |
none / fade / scale_up / scale_down / slide_up / slide_down / circle_expand |
MOBILE_SPLASHSCREEN_TRANSITION_DURATION |
400 |
Duration in milliseconds |
MOBILE_SPLASHSCREEN_TRANSITION_ORIGIN |
center |
circle_expand start point: center / top / bottom / top_left / top_right / bottom_left / bottom_right / center_left / center_right |
circle_expand punches a transparent hole through the splash layer, revealing the WebView behind it — not a colored overlay. The hole expands from the chosen origin point until it covers the entire screen.
Example — circle reveal from center:
MOBILE_SPLASHSCREEN_LOOP=false
MOBILE_SPLASHSCREEN_TRANSITION_OUT=circle_expand
MOBILE_SPLASHSCREEN_TRANSITION_DURATION=500
MOBILE_SPLASHSCREEN_TRANSITION_ORIGIN=centerExample — reveal from the left edge (loop mode):
MOBILE_SPLASHSCREEN_LOOP=true
MOBILE_SPLASHSCREEN_TRANSITION_OUT=circle_expand
MOBILE_SPLASHSCREEN_TRANSITION_DURATION=600
MOBILE_SPLASHSCREEN_TRANSITION_ORIGIN=center_leftControls how the splash responds to the system dark/light mode.
| ENV variable | Default | Description |
|---|---|---|
MOBILE_SPLASHSCREEN_THEME |
auto |
auto = follow system, light = always light, dark = always dark |
MOBILE_SPLASHSCREEN_DARK_ANIMATION_PATH |
null |
Alternative .lottie file for dark mode |
MOBILE_SPLASHSCREEN_DARK_BG_TYPE |
null |
Set to color or gradient to activate dark background |
MOBILE_SPLASHSCREEN_DARK_BG_COLOR |
#000000 |
Dark mode solid background color |
MOBILE_SPLASHSCREEN_DARK_GRADIENT_COLORS |
#000000,#1A1A1A |
Dark mode gradient colors |
MOBILE_SPLASHSCREEN_DARK_GRADIENT_DIRECTION |
vertical |
Dark mode gradient direction |
Example — dark background with a separate animation:
MOBILE_SPLASHSCREEN_THEME=auto
MOBILE_SPLASHSCREEN_DARK_BG_TYPE=gradient
MOBILE_SPLASHSCREEN_DARK_GRADIENT_COLORS="#0D0D0D,#1A1A1A"
MOBILE_SPLASHSCREEN_DARK_ANIMATION_PATH="resources/animations/splash_dark.lottie"When theme = auto, iOS uses @Environment(\.colorScheme) and Android uses isSystemInDarkTheme(). The detection happens at runtime — no rebuild needed when the user toggles dark mode.
Bake a static schedule into the binary at build time. Animation files are bundled and switching is evaluated on-device — no rebuild when the date changes, but adding new entries requires a rebuild.
MOBILE_SPLASHSCREEN_SCHEDULE="resources/splash-schedule.json"Schedule JSON format:
{
"schedule": [
{
"name": "christmas",
"from": "2024-12-24",
"to": "2024-12-26",
"animation": "resources/animations/christmas.lottie",
"background": {
"type": "gradient",
"colors": ["#1B4F72", "#154360"],
"direction": "vertical"
}
},
{
"name": "new_year",
"from": "2024-12-31",
"to": "2025-01-01",
"animation": "resources/animations/fireworks.lottie"
}
]
}from/to:YYYY-MM-DDformat. Ranges that span a year boundary are handled correctly.- Every per-entry property listed in the dynamic schedule table (
loop,size,transition_out,background, etc.) works here too. The entries are embedded in the binary as JSON and resolved on-device at launch — same mechanism, just baked in at build time instead of downloaded. - All referenced animation files are automatically deployed at build time.
Priority at runtime: dynamic schedule-local → build-time schedule entry → dark mode override → default.
For animations that can be updated without a new app release. The sync command fetches your remote schedule, pre-downloads .lottie files for every entry active within a configurable lookahead window, and writes schedule-local.json to device storage. Native code reads this file on every app launch and resolves today's active entry on-device — no network call is needed at launch, and the correct animation plays even when the device is offline.
How it works:
- Your server exposes a JSON schedule at a URL.
- A daily Laravel scheduler runs the sync command while the device has internet.
- The command pre-downloads animation files for the next N days (default 30).
- At launch the app reads the local schedule and picks the entry matching today's date.
- If no entry matches, the static build-time animation plays instead.
- Stale animation files (past their end date and no longer in the schedule) are automatically deleted.
Step 1 — Define your remote schedule JSON:
{
"schedule": [
{
"name": "christmas",
"date": { "from": "2026-12-24", "to": "2026-12-26" },
"url": "https://your-cdn.com/animations/christmas.lottie",
"background": {
"type": "gradient",
"colors": ["#1B4F72", "#154360"]
}
},
{
"name": "new_year",
"date": { "from": "2026-12-31", "to": "2027-01-02" },
"url": "https://your-cdn.com/animations/new_year.lottie",
"background": {
"type": "color",
"color": "#0D0D0D"
}
}
]
}| Field | Description |
|---|---|
date.from / date.to |
Date range in YYYY-MM-DD format. Year-boundary ranges work correctly. |
url |
URL to download the .lottie file from. |
background |
Optional. type: "gradient" with a colors array, or type: "color" with a color hex. Overrides the build-time background for this entry. |
loop |
Optional boolean. Overrides MOBILE_SPLASHSCREEN_LOOP for this entry. |
size |
Optional float (0.1–1.0). Overrides MOBILE_SPLASHSCREEN_SIZE. |
position |
Optional string (center / top / bottom). Overrides MOBILE_SPLASHSCREEN_POSITION. |
transition_out |
Optional string (none / fade / scale_up / scale_down / slide_up / slide_down / circle_expand). |
transition_duration |
Optional integer (ms). Overrides MOBILE_SPLASHSCREEN_TRANSITION_DURATION. |
transition_origin |
Optional string. Overrides MOBILE_SPLASHSCREEN_TRANSITION_ORIGIN for circle_expand. |
delay_before |
Optional integer (ms). Overrides MOBILE_SPLASHSCREEN_DELAY_BEFORE. |
fade_in |
Optional integer (ms). Overrides MOBILE_SPLASHSCREEN_FADE_IN. |
delay_after |
Optional integer (ms). Overrides MOBILE_SPLASHSCREEN_DELAY_AFTER. |
on_complete |
Optional boolean. Overrides MOBILE_SPLASHSCREEN_EVENT_COMPLETE. |
on_loop |
Optional boolean. Overrides MOBILE_SPLASHSCREEN_EVENT_LOOP. |
show_icon |
Optional boolean. Overrides MOBILE_SPLASHSCREEN_SHOW_ICON. |
icon_size |
Optional float (0.1–0.5). Overrides MOBILE_SPLASHSCREEN_ICON_SIZE. |
icon_position |
Optional string (top / bottom). Overrides MOBILE_SPLASHSCREEN_ICON_POSITION. |
icon_radius |
Optional float (0.0–0.5). Overrides MOBILE_SPLASHSCREEN_ICON_RADIUS. |
progress_bar |
Optional boolean. Show or hide the progress bar for this entry. Overrides MOBILE_SPLASHSCREEN_PROGRESS_BAR. |
progress_bar_color |
Optional hex string. Overrides MOBILE_SPLASHSCREEN_PROGRESS_BAR_COLOR for this entry. |
Every property is optional — omit any field to inherit the build-time value from your .env/config. This means a minimal entry only needs date, url, and the properties that differ from your defaults.
Full example — entry with all overrides:
{
"schedule": [
{
"name": "christmas",
"date": { "from": "2026-12-24", "to": "2026-12-26" },
"url": "https://your-cdn.com/animations/christmas.lottie",
"background": {
"type": "gradient",
"colors": ["#1B4F72", "#154360"]
},
"loop": true,
"size": 0.9,
"position": "center",
"transition_out": "circle_expand",
"transition_duration": 600,
"transition_origin": "center",
"delay_before": 0,
"fade_in": 800,
"delay_after": 0,
"on_complete": false,
"on_loop": true,
"show_icon": true,
"icon_size": 0.15,
"icon_position": "bottom",
"icon_radius": 0.22,
"progress_bar": true,
"progress_bar_color": "#FFFFFF"
}
]
}Step 2 — Run the sync command:
# Fetch schedule + pre-download animations active within the next 30 days (default)
php artisan nativephp:mobile-splashscreen:sync --url=https://your-cdn.com/animations.json
# Extend the lookahead window (e.g. 60 days, useful before a long offline period)
php artisan nativephp:mobile-splashscreen:sync --url=https://your-cdn.com/animations.json --days=60
# Re-resolve from an already-downloaded animations.json (no re-fetch)
php artisan nativephp:mobile-splashscreen:syncStep 3 — Schedule it to run daily:
// routes/console.php
Schedule::command('nativephp:mobile-splashscreen:sync --url=https://your-cdn.com/animations.json')
->daily();Or use the fluent PHP API directly:
use S2BR\MobileSplashscreen\Facades\MobileSplashscreen;
MobileSplashscreen::syncFromUrl('https://your-cdn.com/animations.json')
->resolveActive(daysAhead: 30);Other helpers:
MobileSplashscreen::hasActive(); // true if schedule-local.json exists
MobileSplashscreen::clearActive(); // remove schedule-local.json (reverts to default)Files written to storage/app/splashscreen/:
| File | Description |
|---|---|
animations.json |
Full schedule downloaded from your server |
animations/ |
Pre-downloaded .lottie files (stale files auto-deleted on each sync) |
schedule-local.json |
Normalised upcoming entries with local file paths — read by native code at launch |
The bundled default animation (from .env) always serves as fallback if schedule-local.json does not exist or no entry matches today's date.
Dispatched when a single-run animation (loop = false) finishes, after any transition_out animation completes.
Payload: animationName (string), duration (float, reserved — currently always 0.0).
Dispatched after each loop iteration when loop = true and events.on_loop = true.
Payload: animationName (string), iteration (int, starts at 1).
use Livewire\Attributes\On;
use S2BR\MobileSplashscreen\Events\SplashscreenCompleted;
use S2BR\MobileSplashscreen\Events\SplashscreenLoopCompleted;
#[On('native:'.SplashscreenCompleted::class)]
public function onSplashDone(string $animationName, float $duration): void
{
// Dispatched after the splash exits (including any transition_out animation)
}
#[On('native:'.SplashscreenLoopCompleted::class)]
public function onSplashLoop(int $iteration, string $animationName): void
{
if ($iteration >= 3) {
// After 3 loops, do something in your app
}
}import { useEffect } from 'react';
import { SplashscreenCompleted } from '@/nativephp/events';
useEffect(() => {
const handler = (event) => {
const { animationName, duration } = event.detail;
console.log('Splash done', animationName);
};
window.addEventListener('native:S2BR\\MobileSplashscreen\\Events\\SplashscreenCompleted', handler);
return () => {
window.removeEventListener('native:S2BR\\MobileSplashscreen\\Events\\SplashscreenCompleted', handler);
};
}, []);import { onMounted, onUnmounted } from 'vue';
function onSplashDone(event) {
const { animationName } = event.detail;
console.log('Splash done', animationName);
}
onMounted(() => {
window.addEventListener('native:S2BR\\MobileSplashscreen\\Events\\SplashscreenCompleted', onSplashDone);
});
onUnmounted(() => {
window.removeEventListener('native:S2BR\\MobileSplashscreen\\Events\\SplashscreenCompleted', onSplashDone);
});Note: Events are dispatched via the NativePHP JS bridge after the WebView is ready. They arrive after the splash has signalled completion, not while it is still visible. NativePHP handles the actual dismissal of the splash view when the WebView finishes loading.
MOBILE_SPLASHSCREEN_ANIMATION_PATH="resources/animations/splash.lottie"
MOBILE_SPLASHSCREEN_BG_TYPE=gradient
MOBILE_SPLASHSCREEN_GRADIENT_COLORS="#079F3D,#046B28"
MOBILE_SPLASHSCREEN_SIZE=0.8MOBILE_SPLASHSCREEN_ANIMATION_PATH="resources/animations/intro.lottie"
MOBILE_SPLASHSCREEN_LOOP=false
MOBILE_SPLASHSCREEN_PROGRESS_BAR=true
MOBILE_SPLASHSCREEN_PROGRESS_BAR_COLOR="#FFFFFF"
MOBILE_SPLASHSCREEN_TRANSITION_OUT=circle_expand
MOBILE_SPLASHSCREEN_TRANSITION_ORIGIN=center_leftMOBILE_SPLASHSCREEN_ANIMATION_PATH="resources/animations/intro.lottie"
MOBILE_SPLASHSCREEN_LOOP=false
MOBILE_SPLASHSCREEN_TRANSITION_OUT=fade
MOBILE_SPLASHSCREEN_TRANSITION_DURATION=400
MOBILE_SPLASHSCREEN_EVENT_COMPLETE=trueMOBILE_SPLASHSCREEN_ANIMATION_PATH="resources/animations/splash.lottie"
MOBILE_SPLASHSCREEN_LOOP=true
MOBILE_SPLASHSCREEN_TRANSITION_OUT=circle_expand
MOBILE_SPLASHSCREEN_TRANSITION_DURATION=500
MOBILE_SPLASHSCREEN_TRANSITION_ORIGIN=centerMOBILE_SPLASHSCREEN_THEME=auto
MOBILE_SPLASHSCREEN_BG_TYPE=gradient
MOBILE_SPLASHSCREEN_GRADIENT_COLORS="#079F3D,#046B28"
MOBILE_SPLASHSCREEN_DARK_BG_TYPE=gradient
MOBILE_SPLASHSCREEN_DARK_GRADIENT_COLORS="#0D0D0D,#1A1A1A"MOBILE_SPLASHSCREEN_SCHEDULE="resources/splash-schedule.json"MOBILE_SPLASHSCREEN_CONTENT=text
MOBILE_SPLASHSCREEN_TEXT="Loading..."
MOBILE_SPLASHSCREEN_TEXT_COLOR="#FFFFFF"
MOBILE_SPLASHSCREEN_TEXT_WEIGHT=light
MOBILE_SPLASHSCREEN_BG_COLOR="#1A1A2E"MOBILE_SPLASHSCREEN_ANIMATION_PATH="resources/animations/splash.lottie"
MOBILE_SPLASHSCREEN_BG_TYPE=gradient
MOBILE_SPLASHSCREEN_GRADIENT_COLORS="#079F3D,#046B28"
MOBILE_SPLASHSCREEN_SHOW_ICON=true
MOBILE_SPLASHSCREEN_ICON_POSITION=bottom
MOBILE_SPLASHSCREEN_ICON_RADIUS=0.22| Scenario | Behavior |
|---|---|
| v1 file | Copied as-is to the native bundle |
| v2 file (LottieFiles Creator default) | Auto-converted to v1 during copy_assets |
| Conversion strips | fonts, layer effects (ef), hasMask, text layers (ty=5), Background layer |
Why this matters: lottie-spm 4.6.0 (bundled by NativePHP iOS) only supports dotLottie v1. A v2 file silently fails — blank screen, no error. The conversion happens automatically; no manual script is needed.
To verify your file format:
unzip -l your-animation.lottie
# v1: shows animations/main.json
# v2: shows a/Main Scene.jsonText layers: ty=5 text layers are stripped during conversion because lottie-spm crashes when a referenced font is not embedded. Vectorize text in LottieFiles Creator before exporting.
use S2BR\MobileSplashscreen\Facades\MobileSplashscreen;
$size = MobileSplashscreen::config('animation.size');
$bgType = MobileSplashscreen::config('background.type');
$errors = MobileSplashscreen::validate();Check the build output for:
Detected dotLottie v2 — converting to v1 for lottie-spm...
If absent, verify animation.path is set and the file exists. Also confirm no Lottie iOS SPM dependency is declared in your own nativephp.json — NativePHP already bundles lottie-spm and adding a second Lottie package causes a product naming conflict.
Ensure no spaces around commas:
# Correct
MOBILE_SPLASHSCREEN_GRADIENT_COLORS="#079F3D,#046B28"
# Wrong
MOBILE_SPLASHSCREEN_GRADIENT_COLORS="#079F3D, #046B28"The plugin replaces SplashView.swift entirely during pre_compile. Manual edits are overwritten. All customization must go through the config file.
Ensure the schedule JSON path is correct and all referenced .lottie files exist at build time. The copy_assets hook reads the schedule and deploys all referenced animations.
- Confirm
schedule-local.jsonexists instorage/app/splashscreen/on the device. - Check that the entry's
from/todates include today inYYYY-MM-DDformat. - Verify the
.lottiefile was downloaded tostorage/app/splashscreen/animations/— check the sync command output. - If the sync command ran before the entry's start date, ensure the lookahead window (
--days) was large enough to include it.
Ensure the plugin is registered in app/Providers/NativeServiceProvider.php:
public function plugins(): array
{
return [
\S2BR\MobileSplashscreen\MobileSplashscreenServiceProvider::class,
];
}Please see CONTRIBUTING for details.
The NativePHP Mobile Splashscreen plugin is open-sourced software licensed under the MIT license.