فهرست منبع

🖼️ 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.
t0ng7u 11 ماه پیش
والد
کامیت
fd7a4461cc
1فایلهای تغییر یافته به همراه20 افزوده شده و 10 حذف شده
  1. 20 10
      web/src/components/layout/HeaderBar.js

+ 20 - 10
web/src/components/layout/HeaderBar.js

@@ -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