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 { &--stacked {
display: none; 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; 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; position: relative;
} }
} }

View File

@@ -331,16 +331,30 @@ export function PageTransition({
return ( return (
<div className={`page-transition${isAnimating ? ' page-transition--animating' : ''}`}> <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 <div
key={layer.key} key={layer.key}
className={[ className={[
'page-transition__layer', 'page-transition__layer',
layer.status === 'exiting' ? 'page-transition__layer--exit' : '', layer.status === 'exiting' ? 'page-transition__layer--exit' : '',
layer.status === 'stacked' ? 'page-transition__layer--stacked' : '', layer.status === 'stacked' ? 'page-transition__layer--stacked' : '',
shouldKeepStacked ? 'page-transition__layer--stacked-keep' : '',
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
aria-hidden={layer.status !== 'current'}
inert={layer.status !== 'current'}
ref={ ref={
layer.status === 'exiting' layer.status === 'exiting'
? exitingLayerRef ? exitingLayerRef
@@ -351,7 +365,9 @@ export function PageTransition({
> >
{render(layer.location)} {render(layer.location)}
</div> </div>
))} );
});
})()}
</div> </div>
); );
} }

View File

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