mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
fix(ui): prevent light flash on dark startup (#5637)
* fix(ui): prevent light flash on dark startup * fix(window): apply initial dark/light theme to prevent white flash * refactor(boot): optimize config fetch and theme fallback * fix: system theme detection * fix(window): remove black flash before loader by aligning initial paint with theme
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
use tauri::WebviewWindow;
|
||||
use dark_light::{Mode as SystemTheme, detect as detect_system_theme};
|
||||
use tauri::utils::config::Color;
|
||||
use tauri::{Theme, WebviewWindow};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::handle,
|
||||
utils::resolve::window_script::{INITIAL_LOADING_OVERLAY, WINDOW_INITIAL_SCRIPT},
|
||||
utils::resolve::window_script::{INITIAL_LOADING_OVERLAY, build_window_initial_script},
|
||||
};
|
||||
use clash_verge_logging::{Type, logging_error};
|
||||
|
||||
const DARK_BACKGROUND_COLOR: Color = Color(46, 48, 61, 255); // #2E303D
|
||||
const LIGHT_BACKGROUND_COLOR: Color = Color(245, 245, 245, 255); // #F5F5F5
|
||||
const DARK_BACKGROUND_HEX: &str = "#2E303D";
|
||||
const LIGHT_BACKGROUND_HEX: &str = "#F5F5F5";
|
||||
|
||||
// 定义默认窗口尺寸常量
|
||||
const DEFAULT_WIDTH: f64 = 940.0;
|
||||
const DEFAULT_HEIGHT: f64 = 700.0;
|
||||
@@ -21,8 +28,37 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
||||
let config = Config::verge().await;
|
||||
let latest = config.latest_arc();
|
||||
let start_page = latest.start_page.as_deref().unwrap_or("/");
|
||||
let initial_theme_mode = match latest.theme_mode.as_deref() {
|
||||
Some("dark") => "dark",
|
||||
Some("light") => "light",
|
||||
_ => "system",
|
||||
};
|
||||
|
||||
match tauri::WebviewWindowBuilder::new(
|
||||
let resolved_theme = match initial_theme_mode {
|
||||
"dark" => Some(Theme::Dark),
|
||||
"light" => Some(Theme::Light),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let prefers_dark_background = match resolved_theme {
|
||||
Some(Theme::Dark) => true,
|
||||
Some(Theme::Light) => false,
|
||||
_ => !matches!(detect_system_theme().ok(), Some(SystemTheme::Light)),
|
||||
};
|
||||
|
||||
let background_color = if prefers_dark_background {
|
||||
DARK_BACKGROUND_COLOR
|
||||
} else {
|
||||
LIGHT_BACKGROUND_COLOR
|
||||
};
|
||||
|
||||
let initial_script = build_window_initial_script(
|
||||
initial_theme_mode,
|
||||
DARK_BACKGROUND_HEX,
|
||||
LIGHT_BACKGROUND_HEX,
|
||||
);
|
||||
|
||||
let mut builder = tauri::WebviewWindowBuilder::new(
|
||||
app_handle,
|
||||
"main", /* the unique window label */
|
||||
tauri::WebviewUrl::App(start_page.into()),
|
||||
@@ -34,11 +70,21 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
||||
.fullscreen(false)
|
||||
.inner_size(DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
||||
.min_inner_size(MINIMAL_WIDTH, MINIMAL_HEIGHT)
|
||||
.visible(true) // 立即显示窗口,避免用户等待
|
||||
.initialization_script(WINDOW_INITIAL_SCRIPT)
|
||||
.build()
|
||||
{
|
||||
.visible(false) // 等待主题色准备好后再展示,避免启动色差
|
||||
.initialization_script(&initial_script);
|
||||
|
||||
if let Some(theme) = resolved_theme {
|
||||
builder = builder.theme(Some(theme));
|
||||
}
|
||||
|
||||
builder = builder.background_color(background_color);
|
||||
|
||||
match builder.build() {
|
||||
Ok(window) => {
|
||||
logging_error!(
|
||||
Type::Window,
|
||||
window.set_background_color(Some(background_color))
|
||||
);
|
||||
logging_error!(Type::Window, window.eval(INITIAL_LOADING_OVERLAY));
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,121 @@
|
||||
pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
||||
pub fn build_window_initial_script(
|
||||
initial_theme_mode: &str,
|
||||
dark_background: &str,
|
||||
light_background: &str,
|
||||
) -> String {
|
||||
let theme_mode = match initial_theme_mode {
|
||||
"dark" => "dark",
|
||||
"light" => "light",
|
||||
_ => "system",
|
||||
};
|
||||
format!(
|
||||
r#"
|
||||
window.__VERGE_INITIAL_THEME_MODE = "{theme_mode}";
|
||||
window.__VERGE_INITIAL_THEME_COLORS = {{
|
||||
darkBg: "{dark_background}",
|
||||
lightBg: "{light_background}",
|
||||
}};
|
||||
{script}
|
||||
"#,
|
||||
theme_mode = theme_mode,
|
||||
dark_background = dark_background,
|
||||
light_background = light_background,
|
||||
script = WINDOW_INITIAL_SCRIPT,
|
||||
)
|
||||
}
|
||||
|
||||
pub const WINDOW_INITIAL_SCRIPT: &str = r##"
|
||||
console.log('[Tauri] 窗口初始化脚本开始执行');
|
||||
|
||||
function createLoadingOverlay() {
|
||||
const initialColors = (() => {
|
||||
try {
|
||||
const colors = window.__VERGE_INITIAL_THEME_COLORS;
|
||||
if (colors && typeof colors === "object") {
|
||||
const { darkBg, lightBg } = colors;
|
||||
if (typeof darkBg === "string" && typeof lightBg === "string") {
|
||||
return { darkBg, lightBg };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Tauri] 读取初始主题颜色失败:", error);
|
||||
}
|
||||
return { darkBg: "#2E303D", lightBg: "#F5F5F5" };
|
||||
})();
|
||||
|
||||
if (document.getElementById('initial-loading-overlay')) {
|
||||
console.log('[Tauri] 加载指示器已存在');
|
||||
const prefersDark = (() => {
|
||||
try {
|
||||
return !!window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)")?.matches;
|
||||
} catch (error) {
|
||||
console.warn("[Tauri] 读取系统主题失败:", error);
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
const initialThemeMode = typeof window.__VERGE_INITIAL_THEME_MODE === "string"
|
||||
? window.__VERGE_INITIAL_THEME_MODE
|
||||
: "system";
|
||||
|
||||
let initialTheme = prefersDark ? "dark" : "light";
|
||||
if (initialThemeMode === "dark") {
|
||||
initialTheme = "dark";
|
||||
} else if (initialThemeMode === "light") {
|
||||
initialTheme = "light";
|
||||
}
|
||||
|
||||
const applyInitialTheme = (theme) => {
|
||||
const isDark = theme === "dark";
|
||||
const root = document.documentElement;
|
||||
const bgColor = isDark ? initialColors.darkBg : initialColors.lightBg;
|
||||
const textColor = isDark ? "#ffffff" : "#333";
|
||||
if (root) {
|
||||
root.dataset.theme = theme;
|
||||
root.style.setProperty("--bg-color", bgColor);
|
||||
root.style.setProperty("--text-color", textColor);
|
||||
root.style.colorScheme = isDark ? "dark" : "light";
|
||||
root.style.backgroundColor = bgColor;
|
||||
root.style.color = textColor;
|
||||
}
|
||||
const paintBody = () => {
|
||||
if (!document.body) return;
|
||||
document.body.style.backgroundColor = bgColor;
|
||||
document.body.style.color = textColor;
|
||||
};
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", paintBody, { once: true });
|
||||
} else {
|
||||
paintBody();
|
||||
}
|
||||
try {
|
||||
localStorage.setItem("verge-theme-mode-cache", theme);
|
||||
} catch (error) {
|
||||
console.warn("[Tauri] 缓存主题模式失败:", error);
|
||||
}
|
||||
return isDark;
|
||||
};
|
||||
|
||||
const isDarkTheme = applyInitialTheme(initialTheme);
|
||||
|
||||
const getInitialOverlayColors = () => ({
|
||||
bg: isDarkTheme ? initialColors.darkBg : initialColors.lightBg,
|
||||
text: isDarkTheme ? "#ffffff" : "#333",
|
||||
spinnerTrack: isDarkTheme ? "#3a3a3a" : "#e3e3e3",
|
||||
spinnerTop: isDarkTheme ? "#0a84ff" : "#3498db",
|
||||
});
|
||||
|
||||
function createOrUpdateLoadingOverlay() {
|
||||
const colors = getInitialOverlayColors();
|
||||
const existed = document.getElementById('initial-loading-overlay');
|
||||
|
||||
const applyOverlayColors = (element) => {
|
||||
element.style.setProperty("--bg-color", colors.bg);
|
||||
element.style.setProperty("--text-color", colors.text);
|
||||
element.style.setProperty("--spinner-track", colors.spinnerTrack);
|
||||
element.style.setProperty("--spinner-top", colors.spinnerTop);
|
||||
};
|
||||
|
||||
if (existed) {
|
||||
console.log('[Tauri] 复用已有加载指示器');
|
||||
applyOverlayColors(existed);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,7 +125,7 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
||||
loadingDiv.innerHTML = `
|
||||
<div style="
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--bg-color, #f5f5f5); color: var(--text-color, #333);
|
||||
background: var(--bg-color, ${colors.bg}); color: var(--text-color, ${colors.text});
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; z-index: 9999;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
@@ -22,8 +133,8 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
||||
">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<div style="
|
||||
width: 40px; height: 40px; border: 3px solid #e3e3e3;
|
||||
border-top: 3px solid #3498db; border-radius: 50%;
|
||||
width: 40px; height: 40px; border: 3px solid var(--spinner-track, ${colors.spinnerTrack});
|
||||
border-top: 3px solid var(--spinner-top, ${colors.spinnerTop}); border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
"></div>
|
||||
</div>
|
||||
@@ -34,12 +145,11 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root { --bg-color: #1a1a1a; --text-color: #ffffff; }
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
applyOverlayColors(loadingDiv);
|
||||
|
||||
if (document.body) {
|
||||
document.body.appendChild(loadingDiv);
|
||||
} else {
|
||||
@@ -51,16 +161,16 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r#"
|
||||
}
|
||||
}
|
||||
|
||||
createLoadingOverlay();
|
||||
createOrUpdateLoadingOverlay();
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', createLoadingOverlay);
|
||||
document.addEventListener('DOMContentLoaded', createOrUpdateLoadingOverlay);
|
||||
} else {
|
||||
createLoadingOverlay();
|
||||
createOrUpdateLoadingOverlay();
|
||||
}
|
||||
|
||||
console.log('[Tauri] 窗口初始化脚本执行完成');
|
||||
"#;
|
||||
"##;
|
||||
|
||||
pub const INITIAL_LOADING_OVERLAY: &str = r"
|
||||
const overlay = document.getElementById('initial-loading-overlay');
|
||||
|
||||
Reference in New Issue
Block a user