- Заменен лишний div.container на стандартный container для единообразия - Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей - Добавлен card-list.css в base.html для корректного отображения карточек новостей - Добавлены CSS правила для правильного поведения .row и колонок в grid-layout - Исправлена проблема с выходом календаря за границы страницы - Убраны видимые скроллбары у modal-body и modal-content для чистого UI
8.4 KiB
8.4 KiB
Устранение FOUC (Flash of Unstyled Content)
Проблема
При переходе между страницами происходило мигание белой темы, даже если пользователь выбрал тёмную тему.
Ошибочное предположение
❌ "Стили не кэшируются" - НЕверно!
- Статические файлы (CSS/JS) всегда кэшируются браузером
- Nginx выдаёт их с
Cache-Control: public, max-age=31536000 - Проблема была НЕ в кэше!
Настоящая причина
✅ Неправильный порядок выполнения скриптов:
1. HTML загружается с data-bs-theme="auto"
2. CSS загружается и применяется (тема = auto = светлая)
3. 🔥 МИГАНИЕ БЕЛОЙ ТЕМЫ 🔥
4. JavaScript выполняется и меняет тему на тёмную
Решение
До (неправильно)
<html data-bs-theme="auto">
<head>
<link rel="stylesheet" href="bootstrap.css">
<!-- Стили применяются с auto = светлая тема -->
</head>
<body>
<script defer>
// Слишком поздно! Уже видели белую тему
document.documentElement.setAttribute('data-bs-theme', 'dark');
</script>
</body>
</html>
После (правильно)
<html>
<head>
<!-- 1. СРАЗУ применяем тему ДО загрузки CSS -->
<script src="themeInitializer.js"></script>
<!-- 2. Preload критичных стилей -->
<link rel="preload" href="bootstrap.css" as="style">
<link rel="preload" href="variables.css" as="style">
<!-- 3. Теперь загружаем CSS - тема уже установлена! -->
<link rel="stylesheet" href="bootstrap.css">
<link rel="stylesheet" href="variables.css">
</head>
<body>
<!-- Никакого мигания! -->
</body>
</html>
Как работает themeInitializer.js
Синхронное выполнение (НЕ defer, НЕ async, НЕ module)
(function() {
'use strict';
// 1. Читаем сохранённую тему из localStorage
const savedTheme = localStorage.getItem('theme') || 'auto';
// 2. Определяем эффективную тему
let effectiveTheme = savedTheme;
if (savedTheme === 'auto') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
effectiveTheme = prefersDark.matches ? 'dark' : 'light';
}
// 3. НЕМЕДЛЕННО устанавливаем атрибут
document.documentElement.setAttribute('data-bs-theme', effectiveTheme);
// Всё это происходит ДО парсинга <body> и ДО загрузки CSS!
})();
Почему это работает
- ✅ Скрипт выполняется синхронно в
<head> - ✅ Браузер блокируется до выполнения скрипта
- ✅ Атрибут
data-bs-themeустанавливается ДО применения CSS - ✅ CSS применяется сразу с правильной темой
- ✅ Никакого мигания!
Preload критичных стилей
Зачем нужен preload?
<link rel="preload" href="bootstrap.css" as="style">
<link rel="stylesheet" href="bootstrap.css">
Без preload:
- Браузер парсит HTML
- Доходит до
<link rel="stylesheet"> - Начинает загрузку CSS (задержка!)
- Парсит CSS
- Применяет стили
С preload:
- Браузер парсит HTML
- Видит
<link rel="preload">→ начинает загрузку параллельно - Доходит до
<link rel="stylesheet">→ CSS уже загружен! - Сразу применяет стили
Результат: ускорение на 100-300ms
Кэширование и FOUC
Важно понимать!
Кэш НЕ решает проблему FOUC:
С кэшем (неправильный порядок):
1. HTML из disk cache (0ms)
2. CSS из disk cache (0ms) ← быстро!
3. 🔥 Но всё равно мигание! 🔥
4. JS меняет тему (слишком поздно)
С кэшем (правильный порядок):
1. HTML из disk cache (0ms)
2. themeInitializer.js СИНХРОННО (0ms)
3. data-bs-theme установлен ДО парсинга CSS
4. CSS из disk cache (0ms) применяется с правильной темой
5. ✅ Никакого мигания!
Вывод:
- Кэш ускоряет загрузку
- Правильный порядок устраняет FOUC
- Нужны оба решения!
Тестирование
Как проверить, что FOUC устранён
- Выберите тёмную тему на сайте
- Hard refresh (Ctrl+Shift+R) для сброса кэша
- Быстро переходите между страницами
- Результат: НЕ должно быть белых вспышек
Проверка в DevTools
// Откройте Console
// Проверьте, что атрибут установлен ДО DOMContentLoaded
console.log('Theme on parse:', document.documentElement.getAttribute('data-bs-theme'));
// Должно вывести 'dark' или 'light', НЕ 'auto'
Проверка порядка загрузки
Network tab → фильтр "CSS":
- ✅
themeInitializer.js- загружен первым - ✅
bootstrap.css(preload) - ✅
variables.css(preload) - ✅ Остальные стили
Частые ошибки
❌ Использование defer/async
<!-- НЕПРАВИЛЬНО: defer выполнится ПОСЛЕ парсинга -->
<script src="themeInitializer.js" defer></script>
<!-- ПРАВИЛЬНО: синхронное выполнение -->
<script src="themeInitializer.js"></script>
❌ Использование type="module"
<!-- НЕПРАВИЛЬНО: модули выполняются с defer -->
<script type="module" src="themeInitializer.js"></script>
<!-- ПРАВИЛЬНО: обычный скрипт -->
<script src="themeInitializer.js"></script>
❌ Установка темы в DOMContentLoaded
// НЕПРАВИЛЬНО: слишком поздно!
document.addEventListener('DOMContentLoaded', () => {
document.documentElement.setAttribute('data-bs-theme', 'dark');
});
// ПРАВИЛЬНО: немедленно
document.documentElement.setAttribute('data-bs-theme', 'dark');
❌ data-bs-theme="auto" в HTML
<!-- НЕПРАВИЛЬНО: браузер применит auto ДО JS -->
<html data-bs-theme="auto">
<!-- ПРАВИЛЬНО: без атрибута, JS установит сразу -->
<html>
<head>
<script>/* устанавливаем тему */</script>
Совместимость с кэшированием
Статика кэшируется правильно
themeInitializer.js:
- Cache-Control: public, max-age=31536000
- Loaded: (disk cache)
bootstrap.css:
- Cache-Control: public, max-age=31536000
- Loaded: (disk cache)
HTML кэшируется с правильным порядком
base.html:
- Cache-Control: private, max-age=60
- Loaded: (disk cache)
- Порядок скриптов сохранён!
Результат:
- ✅ Мгновенная загрузка из кэша
- ✅ Без мигания темы
- ✅ Идеальный UX!
Резюме
| Проблема | Решение | Результат |
|---|---|---|
| Мигание белой темы | Синхронный скрипт в <head> |
✅ Нет FOUC |
| Медленная загрузка CSS | Preload критичных стилей | ✅ +100-300ms |
| "Стили не кэшируются" | Это было заблуждение | ✅ Всегда кэшировались |
| data-bs-theme="auto" | Убрали из HTML | ✅ JS устанавливает сразу |
Главный вывод: Проблема была НЕ в кэшировании, а в порядке выполнения! 🎯