diff --git a/stash-web/src/App.css b/stash-web/src/App.css index 55ab461..c5ed1ff 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -1422,3 +1422,17 @@ html.theme-transition .site-background { grid-template-columns: minmax(0, 1fr); } } + +/* 明暗切换:支持的浏览器直接对整页做快照交叉淡入,避免背景图和底色各变各的 */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 160ms; + animation-timing-function: var(--su-ease-in-out); +} + +@media (prefers-reduced-motion: reduce) { + ::view-transition-old(root), + ::view-transition-new(root) { + animation: none; + } +} diff --git a/stash-web/src/AppNext.tsx b/stash-web/src/AppNext.tsx index bb3fc4c..74d27c5 100644 --- a/stash-web/src/AppNext.tsx +++ b/stash-web/src/AppNext.tsx @@ -308,8 +308,26 @@ export default function AppNext() { useEffect(() => { const root = document.documentElement if (root.dataset.scheme === scheme) return + + const apply = () => { + root.dataset.scheme = scheme + } + const doc = document as Document & { + startViewTransition?: (callback: () => void) => unknown + } + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + + if (!reduceMotion && typeof doc.startViewTransition === 'function') { + try { + doc.startViewTransition(apply) + return + } catch { + // 浏览器正在跑另一段过渡时退回 CSS 过渡 + } + } + root.classList.add('theme-transition') - root.dataset.scheme = scheme + apply() const timer = window.setTimeout(() => root.classList.remove('theme-transition'), 180) return () => { window.clearTimeout(timer)