- Заменен лишний div.container на стандартный container для единообразия - Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей - Добавлен card-list.css в base.html для корректного отображения карточек новостей - Добавлены CSS правила для правильного поведения .row и колонок в grid-layout - Исправлена проблема с выходом календаря за границы страницы - Убраны видимые скроллбары у modal-body и modal-content для чистого UI
246 lines
8.4 KiB
Markdown
246 lines
8.4 KiB
Markdown
# Устранение 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
|
||
<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
|
||
<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)
|
||
```javascript
|
||
(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?
|
||
```html
|
||
<link rel="preload" href="bootstrap.css" as="style">
|
||
<link rel="stylesheet" href="bootstrap.css">
|
||
```
|
||
|
||
**Без preload**:
|
||
1. Браузер парсит HTML
|
||
2. Доходит до `<link rel="stylesheet">`
|
||
3. Начинает загрузку CSS (задержка!)
|
||
4. Парсит CSS
|
||
5. Применяет стили
|
||
|
||
**С preload**:
|
||
1. Браузер парсит HTML
|
||
2. Видит `<link rel="preload">` → начинает загрузку **параллельно**
|
||
3. Доходит до `<link rel="stylesheet">` → CSS уже загружен!
|
||
4. Сразу применяет стили
|
||
|
||
**Результат**: ускорение на 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 устранён
|
||
|
||
1. **Выберите тёмную тему** на сайте
|
||
2. **Hard refresh** (Ctrl+Shift+R) для сброса кэша
|
||
3. **Быстро переходите** между страницами
|
||
4. **Результат**: НЕ должно быть белых вспышек
|
||
|
||
### Проверка в DevTools
|
||
|
||
```javascript
|
||
// Откройте Console
|
||
// Проверьте, что атрибут установлен ДО DOMContentLoaded
|
||
console.log('Theme on parse:', document.documentElement.getAttribute('data-bs-theme'));
|
||
|
||
// Должно вывести 'dark' или 'light', НЕ 'auto'
|
||
```
|
||
|
||
### Проверка порядка загрузки
|
||
|
||
Network tab → фильтр "CSS":
|
||
1. ✅ `themeInitializer.js` - загружен первым
|
||
2. ✅ `bootstrap.css` (preload)
|
||
3. ✅ `variables.css` (preload)
|
||
4. ✅ Остальные стили
|
||
|
||
## Частые ошибки
|
||
|
||
### ❌ Использование defer/async
|
||
```html
|
||
<!-- НЕПРАВИЛЬНО: defer выполнится ПОСЛЕ парсинга -->
|
||
<script src="themeInitializer.js" defer></script>
|
||
|
||
<!-- ПРАВИЛЬНО: синхронное выполнение -->
|
||
<script src="themeInitializer.js"></script>
|
||
```
|
||
|
||
### ❌ Использование type="module"
|
||
```html
|
||
<!-- НЕПРАВИЛЬНО: модули выполняются с defer -->
|
||
<script type="module" src="themeInitializer.js"></script>
|
||
|
||
<!-- ПРАВИЛЬНО: обычный скрипт -->
|
||
<script src="themeInitializer.js"></script>
|
||
```
|
||
|
||
### ❌ Установка темы в DOMContentLoaded
|
||
```javascript
|
||
// НЕПРАВИЛЬНО: слишком поздно!
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||
});
|
||
|
||
// ПРАВИЛЬНО: немедленно
|
||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||
```
|
||
|
||
### ❌ data-bs-theme="auto" в HTML
|
||
```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 устанавливает сразу |
|
||
|
||
**Главный вывод**: Проблема была НЕ в кэшировании, а в **порядке выполнения**! 🎯
|