adding-tauri-splashscreen
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTauri Splashscreen Implementation
Tauri启动屏实现方案
This skill covers implementing splash screens in Tauri v2 applications. A splash screen displays during application startup while the main window loads and initializes.
本技能讲解如何在Tauri v2应用中实现启动屏。启动屏会在应用启动过程中显示,直至主窗口加载并初始化完成。
Overview
概述
The splash screen pattern involves:
- Showing a splash window immediately on launch
- Hiding the main window until ready
- Performing initialization tasks (frontend and backend)
- Closing splash and showing main window when complete
启动屏的实现模式包括:
- 应用启动后立即显示启动窗口
- 隐藏主窗口直至准备就绪
- 执行初始化任务(前端和后端)
- 初始化完成后关闭启动屏并显示主窗口
Configuration
配置
Window Configuration
窗口配置
Configure both windows in :
tauri.conf.jsonjson
{
"app": {
"windows": [
{
"label": "main",
"title": "My Application",
"width": 1200,
"height": 800,
"visible": false
},
{
"label": "splashscreen",
"title": "Loading",
"url": "splashscreen.html",
"width": 400,
"height": 300,
"center": true,
"resizable": false,
"decorations": false,
"transparent": true,
"alwaysOnTop": true
}
]
}
}Key settings:
- on main window - hides it until ready
"visible": false - - points to splash screen HTML
"url": "splashscreen.html" - - removes window chrome for cleaner look
"decorations": false - - enables transparent backgrounds
"transparent": true - - keeps splash visible during loading
"alwaysOnTop": true
在中配置两个窗口:
tauri.conf.jsonjson
{
"app": {
"windows": [
{
"label": "main",
"title": "My Application",
"width": 1200,
"height": 800,
"visible": false
},
{
"label": "splashscreen",
"title": "Loading",
"url": "splashscreen.html",
"width": 400,
"height": 300,
"center": true,
"resizable": false,
"decorations": false,
"transparent": true,
"alwaysOnTop": true
}
]
}
}关键设置:
- 主窗口设置- 直至准备就绪前保持隐藏
"visible": false - - 指向启动屏HTML文件
"url": "splashscreen.html" - - 移除窗口边框,实现更简洁的外观
"decorations": false - - 启用透明背景
"transparent": true - - 确保启动屏在加载过程中始终显示在最上层
"alwaysOnTop": true
Splash Screen HTML
启动屏HTML
Create in your frontend source directory (e.g., or ):
splashscreen.htmlsrc/public/html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
background: transparent;
}
.splash-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 12px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.logo {
width: 80px;
height: 80px;
margin-bottom: 24px;
}
.app-name {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: #4f46e5;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 16px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
}
</style>
</head>
<body>
<div class="splash-container">
<!-- Replace with your logo -->
<svg class="logo" viewBox="0 0 100 100" fill="none">
<circle cx="50" cy="50" r="45" stroke="#4f46e5" stroke-width="4"/>
<path d="M30 50 L45 65 L70 35" stroke="#4f46e5" stroke-width="4" fill="none"/>
</svg>
<div class="app-name">My Application</div>
<div class="loading-spinner"></div>
<div class="loading-text">Loading...</div>
</div>
</body>
</html>在前端源码目录(如或)中创建:
src/public/splashscreen.htmlhtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
background: transparent;
}
.splash-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 12px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.logo {
width: 80px;
height: 80px;
margin-bottom: 24px;
}
.app-name {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: #4f46e5;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 16px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
}
</style>
</head>
<body>
<div class="splash-container">
<!-- Replace with your logo -->
<svg class="logo" viewBox="0 0 100 100" fill="none">
<circle cx="50" cy="50" r="45" stroke="#4f46e5" stroke-width="4"/>
<path d="M30 50 L45 65 L70 35" stroke="#4f46e5" stroke-width="4" fill="none"/>
</svg>
<div class="app-name">My Application</div>
<div class="loading-spinner"></div>
<div class="loading-text">Loading...</div>
</div>
</body>
</html>Frontend Setup
前端设置
TypeScript/JavaScript Implementation
TypeScript/JavaScript实现
In your main entry file (e.g., ):
src/main.tstypescript
import { invoke } from '@tauri-apps/api/core';
// Helper function for delays
function sleep(seconds: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
// Frontend initialization
async function initializeFrontend(): Promise<void> {
// Perform frontend setup tasks here:
// - Load configuration
// - Initialize state management
// - Set up routing
// - Preload critical assets
// Example: simulate initialization time
await sleep(1);
// Notify backend that frontend is ready
await invoke('set_complete', { task: 'frontend' });
}
// Start initialization when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initializeFrontend().catch(console.error);
});在主入口文件(如)中:
src/main.tstypescript
import { invoke } from '@tauri-apps/api/core';
// Helper function for delays
function sleep(seconds: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
// Frontend initialization
async function initializeFrontend(): Promise<void> {
// Perform frontend setup tasks here:
// - Load configuration
// - Initialize state management
// - Set up routing
// - Preload critical assets
// Example: simulate initialization time
await sleep(1);
// Notify backend that frontend is ready
await invoke('set_complete', { task: 'frontend' });
}
// Start initialization when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initializeFrontend().catch(console.error);
});Alternative: Using Window Events
替代方案:使用窗口事件
typescript
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
async function initializeFrontend(): Promise<void> {
// Your initialization logic
const config = await loadConfig();
await setupRouter();
await preloadAssets();
// Signal completion
await invoke('set_complete', { task: 'frontend' });
}
// Wait for window to be fully ready
getCurrentWindow().once('tauri://created', () => {
initializeFrontend();
});typescript
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
async function initializeFrontend(): Promise<void> {
// Your initialization logic
const config = await loadConfig();
await setupRouter();
await preloadAssets();
// Signal completion
await invoke('set_complete', { task: 'frontend' });
}
// Wait for window to be fully ready
getCurrentWindow().once('tauri://created', () => {
initializeFrontend();
});Backend Setup
后端设置
Add Tokio Dependency
添加Tokio依赖
bash
cargo add tokio --features timebash
cargo add tokio --features timeRust Implementation
Rust实现
In :
src-tauri/src/lib.rsrust
use std::sync::Mutex;
use tauri::{AppHandle, Manager, State};
// Track initialization state
struct SetupState {
frontend_task: bool,
backend_task: bool,
}
impl Default for SetupState {
fn default() -> Self {
Self {
frontend_task: false,
backend_task: false,
}
}
}
// Command to mark tasks complete
#[tauri::command]
async fn set_complete(
app: AppHandle,
state: State<'_, Mutex<SetupState>>,
task: String,
) -> Result<(), String> {
let mut state = state.lock().map_err(|e| e.to_string())?;
match task.as_str() {
"frontend" => state.frontend_task = true,
"backend" => state.backend_task = true,
_ => return Err(format!("Unknown task: {}", task)),
}
// Check if all tasks are complete
if state.frontend_task && state.backend_task {
// Close splash and show main window
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().map_err(|e| e.to_string())?;
}
if let Some(main) = app.get_webview_window("main") {
main.show().map_err(|e| e.to_string())?;
main.set_focus().map_err(|e| e.to_string())?;
}
}
Ok(())
}
// Backend initialization
async fn setup_backend(app: AppHandle) {
// IMPORTANT: Use tokio::time::sleep, NOT std::thread::sleep
// std::thread::sleep blocks the entire async runtime
// Perform backend initialization:
// - Database connections
// - Load configuration
// - Initialize services
// Example: simulate work
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
// Mark backend as complete
if let Some(state) = app.try_state::<Mutex<SetupState>>() {
let _ = set_complete(
app.clone(),
state,
"backend".to_string(),
).await;
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(Mutex::new(SetupState::default()))
.invoke_handler(tauri::generate_handler![set_complete])
.setup(|app| {
let handle = app.handle().clone();
// Spawn backend initialization
tauri::async_runtime::spawn(async move {
setup_backend(handle).await;
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}在中:
src-tauri/src/lib.rsrust
use std::sync::Mutex;
use tauri::{AppHandle, Manager, State};
// Track initialization state
struct SetupState {
frontend_task: bool,
backend_task: bool,
}
impl Default for SetupState {
fn default() -> Self {
Self {
frontend_task: false,
backend_task: false,
}
}
}
// Command to mark tasks complete
#[tauri::command]
async fn set_complete(
app: AppHandle,
state: State<'_, Mutex<SetupState>>,
task: String,
) -> Result<(), String> {
let mut state = state.lock().map_err(|e| e.to_string())?;
match task.as_str() {
"frontend" => state.frontend_task = true,
"backend" => state.backend_task = true,
_ => return Err(format!("Unknown task: {}", task)),
}
// Check if all tasks are complete
if state.frontend_task && state.backend_task {
// Close splash and show main window
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().map_err(|e| e.to_string())?;
}
if let Some(main) = app.get_webview_window("main") {
main.show().map_err(|e| e.to_string())?;
main.set_focus().map_err(|e| e.to_string())?;
}
}
Ok(())
}
// Backend initialization
async fn setup_backend(app: AppHandle) {
// IMPORTANT: Use tokio::time::sleep, NOT std::thread::sleep
// std::thread::sleep blocks the entire async runtime
// Perform backend initialization:
// - Database connections
// - Load configuration
// - Initialize services
// Example: simulate work
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
// Mark backend as complete
if let Some(state) = app.try_state::<Mutex<SetupState>>() {
let _ = set_complete(
app.clone(),
state,
"backend".to_string(),
).await;
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.manage(Mutex::new(SetupState::default()))
.invoke_handler(tauri::generate_handler![set_complete])
.setup(|app| {
let handle = app.handle().clone();
// Spawn backend initialization
tauri::async_runtime::spawn(async move {
setup_backend(handle).await;
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Simple Implementation
简易实现方案
For simpler cases where you only need to wait for the frontend:
对于仅需等待前端初始化的简单场景:
Configuration
配置
json
{
"app": {
"windows": [
{
"label": "main",
"visible": false
},
{
"label": "splashscreen",
"url": "splashscreen.html",
"width": 400,
"height": 300,
"decorations": false
}
]
}
}json
{
"app": {
"windows": [
{
"label": "main",
"visible": false
},
{
"label": "splashscreen",
"url": "splashscreen.html",
"width": 400,
"height": 300,
"decorations": false
}
]
}
}Frontend
前端
typescript
import { invoke } from '@tauri-apps/api/core';
async function init() {
// Initialize your app
await setupApp();
// Close splash, show main
await invoke('close_splashscreen');
}
document.addEventListener('DOMContentLoaded', init);typescript
import { invoke } from '@tauri-apps/api/core';
async function init() {
// Initialize your app
await setupApp();
// Close splash, show main
await invoke('close_splashscreen');
}
document.addEventListener('DOMContentLoaded', init);Backend
后端
rust
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn close_splashscreen(app: AppHandle) -> Result<(), String> {
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().map_err(|e| e.to_string())?;
}
if let Some(main) = app.get_webview_window("main") {
main.show().map_err(|e| e.to_string())?;
main.set_focus().map_err(|e| e.to_string())?;
}
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![close_splashscreen])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}rust
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn close_splashscreen(app: AppHandle) -> Result<(), String> {
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().map_err(|e| e.to_string())?;
}
if let Some(main) = app.get_webview_window("main") {
main.show().map_err(|e| e.to_string())?;
main.set_focus().map_err(|e| e.to_string())?;
}
Ok(())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![close_splashscreen])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Styling Variations
样式变体
Minimal Splash
极简启动屏
html
<style>
.splash-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background: #ffffff;
}
.logo {
width: 120px;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(0.95); }
}
</style>html
<style>
.splash-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background: #ffffff;
}
.logo {
width: 120px;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(0.95); }
}
</style>Progress Bar Splash
带进度条的启动屏
html
<style>
.progress-container {
width: 200px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
margin-top: 20px;
}
.progress-bar {
height: 100%;
background: #4f46e5;
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { width: 0%; }
50% { width: 70%; }
100% { width: 100%; }
}
</style>
<div class="progress-container">
<div class="progress-bar"></div>
</div>html
<style>
.progress-container {
width: 200px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
margin-top: 20px;
}
.progress-bar {
height: 100%;
background: #4f46e5;
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { width: 0%; }
50% { width: 70%; }
100% { width: 100%; }
}
</style>
<div class="progress-container">
<div class="progress-bar"></div>
</div>Dark Theme with Glow
深色发光主题
html
<style>
.splash-container {
background: #0a0a0a;
color: #ffffff;
}
.logo {
filter: drop-shadow(0 0 20px rgba(79, 70, 229, 0.5));
}
.app-name {
text-shadow: 0 0 20px rgba(79, 70, 229, 0.5);
}
</style>html
<style>
.splash-container {
background: #0a0a0a;
color: #ffffff;
}
.logo {
filter: drop-shadow(0 0 20px rgba(79, 70, 229, 0.5));
}
.app-name {
text-shadow: 0 0 20px rgba(79, 70, 229, 0.5);
}
</style>Important Notes
重要注意事项
-
Async Sleep: Always usein async Rust code, never
tokio::time::sleep. The latter blocks the entire runtime.std::thread::sleep -
Window Labels: Ensure window labels in code match those in.
tauri.conf.json -
Error Handling: The splash screen should handle errors gracefully. If initialization fails, show the main window anyway with an error state.
-
Timing: Keep splash screen visible long enough for branding but not so long it frustrates users. Aim for 1-3 seconds minimum.
-
Transparent Windows: When using, ensure your HTML has
transparent: trueonbackground: transparentandhtmlelements.body -
Mobile Considerations: On mobile platforms, splash screens work differently. Consider using platform-native splash screens for iOS and Android.
-
异步休眠:在Rust异步代码中始终使用,切勿使用
tokio::time::sleep。后者会阻塞整个运行时。std::thread::sleep -
窗口标签:确保代码中的窗口标签与中的标签一致。
tauri.conf.json -
错误处理:启动屏应能优雅处理错误。如果初始化失败,仍应显示主窗口并展示错误状态。
-
显示时长:启动屏的显示时长需足够展示品牌,但不能过长导致用户厌烦。建议最少显示1-3秒。
-
透明窗口:当设置时,确保HTML的
transparent: true和html元素设置了body。background: transparent -
移动端注意事项:在移动平台上,启动屏的工作方式不同。建议为iOS和Android使用平台原生启动屏。
Troubleshooting
故障排查
Splash screen doesn't appear:
- Verify the URL path is correct in
tauri.conf.json - Check that the HTML file exists in the correct location
Main window shows too early:
- Ensure is set on the main window
visible: false - Verify the command is being called correctly
set_complete
Transparent background not working:
- Set in window config
transparent: true - Set in CSS for html and body
background: transparent - On some platforms, you may need
decorations: false
Window position issues:
- Use for centered splash screens
center: true - Or specify explicit and
xcoordinatesy
启动屏不显示:
- 验证中的URL路径是否正确
tauri.conf.json - 检查HTML文件是否存在于正确位置
主窗口过早显示:
- 确保主窗口设置了
visible: false - 验证命令是否被正确调用
set_complete
透明背景不生效:
- 在窗口配置中设置
transparent: true - 在CSS中为html和body设置
background: transparent - 在部分平台上,可能需要同时设置
decorations: false
窗口位置问题:
- 使用使启动屏居中显示
center: true - 或指定明确的和
x坐标y