方式一、使用Splashscreen掩盖应用启动的耗时,同时完成初始化任务

1. 新建Splashscreen页面

在根目录中新建一个 splashscreen.htmlsrc/splashscreen-main.tsx 用于在软件启动过程前显示加载页面

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tauri App - Splash</title>
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/splashscreen-main.tsx"></script>
  </body>
</html>
import React from 'react'
import ReactDOM from 'react-dom/client'
import Splashscreen from './Splashscreen'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Splashscreen />
  </React.StrictMode>
)

然后创建 src/Splashscreen.tsxsrc/Splashscreen.css

import "./Splashscreen.css";

function Splashscreen() {
  return (
    <div className="container">
      <h1>Tauri used Splash!</h1>
      <div className="row">
        <h5>It was super effective!</h5>
      </div>
    </div>
  );
}

export default Splashscreen;
:root {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  color: #0f0f0f;
  background-color: #f6f6f6;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-color-scheme: dark) {
  :root {
    color: #f6f6f6;
    background-color: #2f2f2f;
  }
}

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

.container {
  margin: 0;
  padding: 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}

.row {
  display: flex;
  justify-content: center;
}

h1 {
  text-align: center;
  margin: 0 0 10px 0;
}

h5 {
  margin: 0;
  font-weight: 500;
  color: #646cff;
}

2. 在tauri.conf.json中注册新窗口

修改 src-tauri/tauri.conf.json,注册新窗口

"windows": [
      {
        "title": "tauri-app",
        "width": 800,
        "height": 600,
        "label": "main",
        "visible": false,
        "center": true
      },
      {
        "label": "splashscreen",
        "url": "splashscreen.html",
        "width": 400,
        "height": 200,
        "resizable": false,
        "maximizable": false,
        "minimizable": false,
        "closable": false,
        "decorations": false,
        "alwaysOnTop": true,
        "center": true,
        "visible": true
      }
    ]

3. 添加tokio异步运行时依赖

cd src-tauri
cargo add tokio -F time
cd ..

4. 修改lib.rs,支持Splashscreen自动关闭

use std::sync::Mutex;
use tauri::async_runtime::spawn;
use tauri::{AppHandle, Manager, State};
use tokio::time::{sleep, Duration};

// 跟踪前后端初始化任务完成状态
struct SetupState {
    frontend_task: bool,
    backend_task: bool,
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        // 将共享状态注册到 Tauri 状态管理
        .manage(Mutex::new(SetupState {
            frontend_task: false,
            backend_task: false,
        }))
        .invoke_handler(tauri::generate_handler![greet, set_complete])
        .setup(|app| {
            // 异步执行后端初始化任务,不阻塞窗口创建
            spawn(setup(app.handle().clone()));
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

#[tauri::command]
fn greet(name: String) -> String {
    format!("Hello {name} from Rust!")
}

// 前端/后端完成各自初始化后调用此命令标记完成
#[tauri::command]
async fn set_complete(
    app: AppHandle,
    state: State<'_, Mutex<SetupState>>,
    task: String,
) -> Result<(), ()> {
    let 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!"),
    }

    // 前后端均就绪时关闭启动画面并显示主窗口
    if state_lock.backend_task && state_lock.frontend_task {
        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(())
}

// 模拟耗时后端初始化任务,同时等待前端就绪
async fn setup(app: AppHandle) -> Result<(), ()> {
    println!("Performing really heavy backend setup task...");

    let start_time = tokio::time::Instant::now();

    loop {
        let elapsed = start_time.elapsed();
        // 确保启动画面至少展示 1 秒,避免闪烁
        if elapsed < Duration::from_secs(1) {
            sleep(Duration::from_millis(100)).await;
            continue;
        }

        {
            let state = app.state::<Mutex<SetupState>>();
            let state_lock = state.lock().unwrap();
            if state_lock.frontend_task {
                break;
            }
        }

        sleep(Duration::from_millis(100)).await;
    }

    println!("Backend setup task completed!");
    // 标记后端任务完成
    set_complete(
        app.clone(),
        app.state::<Mutex<SetupState>>(),
        "backend".to_string(),
    )
    .await?;
    Ok(())
}

5. 修改App.tsx模拟耗时操作

import { useState, useEffect } from "react";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  useEffect(() => {
    // 模拟耗时操作(忙等待)
    const start = Date.now();
    while (Date.now() - start < 3000) {
      // 模拟耗时计算
    }

    requestAnimationFrame(() => {
      invoke("set_complete", { task: "frontend" });
    });
  }, []);

  async function greet() {
    // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <main className="container">
      <h1>Welcome to Tauri + React</h1>

      <div className="row">
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" className="logo vite" alt="Vite logo" />
        </a>
        <a href="https://tauri.app" target="_blank">
          <img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
        </a>
        <a href="https://reactjs.org" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <p>Click on the Tauri, Vite, and React logos to learn more.</p>

      <form
        className="row"
        onSubmit={(e) => {
          e.preventDefault();
          greet();
        }}
      >
        <input
          id="greet-input"
          onChange={(e) => setName(e.currentTarget.value)}
          placeholder="Enter a name..."
        />
        <button type="submit">Greet</button>
      </form>
      <p>{greetMsg}</p>
    </main>
  );
}

export default App;

方式二、白屏时等Webview加载完成后再显示

1. 添加权限

src-tauri/default.json中添加 permision权限

"core:window:allow-show",
"core:window:allow-set-focus"

2. 设置visible

src-tauri/tauri.conf.json中设置 visible

"windows": [
  {
    "title": "tauri-app",
    "width": 800,
    "height": 600,
    "visible": false
  }
]

3. 在 src/App.tsx中添加延迟加载

import { useState, useEffect } from "react";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import { getCurrentWindow } from '@tauri-apps/api/window';

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  const [ready, setReady] = useState(false);

  useEffect(() => {
    // 你的数据初始化逻辑放这里
    async function init() {
      // 比如:加载配置、读取本地数据等
      // await loadConfig();
      // await fetchInitialData();

      // 数据准备就绪
      setReady(true);
    }

    init();
  }, []);

  useEffect(() => {
    if (!ready) return;

    // 内容已渲染到 DOM,显示窗口
    const show = async () => {
      // 先让 CSS 生效,解除 visibility: hidden
      document.body.classList.add('loaded');

      // 再显示窗口
      const appWindow = getCurrentWindow();
      await appWindow.show();
      await appWindow.setFocus();
    };

    // 等待下一帧渲染完成
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        show();
      });
    });
  }, [ready]);

  if (!ready) {
    return null; // 数据没好之前不渲染任何内容
  }

  async function greet() {
    // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
    setGreetMsg(await invoke("greet", { name }));
  }

  return (
    <main className="container">
      <h1>Welcome to Tauri + React</h1>

      <div className="row">
        <a href="https://vite.dev" target="_blank">
          <img src="/vite.svg" className="logo vite" alt="Vite logo" />
        </a>
        <a href="https://tauri.app" target="_blank">
          <img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <p>Click on the Tauri, Vite, and React logos to learn more.</p>

      <form
        className="row"
        onSubmit={(e) => {
          e.preventDefault();
          greet();
        }}
      >
        <input
          id="greet-input"
          onChange={(e) => setName(e.currentTarget.value)}
          placeholder="Enter a name..."
        />
        <button type="submit">Greet</button>
      </form>
      <p>{greetMsg}</p>
    </main>
  );
}

export default App;