feat(PageTransition): enhance layer management to prevent blank flashes during transitions

This commit is contained in:
LTbinglingfeng
2026-02-01 02:18:14 +08:00
parent f0735dbc1e
commit 237cca5680
3 changed files with 47 additions and 8 deletions

View File

@@ -29,6 +29,18 @@
&--stacked {
display: none;
// Keep the previous layer rendered (but invisible) to avoid a blank flash when popping back.
// Older stacked layers remain `display: none` for performance.
&.page-transition__layer--stacked-keep {
display: flex;
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
opacity: 0;
will-change: transform, opacity;
}
}
}
@@ -36,7 +48,7 @@
will-change: transform, opacity;
}
&--animating &__layer:not(.page-transition__layer--exit) {
&--animating &__layer:not(.page-transition__layer--exit):not(.page-transition__layer--stacked) {
position: relative;
}
}

View File

@@ -331,16 +331,30 @@ export function PageTransition({
return (
<div className={`page-transition${isAnimating ? ' page-transition--animating' : ''}`}>
{layers.map((layer) => (
{(() => {
const currentIndex = layers.findIndex((layer) => layer.status === 'current');
const resolvedCurrentIndex = currentIndex === -1 ? layers.length - 1 : currentIndex;
const keepStackedIndex = layers
.slice(0, resolvedCurrentIndex)
.map((layer, index) => ({ layer, index }))
.reverse()
.find(({ layer }) => layer.status === 'stacked')?.index;
return layers.map((layer, index) => {
const shouldKeepStacked = layer.status === 'stacked' && index === keepStackedIndex;
return (
<div
key={layer.key}
className={[
'page-transition__layer',
layer.status === 'exiting' ? 'page-transition__layer--exit' : '',
layer.status === 'stacked' ? 'page-transition__layer--stacked' : '',
shouldKeepStacked ? 'page-transition__layer--stacked-keep' : '',
]
.filter(Boolean)
.join(' ')}
aria-hidden={layer.status !== 'current'}
inert={layer.status !== 'current'}
ref={
layer.status === 'exiting'
? exitingLayerRef
@@ -351,7 +365,9 @@ export function PageTransition({
>
{render(layer.location)}
</div>
))}
);
});
})()}
</div>
);
}

View File

@@ -71,12 +71,18 @@ export function AiProvidersOpenAIEditLayout() {
const connectionStatus = useAuthStore((state) => state.connectionStatus);
const disableControls = connectionStatus !== 'connected';
const config = useConfigStore((state) => state.config);
const fetchConfig = useConfigStore((state) => state.fetchConfig);
const updateConfigValue = useConfigStore((state) => state.updateConfigValue);
const clearCache = useConfigStore((state) => state.clearCache);
const isCacheValid = useConfigStore((state) => state.isCacheValid);
const [providers, setProviders] = useState<OpenAIProviderConfig[]>([]);
const [loading, setLoading] = useState(true);
const [providers, setProviders] = useState<OpenAIProviderConfig[]>(
() => config?.openaiCompatibility ?? []
);
const [loading, setLoading] = useState(
() => !isCacheValid('openai-compatibility')
);
const [saving, setSaving] = useState(false);
const draftKey = useMemo(() => {
@@ -156,7 +162,10 @@ export function AiProvidersOpenAIEditLayout() {
useEffect(() => {
let cancelled = false;
setLoading(true);
const hasValidCache = isCacheValid('openai-compatibility');
if (!hasValidCache) {
setLoading(true);
}
fetchConfig('openai-compatibility')
.then((value) => {
@@ -176,7 +185,7 @@ export function AiProvidersOpenAIEditLayout() {
return () => {
cancelled = true;
};
}, [fetchConfig, showNotification, t]);
}, [fetchConfig, isCacheValid, showNotification, t]);
useEffect(() => {
if (loading) return;
@@ -322,6 +331,8 @@ export function AiProvidersOpenAIEditLayout() {
updateConfigValue,
]);
const resolvedLoading = !draft?.initialized;
return (
<Outlet
context={{
@@ -330,7 +341,7 @@ export function AiProvidersOpenAIEditLayout() {
invalidIndexParam,
invalidIndex,
disableControls,
loading,
loading: resolvedLoading,
saving,
form,
setForm,