启动画面
在这个实验中,我们将实现一个基本的 Tauri 应用启动画面功能。这样做相当直接,启动画面实际上只是创建一个新的窗口,在应用程序执行一些与设置相关的重任务期间显示一些内容,然后在设置完成后关闭它。
先决条件
步骤
-
在开始开发任何项目之前,构建和运行初始模板非常重要,以验证您的设置是否按预期工作。
# Make sure you're in the right directorycd splashscreen-lab# Install dependenciespnpm install# Build and run the apppnpm tauri dev -
添加新窗口的最简单方法是将它们直接添加到
tauri.conf.json
。您也可以在启动时动态地创建它们,但为了简单起见,让我们只注册它们。确保您有一个标签为main
的窗口,作为隐藏窗口创建,以及一个标签为splashscreen
的窗口,直接创建为显示。您可以将所有其他选项保留为默认设置,或者根据个人偏好进行调整。src-tauri/tauri.conf.json {"windows": [{"label": "main","visible": false},{"label": "splashscreen","url": "/splashscreen"}]} -
在您开始之前,您需要一些要显示的内容。您如何开发新页面取决于您选择的框架,大多数框架都有“路由器”的概念,用于处理页面导航,这应该在 Tauri 中像正常一样工作。在这种情况下,您只需创建一个新的启动画面页面。或者,就像我们在这里要做的那样,创建一个新的
splashscreen.html
文件来承载内容。重要的是,您可以导航到
/splashscreen
URL,并显示您想要的启动画面内容。在这步之后尝试再次运行应用程序!/splashscreen.html <!doctype html><html lang="en"><head><meta charset="UTF-8" /><link rel="stylesheet" href="/src/styles.css" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Tauri App</title></head><body><div class="container"><h1>Tauri used Splash!</h1><div class="row"><h5>It was super effective!</h5></div></div></body></html> -
由于启动画面通常是为了隐藏与设置相关的重任务,因此让我们假装给应用程序一项重任务来执行,一些在前端,一些在后台。
要模拟前端中的重设置,我们将使用一个简单的
setTimeout
函数。在后台模拟重操作最容易的方法是使用Tokio库,这是Tauri在后台用于提供异步运行的Rust库。尽管Tauri提供了运行时,但Tauri并未从其中重新导出各种实用工具,因此我们需要将其添加到我们的项目中以便访问它们。这在Rust生态系统中是一种非常正常的做法。
不要在异步函数中使用
std::thread::sleep
,它们在一个并发环境中是协作运行的,而不是并行的,这意味着如果你让线程休眠而不是Tokio任务,则会导致所有计划在该线程上运行的任务无法执行,从而导致你的应用程序冻结。# Run this command where the `Cargo.toml` file iscd src-tauri# Add the Tokio cratecargo add tokio# Optionally go back to the top folder to keep developing# `tauri dev` can figure out where to run automaticallycd ..src/main.ts // These contents can be copy-pasted below the existing code, don't replace the entire file!!// Utility function to implement a sleep function in TypeScriptfunction sleep(seconds: number): Promise<void> {return new Promise(resolve => setTimeout(resolve, seconds * 1000));}// Setup functionasync function setup() {// Fake perform some really heavy setup taskconsole.log('Performing really heavy frontend setup task...')await sleep(3);console.log('Frontend setup task complete!')// Set the frontend task as being completedinvoke('set_complete', {task: 'frontend'})}// Effectively a JavaScript main functionwindow.addEventListener("DOMContentLoaded", () => {setup()});/src-tauri/src/lib.rs // Import functionalities we'll be usinguse std::sync::Mutex;use tauri::async_runtime::spawn;use tauri::{AppHandle, Manager, State};use tokio::time::{sleep, Duration};// Create a struct we'll use to track the completion of// setup related tasksstruct SetupState {frontend_task: bool,backend_task: bool,}// Our main entrypoint in a version 2 mobile compatible app#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() {// Don't write code before Tauri starts, write it in the// setup hook instead!tauri::Builder::default()// Register a `State` to be managed by Tauri// We need write access to it so we wrap it in a `Mutex`.manage(Mutex::new(SetupState {frontend_task: false,backend_task: false,}))// Add a command we can use to check.invoke_handler(tauri::generate_handler![greet, set_complete])// Use the setup hook to execute setup related tasks// Runs before the main loop, so no windows are yet created.setup(|app| {// Spawn setup as a non-blocking task so the windows can be// created and ran while it executesspawn(setup(app.handle().clone()));// The hook expects an Ok resultOk(())})// Run the app.run(tauri::generate_context!()).expect("error while running tauri application");}#[tauri::command]fn greet(name: String) -> String {format!("Hello {name} from Rust!")}// A custom task for setting the state of a setup task#[tauri::command]async fn set_complete(app: AppHandle,state: State<'_, Mutex<SetupState>>,task: String,) -> Result<(), ()> {// Lock the state without write accesslet mut state_lock = state.lock().unwrap();match task.as_str() {"frontend" => state_lock.frontend_task = true,"backend" => state_lock.backend_task = true,_ => panic!("invalid task completed!"),}// Check if both tasks are completedif state_lock.backend_task && state_lock.frontend_task {// Setup is complete, we can close the splashscreen// and unhide the main window!let splash_window = app.get_webview_window("splashscreen").unwrap();let main_window = app.get_webview_window("main").unwrap();splash_window.close().unwrap();main_window.show().unwrap();}Ok(())}// An async function that does some heavy setup taskasync fn setup(app: AppHandle) -> Result<(), ()> {// Fake performing some heavy action for 3 secondsprintln!("Performing really heavy backend setup task...");sleep(Duration::from_secs(3)).await;println!("Backend setup task completed!");// Set the backend task as being completed// Commands can be ran as regular functions as long as you take// care of the input arguments yourselfset_complete(app.clone(),app.state::<Mutex<SetupState>>(),"backend".to_string(),).await?;Ok(())} -
运行应用程序
现在你应该能看到一个启动窗口弹出,前端和后端都将执行它们各自的3秒重设置任务,然后启动窗口消失,主窗口将被显示出来!
讨论
你是否需要一个启动画面?
总的来说,拥有一个启动窗口似乎是你无法让应用程序加载足够快,以至于不需要它的一个迹象。事实上,直接进入主窗口并在角落显示一个小的旋转器,告知用户后台仍在执行设置任务,可能更好。
但是,话虽如此,这可能是你想要的一个风格选择,或者你可能有一些非常具体的需求,这使得应用程序在完成某些任务之前无法启动。拥有启动窗口绝对是正确的,但它通常并不是必要的,可能会让用户感觉应用程序的优化不是很出色。
© 2025 Tauri contributors. CC-BY / MIT