mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
fix: render animated background via portal at z-index:-1 to stop implicit compositing
Previous isolation:isolate fix didn't work because: - position:fixed elements still see through isolation boundary - backdrop-filter samples all rendered content regardless of stacking context - The real issue: animated background at z-index:0 with will-change:transform forces Chrome to implicitly composite EVERY overlapping element Fix: render BackgroundRenderer via createPortal on document.body with z-index:-1, placing it below the root stacking context. This eliminates implicit compositing entirely. Also: - Remove backdrop-blur-xl from desktop header (was resampling bg 60fps) - Remove will-change:opacity from card ::after pseudo-element - Replace transition-all with transition-colors on header buttons
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Suspense, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { brandingApi } from '@/api/branding';
|
||||
import type { AnimationConfig, BackgroundType } from '@/components/ui/backgrounds/types';
|
||||
@@ -83,20 +84,25 @@ export function BackgroundRenderer() {
|
||||
? reduceMobileSettings(effectiveConfig.settings)
|
||||
: effectiveConfig.settings;
|
||||
|
||||
return (
|
||||
// Render via portal on document.body with z-index: -1.
|
||||
// This places the animated background BELOW the root stacking context,
|
||||
// preventing Chrome's implicit compositing from promoting every
|
||||
// overlapping element to its own GPU layer (the root cause of flickering).
|
||||
return createPortal(
|
||||
<div
|
||||
className="pointer-events-none fixed inset-0 z-0"
|
||||
className="pointer-events-none fixed inset-0"
|
||||
style={{
|
||||
zIndex: -1,
|
||||
opacity: effectiveConfig.opacity,
|
||||
filter: effectiveConfig.blur > 0 ? `blur(${effectiveConfig.blur}px)` : undefined,
|
||||
contain: 'strict',
|
||||
willChange: 'transform',
|
||||
transform: 'translateZ(0)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<Component settings={settings} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -280,19 +280,16 @@ export function AppShell({ children }: AppShellProps) {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Animated background — on its own GPU layer, below everything */}
|
||||
{/* Animated background renders via portal on document.body at z-index: -1 */}
|
||||
<BackgroundRenderer />
|
||||
|
||||
{/* Content layer — isolated from background to prevent backdrop-filter
|
||||
from resampling the animated background every frame (Chromium bug) */}
|
||||
<div className="relative z-10" style={{ isolation: 'isolate' }}>
|
||||
{/* Global components */}
|
||||
<WebSocketNotifications />
|
||||
<CampaignBonusNotifier />
|
||||
<SuccessNotificationModal />
|
||||
|
||||
{/* Desktop Header */}
|
||||
<header className="fixed left-0 right-0 top-0 z-50 hidden border-b border-dark-800/50 bg-dark-950/80 backdrop-blur-xl lg:block">
|
||||
<header className="fixed left-0 right-0 top-0 z-50 hidden border-b border-dark-800/50 bg-dark-950/95 lg:block">
|
||||
<div className="mx-auto grid h-14 max-w-6xl grid-cols-[auto_1fr_auto] items-center gap-4 px-6">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-2.5" onClick={handleNavClick}>
|
||||
@@ -381,7 +378,7 @@ export function AppShell({ children }: AppShellProps) {
|
||||
toggleTheme();
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-all duration-200 hover:bg-dark-700 hover:text-accent-400',
|
||||
'rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-colors duration-200 hover:bg-dark-700 hover:text-accent-400',
|
||||
!canToggleTheme && 'pointer-events-none invisible',
|
||||
)}
|
||||
title={isDark ? t('theme.light') || 'Light mode' : t('theme.dark') || 'Dark mode'}
|
||||
@@ -395,7 +392,7 @@ export function AppShell({ children }: AppShellProps) {
|
||||
haptic.impact('light');
|
||||
logout();
|
||||
}}
|
||||
className="rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-all duration-200 hover:bg-dark-700 hover:text-accent-400"
|
||||
className="rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-colors duration-200 hover:bg-dark-700 hover:text-accent-400"
|
||||
title={t('nav.logout')}
|
||||
>
|
||||
<LogoutIcon className="h-5 w-5" />
|
||||
@@ -436,6 +433,5 @@ export function AppShell({ children }: AppShellProps) {
|
||||
wheelEnabled={wheelEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -400,7 +400,6 @@ img.twemoji {
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.bento-card-hover:hover::after {
|
||||
|
||||
Reference in New Issue
Block a user