🖼️ feat(header): improve logo loading UX with skeleton overlay
Ensure the header logo is shown only after the image has fully loaded to eliminate flicker:
• Introduced `logoLoaded` state to track image load completion.
• Pre-loaded the logo using `new Image()` inside a `useEffect` hook and set state on `onload`.
• Replaced the previous Skeleton wrapper with a stacked layout:
– A `Skeleton.Image` placeholder is rendered while the logo is loading.
– The real `<img>` element fades in with an opacity transition once both global
`isLoading` and `logoLoaded` are true.
• Added automatic reset of `logoLoaded` whenever the logo source changes.
• Removed redundant `onLoad` on the `<img>` tag to avoid double triggers.
• Ensured placeholder and image sizes match via absolute positioning to prevent layout shift.
This delivers a smoother visual experience by keeping the skeleton visible until the logo is completely ready and then revealing it seamlessly.
This commit is contained in:
@@ -60,6 +60,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||
const isMobile = useIsMobile();
|
||||
const [collapsed, toggleCollapsed] = useSidebarCollapsed();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [logoLoaded, setLogoLoaded] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
const [currentLang, setCurrentLang] = useState(i18n.language);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
@@ -226,6 +227,14 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||
}
|
||||
}, [statusState?.status]);
|
||||
|
||||
useEffect(() => {
|
||||
setLogoLoaded(false);
|
||||
if (!logo) return;
|
||||
const img = new Image();
|
||||
img.src = logo;
|
||||
img.onload = () => setLogoLoaded(true);
|
||||
}, [logo]);
|
||||
|
||||
const handleLanguageChange = (lang) => {
|
||||
i18n.changeLanguage(lang);
|
||||
setMobileMenuOpen(false);
|
||||
@@ -496,19 +505,20 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||
/>
|
||||
</div>
|
||||
<Link to="/" onClick={() => handleNavLinkClick('home')} className="flex items-center gap-2 group ml-2">
|
||||
<Skeleton
|
||||
loading={isLoading}
|
||||
active
|
||||
placeholder={
|
||||
<div className="relative w-8 h-8 md:w-8 md:h-8">
|
||||
{(isLoading || !logoLoaded) && (
|
||||
<Skeleton.Image
|
||||
active
|
||||
className="h-7 md:h-8 !rounded-full"
|
||||
style={{ width: 32, height: 32 }}
|
||||
className="absolute inset-0 !rounded-full"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<img src={logo} alt="logo" className="h-7 md:h-8 transition-transform duration-300 ease-in-out group-hover:scale-105 rounded-full" />
|
||||
</Skeleton>
|
||||
)}
|
||||
<img
|
||||
src={logo}
|
||||
alt="logo"
|
||||
className={`absolute inset-0 w-full h-full transition-opacity duration-200 group-hover:scale-105 rounded-full ${(!isLoading && logoLoaded) ? 'opacity-100' : 'opacity-0'}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton
|
||||
|
||||
Reference in New Issue
Block a user