mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
fix: fonts
This commit is contained in:
parent
ff2f9ed97e
commit
5ab1aec408
@ -2,11 +2,9 @@
|
|||||||
/* Only includes above-the-fold styles needed for initial paint */
|
/* Only includes above-the-fold styles needed for initial paint */
|
||||||
/* This CSS should not conflict with main styles - only prevent layout shift */
|
/* This CSS should not conflict with main styles - only prevent layout shift */
|
||||||
|
|
||||||
/* Import Google Fonts with optimal loading */
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Sen:wght@400;600;700&display=swap');
|
|
||||||
|
|
||||||
/* Prevent layout shift - reserve space */
|
/* Prevent layout shift - reserve space */
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@ -30,7 +28,7 @@ html, body {
|
|||||||
.navbar .navbar__brand {
|
.navbar .navbar__brand {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reserve space for GitHub star button to prevent shift */
|
/* Reserve space for GitHub star button to prevent shift */
|
||||||
.header-github-link.navbar-link-outlined {
|
.header-github-link.navbar-link-outlined {
|
||||||
min-width: 120px; /* Reserve space for "Star 12.5k" */
|
min-width: 120px; /* Reserve space for "Star 12.5k" */
|
||||||
@ -38,7 +36,7 @@ html, body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure navbar items don't shift */
|
/* Ensure navbar items don't shift */
|
||||||
.navbar__items--right {
|
.navbar__items--right {
|
||||||
min-width: 450px; /* Stabilize right navbar width */
|
min-width: 450px; /* Stabilize right navbar width */
|
||||||
@ -85,9 +83,9 @@ main {
|
|||||||
aside[class*="docSidebarContainer"] {
|
aside[class*="docSidebarContainer"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
||||||
import Head from '@docusaurus/Head';
|
|
||||||
|
|
||||||
export default function FontLoader(): React.JSX.Element {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ExecutionEnvironment.canUseDOM) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font loading strategy
|
|
||||||
const loadFonts = async () => {
|
|
||||||
// Check if fonts are already loaded
|
|
||||||
if (document.fonts && document.fonts.ready) {
|
|
||||||
try {
|
|
||||||
await document.fonts.ready;
|
|
||||||
document.documentElement.classList.add('fonts-loaded');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Font loading error:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create font face with font-display: swap
|
|
||||||
const senFontFace = new FontFace(
|
|
||||||
'Sen',
|
|
||||||
`url('/fonts/Sen-Regular.woff2') format('woff2'),
|
|
||||||
url('/fonts/Sen-Regular.woff') format('woff')`,
|
|
||||||
{
|
|
||||||
weight: '400',
|
|
||||||
style: 'normal',
|
|
||||||
display: 'swap' // Critical for preventing invisible text
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const senBoldFontFace = new FontFace(
|
|
||||||
'Sen',
|
|
||||||
`url('/fonts/Sen-Bold.woff2') format('woff2'),
|
|
||||||
url('/fonts/Sen-Bold.woff') format('woff')`,
|
|
||||||
{
|
|
||||||
weight: '700',
|
|
||||||
style: 'normal',
|
|
||||||
display: 'swap'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load fonts asynchronously
|
|
||||||
try {
|
|
||||||
const loadedFonts = await Promise.all([
|
|
||||||
senFontFace.load(),
|
|
||||||
senBoldFontFace.load()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Add fonts to document
|
|
||||||
loadedFonts.forEach(font => {
|
|
||||||
(document.fonts as any).add(font);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mark fonts as loaded
|
|
||||||
document.documentElement.classList.add('custom-fonts-loaded');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load custom fonts:', error);
|
|
||||||
// Fallback to system fonts is automatic due to font stack
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load fonts with requestIdleCallback for better performance
|
|
||||||
if ('requestIdleCallback' in window) {
|
|
||||||
window.requestIdleCallback(() => loadFonts(), { timeout: 3000 });
|
|
||||||
} else {
|
|
||||||
// Fallback for browsers without requestIdleCallback
|
|
||||||
setTimeout(loadFonts, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add CSS for font loading states
|
|
||||||
const fontLoadingStyles = document.createElement('style');
|
|
||||||
fontLoadingStyles.textContent = `
|
|
||||||
/* Use system fonts initially for faster paint */
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
||||||
Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Apply custom fonts when loaded */
|
|
||||||
.custom-fonts-loaded body {
|
|
||||||
font-family: "Sen", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
||||||
Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Smooth transition when fonts load */
|
|
||||||
body, h1, h2, h3, h4, h5, h6, p, a, li {
|
|
||||||
transition: font-family 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent layout shift from font loading */
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-synthesis: none;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(fontLoadingStyles);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// Cleanup if needed
|
|
||||||
if (fontLoadingStyles.parentNode) {
|
|
||||||
fontLoadingStyles.parentNode.removeChild(fontLoadingStyles);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Head>
|
|
||||||
{/* Preload font files for faster loading */}
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/Sen-Regular.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossOrigin="anonymous"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/Sen-Bold.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossOrigin="anonymous"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Font-face declarations with font-display: swap */}
|
|
||||||
<style>{`
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Sen';
|
|
||||||
src: url('/fonts/Sen-Regular.woff2') format('woff2'),
|
|
||||||
url('/fonts/Sen-Regular.woff') format('woff');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap; /* Critical: shows text immediately */
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Sen';
|
|
||||||
src: url('/fonts/Sen-Bold.woff2') format('woff2'),
|
|
||||||
url('/fonts/Sen-Bold.woff') format('woff');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Sen';
|
|
||||||
src: url('/fonts/Sen-SemiBold.woff2') format('woff2'),
|
|
||||||
url('/fonts/Sen-SemiBold.woff') format('woff');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fallback for older browsers */
|
|
||||||
@supports not (font-display: swap) {
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
||||||
Roboto, Oxygen, Ubuntu, sans-serif;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</Head>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,253 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
||||||
import Head from '@docusaurus/Head';
|
|
||||||
|
|
||||||
export default function LayoutStabilizer(): React.JSX.Element {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ExecutionEnvironment.canUseDOM) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add layout stability classes
|
|
||||||
const stabilizeLayout = () => {
|
|
||||||
// Reserve space for navbar
|
|
||||||
const navbar = document.querySelector('.navbar');
|
|
||||||
if (navbar && !navbar.classList.contains('layout-reserved')) {
|
|
||||||
navbar.classList.add('layout-reserved');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserve space for sidebar
|
|
||||||
const sidebar = document.querySelector('aside[class*="docSidebarContainer"]');
|
|
||||||
if (sidebar && !sidebar.classList.contains('layout-reserved')) {
|
|
||||||
sidebar.classList.add('layout-reserved');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stabilize main container
|
|
||||||
const mainContainer = document.querySelector('main[class*="docMainContainer"]');
|
|
||||||
if (mainContainer && !mainContainer.classList.contains('layout-stabilized')) {
|
|
||||||
mainContainer.classList.add('layout-stabilized');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add skeleton loading for slow content
|
|
||||||
const addSkeletonLoading = () => {
|
|
||||||
const contentContainers = document.querySelectorAll('article:empty, .markdown:empty');
|
|
||||||
contentContainers.forEach(container => {
|
|
||||||
if (!container.querySelector('.skeleton-loader')) {
|
|
||||||
const skeleton = document.createElement('div');
|
|
||||||
skeleton.className = 'skeleton-loader';
|
|
||||||
skeleton.innerHTML = `
|
|
||||||
<div class="skeleton skeleton-title"></div>
|
|
||||||
<div class="skeleton skeleton-text"></div>
|
|
||||||
<div class="skeleton skeleton-text"></div>
|
|
||||||
<div class="skeleton skeleton-text" style="width: 80%"></div>
|
|
||||||
`;
|
|
||||||
container.appendChild(skeleton);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
addSkeletonLoading();
|
|
||||||
|
|
||||||
// Observe images for lazy loading with proper dimensions
|
|
||||||
const images = document.querySelectorAll('img:not([data-stabilized])');
|
|
||||||
images.forEach(img => {
|
|
||||||
const imgElement = img as HTMLImageElement;
|
|
||||||
|
|
||||||
// If image has intrinsic dimensions, reserve space
|
|
||||||
if (imgElement.naturalWidth && imgElement.naturalHeight) {
|
|
||||||
const aspectRatio = imgElement.naturalHeight / imgElement.naturalWidth;
|
|
||||||
imgElement.style.aspectRatio = `${imgElement.naturalWidth} / ${imgElement.naturalHeight}`;
|
|
||||||
} else {
|
|
||||||
// Set a default aspect ratio for common image types
|
|
||||||
if (imgElement.src.includes('screenshot') || imgElement.src.includes('demo')) {
|
|
||||||
imgElement.style.aspectRatio = '16 / 9';
|
|
||||||
} else if (imgElement.src.includes('logo') || imgElement.src.includes('icon')) {
|
|
||||||
imgElement.style.aspectRatio = '1 / 1';
|
|
||||||
} else {
|
|
||||||
imgElement.style.aspectRatio = '4 / 3'; // Default aspect ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imgElement.setAttribute('data-stabilized', 'true');
|
|
||||||
imgElement.loading = 'lazy'; // Enable native lazy loading
|
|
||||||
imgElement.decoding = 'async'; // Async decoding for better performance
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run stabilization immediately
|
|
||||||
stabilizeLayout();
|
|
||||||
|
|
||||||
// Re-run on route changes (for SPA navigation)
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
stabilizeLayout();
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Monitor viewport changes
|
|
||||||
let resizeTimer: NodeJS.Timeout;
|
|
||||||
const handleResize = () => {
|
|
||||||
clearTimeout(resizeTimer);
|
|
||||||
resizeTimer = setTimeout(() => {
|
|
||||||
stabilizeLayout();
|
|
||||||
}, 250);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize, { passive: true });
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
clearTimeout(resizeTimer);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Head>
|
|
||||||
<style>{`
|
|
||||||
/* Layout Stability Styles */
|
|
||||||
|
|
||||||
/* Reserve space for navbar */
|
|
||||||
.navbar.layout-reserved {
|
|
||||||
min-height: 56px;
|
|
||||||
contain: layout style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reserve space for sidebar */
|
|
||||||
aside.layout-reserved {
|
|
||||||
min-width: 250px;
|
|
||||||
contain: layout style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stabilize main container */
|
|
||||||
main.layout-stabilized {
|
|
||||||
min-height: calc(100vh - 56px);
|
|
||||||
contain: layout;
|
|
||||||
will-change: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent layout shifts from async content */
|
|
||||||
article {
|
|
||||||
min-height: 400px;
|
|
||||||
contain: layout style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Image stability */
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
img[data-stabilized] {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skeleton loading styles */
|
|
||||||
.skeleton-loader {
|
|
||||||
padding: 1rem;
|
|
||||||
animation: fade-in 0.3s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton {
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
#f0f0f0 25%,
|
|
||||||
#e0e0e0 50%,
|
|
||||||
#f0f0f0 75%
|
|
||||||
);
|
|
||||||
background-size: 200% 100%;
|
|
||||||
animation: skeleton-shimmer 1.5s infinite;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-title {
|
|
||||||
height: 2rem;
|
|
||||||
width: 60%;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-text {
|
|
||||||
height: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes skeleton-shimmer {
|
|
||||||
0% { background-position: 200% 0; }
|
|
||||||
100% { background-position: -200% 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent CLS from font loading */
|
|
||||||
body {
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stabilize buttons and interactive elements */
|
|
||||||
button, a.button {
|
|
||||||
min-height: 36px;
|
|
||||||
contain: layout style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stabilize form elements */
|
|
||||||
input, textarea, select {
|
|
||||||
min-height: 36px;
|
|
||||||
contain: layout style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Code blocks stability */
|
|
||||||
pre {
|
|
||||||
min-height: 60px;
|
|
||||||
contain: layout;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table stability */
|
|
||||||
table {
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
contain: layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile stability */
|
|
||||||
@media (max-width: 996px) {
|
|
||||||
main.layout-stabilized {
|
|
||||||
min-height: calc(100vh - 60px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar.layout-reserved {
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent layout shift from lazy-loaded content */
|
|
||||||
[data-lazy-load] {
|
|
||||||
min-height: 100px;
|
|
||||||
contain: layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Optimize Cumulative Layout Shift */
|
|
||||||
* {
|
|
||||||
/* Prevent margin collapse issues */
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6, p, ul, ol {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</Head>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
|
||||||
|
|
||||||
export default function OptimizedStyles() {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ExecutionEnvironment.canUseDOM) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to load CSS asynchronously
|
|
||||||
const loadStylesheet = (href: string, media: string = 'all') => {
|
|
||||||
// Check if already loaded
|
|
||||||
if (document.querySelector(`link[href="${href}"]`)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create preload link
|
|
||||||
const preloadLink = document.createElement('link');
|
|
||||||
preloadLink.rel = 'preload';
|
|
||||||
preloadLink.as = 'style';
|
|
||||||
preloadLink.href = href;
|
|
||||||
|
|
||||||
// Create actual stylesheet link
|
|
||||||
const styleLink = document.createElement('link');
|
|
||||||
styleLink.rel = 'stylesheet';
|
|
||||||
styleLink.href = href;
|
|
||||||
styleLink.media = media;
|
|
||||||
|
|
||||||
// Once preloaded, apply the stylesheet
|
|
||||||
preloadLink.onload = () => {
|
|
||||||
preloadLink.onload = null;
|
|
||||||
// Switch from preload to stylesheet
|
|
||||||
preloadLink.rel = 'stylesheet';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fallback for browsers that don't support preload
|
|
||||||
const noscriptFallback = document.createElement('noscript');
|
|
||||||
const fallbackLink = document.createElement('link');
|
|
||||||
fallbackLink.rel = 'stylesheet';
|
|
||||||
fallbackLink.href = href;
|
|
||||||
noscriptFallback.appendChild(fallbackLink);
|
|
||||||
|
|
||||||
// Add to document head
|
|
||||||
document.head.appendChild(preloadLink);
|
|
||||||
document.head.appendChild(noscriptFallback);
|
|
||||||
|
|
||||||
// Also add regular link with print media to load in background
|
|
||||||
const printLink = document.createElement('link');
|
|
||||||
printLink.rel = 'stylesheet';
|
|
||||||
printLink.href = href;
|
|
||||||
printLink.media = 'print';
|
|
||||||
printLink.onload = function() {
|
|
||||||
(this as HTMLLinkElement).media = media;
|
|
||||||
};
|
|
||||||
document.head.appendChild(printLink);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load non-critical styles after initial render
|
|
||||||
const loadNonCriticalStyles = () => {
|
|
||||||
// Load additional styles based on viewport
|
|
||||||
if (window.innerWidth > 996) {
|
|
||||||
// Desktop-specific styles
|
|
||||||
loadStylesheet('/assets/css/desktop-enhancements.css', 'screen and (min-width: 997px)');
|
|
||||||
} else {
|
|
||||||
// Mobile-specific styles
|
|
||||||
loadStylesheet('/assets/css/mobile-enhancements.css', 'screen and (max-width: 996px)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load animation and interaction styles
|
|
||||||
loadStylesheet('/assets/css/animations.css', 'all');
|
|
||||||
|
|
||||||
// Load theme-specific styles
|
|
||||||
const theme = document.documentElement.getAttribute('data-theme');
|
|
||||||
if (theme === 'dark') {
|
|
||||||
loadStylesheet('/assets/css/dark-theme.css', 'all');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use requestIdleCallback if available, otherwise setTimeout
|
|
||||||
if ('requestIdleCallback' in window) {
|
|
||||||
window.requestIdleCallback(loadNonCriticalStyles, { timeout: 2000 });
|
|
||||||
} else {
|
|
||||||
setTimeout(loadNonCriticalStyles, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimize existing stylesheets
|
|
||||||
const optimizeExistingStyles = () => {
|
|
||||||
const allStylesheets = document.querySelectorAll('link[rel="stylesheet"]');
|
|
||||||
allStylesheets.forEach((stylesheet) => {
|
|
||||||
const link = stylesheet as HTMLLinkElement;
|
|
||||||
// Skip critical styles and any styles that are already optimized
|
|
||||||
if (link.href.includes('critical') ||
|
|
||||||
link.getAttribute('data-critical') === 'true' ||
|
|
||||||
link.getAttribute('data-optimized') === 'true') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as optimized to avoid re-processing
|
|
||||||
link.setAttribute('data-optimized', 'true');
|
|
||||||
|
|
||||||
// Convert blocking stylesheets to non-blocking
|
|
||||||
if (!link.media || link.media === 'all') {
|
|
||||||
// Temporarily set to print to make non-blocking
|
|
||||||
const originalMedia = link.media || 'all';
|
|
||||||
link.media = 'print';
|
|
||||||
link.onload = function() {
|
|
||||||
(this as HTMLLinkElement).media = originalMedia;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run optimization immediately
|
|
||||||
optimizeExistingStyles();
|
|
||||||
|
|
||||||
// Monitor for dynamically added stylesheets
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.type === 'childList') {
|
|
||||||
mutation.addedNodes.forEach((node) => {
|
|
||||||
if (node.nodeName === 'LINK') {
|
|
||||||
const link = node as HTMLLinkElement;
|
|
||||||
if (link.rel === 'stylesheet' &&
|
|
||||||
!link.getAttribute('data-optimized') &&
|
|
||||||
!link.getAttribute('data-critical') &&
|
|
||||||
!link.href.includes('critical')) {
|
|
||||||
link.setAttribute('data-optimized', 'true');
|
|
||||||
// Make it non-blocking
|
|
||||||
const originalMedia = link.media || 'all';
|
|
||||||
link.media = 'print';
|
|
||||||
link.onload = function() {
|
|
||||||
(this as HTMLLinkElement).media = originalMedia;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start observing the document head for changes
|
|
||||||
observer.observe(document.head, {
|
|
||||||
childList: true,
|
|
||||||
subtree: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// This component handles non-critical CSS optimization only
|
|
||||||
// Critical CSS is handled in Root.tsx
|
|
||||||
return null;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user