fix(theme): 跟随系统切换改为瞬切,放弃 VT 动画
诊断确认:跟随系统与手动点击的 VT 流程在 JS/CSS 层完全等价(animationName、 --tt-r、bg 翻转、clip-path 全部正确),但视觉上跟随系统场景下圆形展开动画 不显示,仅瞬切。系统偏好变化是后台事件,VT 在此上下文下动画不可靠。 改用设置语义的瞬切:JS 侧新增 applyResolvedTheme(isDark) 入口(非翻转语义, 直接 add/remove dark class),Rust effect 改调 __applyResolvedTheme 传实际 明暗值,移除圆心计算逻辑。职责清晰: - 手动点击 → __startThemeTransition → VT 圆形展开(用户手势,动画可靠) - 跟随系统 → __applyResolvedTheme → 瞬切(后台同步语义)
This commit is contained in:
parent
eb3087edf4
commit
cb77d28a68
@ -1,13 +1,15 @@
|
|||||||
import { initPostContent } from './post-content';
|
import { initPostContent } from './post-content';
|
||||||
import { startThemeTransition } from './theme-transition';
|
import { applyResolvedTheme, startThemeTransition } from './theme-transition';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__initPostContent: (selector: string) => void;
|
__initPostContent: (selector: string) => void;
|
||||||
__startThemeTransition: (x: number, y: number) => void;
|
__startThemeTransition: (x: number, y: number) => void;
|
||||||
|
__applyResolvedTheme: (isDark: boolean) => void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__initPostContent = initPostContent;
|
window.__initPostContent = initPostContent;
|
||||||
window.__startThemeTransition = startThemeTransition;
|
window.__startThemeTransition = startThemeTransition;
|
||||||
|
window.__applyResolvedTheme = applyResolvedTheme;
|
||||||
|
|||||||
@ -40,6 +40,18 @@ function applyDarkClass(isDark: boolean): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接设置 <html> 的 dark class(设置语义,非翻转)。
|
||||||
|
*
|
||||||
|
* 用于跟随系统偏好变化时的后台同步:系统偏好变化是后台事件,View Transitions
|
||||||
|
* 在此上下文下动画不可靠(实测圆形展开不显示,仅瞬切),故跟随系统场景不走
|
||||||
|
* startThemeTransition 的 VT 路径,改用此函数直接同步 class,做无动画的瞬切。
|
||||||
|
* 手动点击主题按钮仍走 startThemeTransition,保留圆形展开动画。
|
||||||
|
*/
|
||||||
|
export function applyResolvedTheme(isDark: boolean): void {
|
||||||
|
applyDarkClass(isDark);
|
||||||
|
}
|
||||||
|
|
||||||
export function startThemeTransition(x: number, y: number): void {
|
export function startThemeTransition(x: number, y: number): void {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const isDark = !html.classList.contains('dark');
|
const isDark = !html.classList.contains('dark');
|
||||||
|
|||||||
44
src/theme.rs
44
src/theme.rs
@ -206,9 +206,12 @@ pub fn use_theme_provider() -> Signal<Theme> {
|
|||||||
//
|
//
|
||||||
// 仅在 System 模式(theme == System)且 resolved 实际翻转时触发:Light/Dark 显式
|
// 仅在 System 模式(theme == System)且 resolved 实际翻转时触发:Light/Dark 显式
|
||||||
// 模式下 resolved 由 theme 决定,DOM 已由 ThemeToggle::onclick 全权处理,effect
|
// 模式下 resolved 由 theme 决定,DOM 已由 ThemeToggle::onclick 全权处理,effect
|
||||||
// 不介入,避免与手动点击的 VT 动画重复触发。以主题按钮位置为圆心复用同一条 JS
|
// 不介入,避免与手动点击的 VT 动画重复触发。
|
||||||
// 路径(__startThemeTransition 从 DOM 现状推导目标明暗),让自动切换与手动点击
|
//
|
||||||
// 的动画起点一致;屏幕中心圆心会让圆形展开近似「整体覆盖」,被误看成淡入淡出。
|
// 采用无动画的瞬切(__applyResolvedTheme,设置语义)而非 VT 圆形展开:系统偏好
|
||||||
|
// 变化是后台事件,实测 View Transitions 在此上下文下动画不显示(VT 流程虽正常
|
||||||
|
// 完成、CSS 也正确挂上 tt-expand,但视觉上仅瞬切),故跟随系统走瞬切更可靠;
|
||||||
|
// 手动点击仍走 __startThemeTransition 保留圆形展开动画。
|
||||||
//
|
//
|
||||||
// 首次挂载自然跳过:effect 通过 prev 信号记录上一次 resolved,初次运行 prev 为
|
// 首次挂载自然跳过:effect 通过 prev 信号记录上一次 resolved,初次运行 prev 为
|
||||||
// None,此时 DOM 已由 ThemePreload 脚本设为正确状态,直接对齐 prev 后返回。
|
// None,此时 DOM 已由 ThemePreload 脚本设为正确状态,直接对齐 prev 后返回。
|
||||||
@ -221,7 +224,7 @@ pub fn use_theme_provider() -> Signal<Theme> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let current = resolved();
|
let current = resolved();
|
||||||
// 首次运行:DOM 已由 ThemePreload 设好,仅记录基线,不触发动画。
|
// 首次运行:DOM 已由 ThemePreload 设好,仅记录基线,不触发同步。
|
||||||
let Some(prev) = prev_resolved() else {
|
let Some(prev) = prev_resolved() else {
|
||||||
prev_resolved.set(Some(current));
|
prev_resolved.set(Some(current));
|
||||||
return;
|
return;
|
||||||
@ -231,40 +234,17 @@ pub fn use_theme_provider() -> Signal<Theme> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prev_resolved.set(Some(current));
|
prev_resolved.set(Some(current));
|
||||||
// resolved 翻转 → 触发 VT 圆形展开动画。
|
// resolved 翻转 → 瞬切同步 dark class(设置语义,非翻转)。
|
||||||
let Some(window) = web_sys::window() else {
|
let Some(window) = web_sys::window() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// 圆心优先取主题切换按钮(.theme-toggle)的中心,与手动点击的动画起点一致;
|
let key = "__applyResolvedTheme".into();
|
||||||
// 按钮不在 DOM(如未渲染导航栏的页面)时回退到视口中心。
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
let (x, y) = window
|
|
||||||
.document()
|
|
||||||
.and_then(|doc| doc.query_selector(".theme-toggle").ok().flatten())
|
|
||||||
.map(|el| {
|
|
||||||
let r = el.unchecked_into::<web_sys::Element>().get_bounding_client_rect();
|
|
||||||
(r.x() + r.width() / 2.0, r.y() + r.height() / 2.0)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
let w = window
|
|
||||||
.inner_width()
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.as_f64())
|
|
||||||
.map(|v| v / 2.0)
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
let h = window
|
|
||||||
.inner_height()
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.as_f64())
|
|
||||||
.map(|v| v / 2.0)
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
(w, h)
|
|
||||||
});
|
|
||||||
let key = "__startThemeTransition".into();
|
|
||||||
if let Ok(fn_val) = js_sys::Reflect::get(&window, &key) {
|
if let Ok(fn_val) = js_sys::Reflect::get(&window, &key) {
|
||||||
if !fn_val.is_undefined() && !fn_val.is_null() {
|
if !fn_val.is_undefined() && !fn_val.is_null() {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
let fn_obj = fn_val.unchecked_into::<js_sys::Function>();
|
let fn_obj = fn_val.unchecked_into::<js_sys::Function>();
|
||||||
let _ = fn_obj.call2(&window, &x.into(), &y.into());
|
let is_dark = current == ResolvedTheme::Dark;
|
||||||
|
let _ = fn_obj.call1(&window, &is_dark.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user