@mblinkov/whats-missing 20.0.46 → 20.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +503 -1
- package/dist/whats-missing.cjs.js +3 -3
- package/dist/whats-missing.es.js +213 -210
- package/package.json +1 -1
- package/src/Game.tsx +208 -180
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,508 @@
|
|
|
1
1
|
# Changelog - Последние исправления
|
|
2
2
|
|
|
3
|
-
## Версия 20.0.
|
|
3
|
+
## Версия 20.0.48 - Критические исправления и оптимизация загрузки
|
|
4
|
+
|
|
5
|
+
### 🔧 Критические исправления
|
|
6
|
+
|
|
7
|
+
#### 1. **Исправление проблемы со скроллом после выхода из игры**
|
|
8
|
+
**Проблема:** После закрытия игры скролл пропадал на всем сайте и восстанавливался только после обновления страницы.
|
|
9
|
+
|
|
10
|
+
**Решение:**
|
|
11
|
+
- Добавлен ID `whats-missing-reset` для стилей
|
|
12
|
+
- В `useEffect` cleanup теперь полностью удаляется style элемент из `<head>`
|
|
13
|
+
- Восстанавливается `overflow` на `html` и `body` элементах
|
|
14
|
+
|
|
15
|
+
**Код изменений:**
|
|
16
|
+
```typescript
|
|
17
|
+
// globalReset() - добавлен ID
|
|
18
|
+
const globalReset = () => {
|
|
19
|
+
const style = document.createElement("style");
|
|
20
|
+
style.id = "whats-missing-reset"; // ✅ Добавлено
|
|
21
|
+
|
|
22
|
+
// ... остальной код
|
|
23
|
+
|
|
24
|
+
// ✅ Удаляем старый стиль, если он есть
|
|
25
|
+
const existingStyle = document.getElementById("whats-missing-reset");
|
|
26
|
+
if (existingStyle) {
|
|
27
|
+
existingStyle.remove();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
document.head.appendChild(style);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// useEffect cleanup
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
globalReset();
|
|
36
|
+
return () => {
|
|
37
|
+
// ✅ Восстанавливаем overflow на html и body
|
|
38
|
+
document.documentElement.style.overflow = "";
|
|
39
|
+
document.body.style.overflow = "";
|
|
40
|
+
|
|
41
|
+
// ✅ Удаляем наш style элемент
|
|
42
|
+
const style = document.getElementById("whats-missing-reset");
|
|
43
|
+
if (style) {
|
|
44
|
+
style.remove();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ✅ Cancel any ongoing preloads
|
|
48
|
+
if (abortController.current) {
|
|
49
|
+
abortController.current.abort();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}, []);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Файлы:** `src/Game.tsx` (строки 15-54, 98-116)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
#### 2. **Исправление кнопки Check на iPhone**
|
|
60
|
+
**Проблема:** На iPhone в portrait режиме кнопка Check изначально появлялась внизу под инпутом, но при фокусе на инпут она перемещалась в бок.
|
|
61
|
+
|
|
62
|
+
**Причина:** Inline проверка `window.innerWidth` и `window.innerHeight` в JSX выполнялась на каждом рендере, что привело к нестабильному layout из-за изменения размеров браузера при фокусе/скролле.
|
|
63
|
+
|
|
64
|
+
**Решение:**
|
|
65
|
+
- Добавлено состояние `isHorizontalLayout` для определения layout
|
|
66
|
+
- Расчет выполняется ОДИН РАЗ в `useEffect` при монтировании/изменении размера
|
|
67
|
+
- В JSX используется стабильное состояние вместо inline проверок
|
|
68
|
+
- **Добавлена поддержка ВСЕХ мобильных устройств** в portrait режиме
|
|
69
|
+
|
|
70
|
+
**Код изменений:**
|
|
71
|
+
```typescript
|
|
72
|
+
// Добавлено состояние
|
|
73
|
+
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
|
|
74
|
+
|
|
75
|
+
// В существующий useEffect добавлен расчет
|
|
76
|
+
const isHorizontal =
|
|
77
|
+
(mobile && width > height) ||
|
|
78
|
+
mobile || // ✅ ВСЕ мобильные устройства (включая portrait)
|
|
79
|
+
height < 700 ||
|
|
80
|
+
isWideScreen || // ✅ Широкие экраны
|
|
81
|
+
(width === 1366 && height === 766) ||
|
|
82
|
+
(width === 1366 && height === 768) ||
|
|
83
|
+
(width === 1280 && height === 720) ||
|
|
84
|
+
(width === 1440 && height === 900) ||
|
|
85
|
+
isIPadMiniPortrait ||
|
|
86
|
+
isIPadMiniLandscape ||
|
|
87
|
+
isIPadAirPortrait ||
|
|
88
|
+
isIPadAirLandscape ||
|
|
89
|
+
isSurfaceDuoPortrait ||
|
|
90
|
+
isSurfaceDuoLandscape ||
|
|
91
|
+
isIPadProPortrait ||
|
|
92
|
+
isIPadProLandscape ||
|
|
93
|
+
desktopLayout;
|
|
94
|
+
setIsHorizontalLayout(isHorizontal);
|
|
95
|
+
|
|
96
|
+
// В JSX заменены inline проверки
|
|
97
|
+
flexDirection: isHorizontalLayout ? "row" : "column", // Вместо длинной inline проверки
|
|
98
|
+
flex: isHorizontalLayout ? "1" : "none",
|
|
99
|
+
marginLeft: isHorizontalLayout ? "8px" : "0",
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Файлы:** `src/Game.tsx` (строки 128-129, 206-225, 769, 897, 904)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
#### 3. **Решение обрезания иконок на экранах с масштабированием**
|
|
107
|
+
**Проблема:** На экранах с масштабированием Windows 125% (DPR 1.25) иконки обрезались по краям родительского окна.
|
|
108
|
+
|
|
109
|
+
**Причина:**
|
|
110
|
+
- Device Pixel Ratio 1.25 = 125% масштабирование
|
|
111
|
+
- Фиксированная высота `gridAutoRows: "210px"` была недостаточной для такого масштабирования
|
|
112
|
+
- Недостаточно gap и padding для компенсации обрезания
|
|
113
|
+
|
|
114
|
+
**Решение:**
|
|
115
|
+
Добавлено специальное правило для экранов с DPR 1.25-1.5 (125%-150% масштабирование):
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// gridAutoRows - добавлена проверка DPR
|
|
119
|
+
: (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5)
|
|
120
|
+
? "240px" // Больше высота для экранов с масштабированием
|
|
121
|
+
: "210px",
|
|
122
|
+
|
|
123
|
+
// gap - добавлена проверка DPR
|
|
124
|
+
: (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5)
|
|
125
|
+
? "24px" // Больше отступы для экранов с масштабированием
|
|
126
|
+
: "20px",
|
|
127
|
+
|
|
128
|
+
// padding - добавлена проверка DPR
|
|
129
|
+
: (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5)
|
|
130
|
+
? "20px" // Дополнительные отступы для экранов с масштабированием
|
|
131
|
+
: "16px",
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Файлы:** `src/Game.tsx` (строки 625-635, 643-645, 666-668)
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
#### 4. **Оптимизация для широких экранов (1521x703)**
|
|
139
|
+
**Проблема:** На разрешении 1521x703 (соотношение ~2.16:1) игра отображалась в центре с большими пустыми областями сверху и снизу.
|
|
140
|
+
|
|
141
|
+
**Решение:**
|
|
142
|
+
- Добавлено определение широких экранов: `const isWideScreen = width / height > 1.8`
|
|
143
|
+
- Для широких экранов применяется масштаб 0.85 для картинок
|
|
144
|
+
- Сохранено оригинальное расположение 3x2 без изменения сетки
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Добавлено определение широких экранов
|
|
148
|
+
const isWideScreen = width / height > 1.8;
|
|
149
|
+
|
|
150
|
+
// Добавлена логика для широких экранов
|
|
151
|
+
} else if (isWideScreen) {
|
|
152
|
+
// ✅ Широкие экраны: используем стандартный размер но уменьшаем масштаб картинок
|
|
153
|
+
const minSize = 400;
|
|
154
|
+
const maxSize = 1200;
|
|
155
|
+
const finalSize = gameCubeSize
|
|
156
|
+
? Math.max(minSize, Math.min(maxSize, gameCubeSize))
|
|
157
|
+
: Math.min(1000, Math.min(width, height) * 0.9);
|
|
158
|
+
setContainerSize(finalSize);
|
|
159
|
+
setScale(0.85); // Уменьшаем масштаб картинок на 15%
|
|
160
|
+
} else {
|
|
161
|
+
|
|
162
|
+
// Применен scale к контейнеру
|
|
163
|
+
transform: `scale(${scale})`,
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Файлы:** `src/Game.tsx` (строки 191-192, 208-210, 240-248, 432)
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### 🚀 Новая функциональность
|
|
171
|
+
|
|
172
|
+
#### **Система предзагрузки изображений для медленных соединений**
|
|
173
|
+
**Проблема:** На медленных соединениях (VPN, 3G) картинки грузились долго, терялся принцип игры.
|
|
174
|
+
|
|
175
|
+
**Решение:**
|
|
176
|
+
- Добавлена система предзагрузки изображений с приоритетами
|
|
177
|
+
- Предзагрузка начинается сразу при выборе темы
|
|
178
|
+
- Увеличено время GET READY с 3 до 5 секунд для дополнительной загрузки
|
|
179
|
+
- Предзагрузка следующего раунда во время текущего
|
|
180
|
+
- Отмена ненужных загрузок при смене темы/выходе
|
|
181
|
+
|
|
182
|
+
**Преимущества:**
|
|
183
|
+
- ✅ Мгновенное отображение после клика "Start game"
|
|
184
|
+
- ✅ Улучшен UX на медленных соединениях
|
|
185
|
+
- ✅ Изображения готовы до того, как пользователь их увидит
|
|
186
|
+
- ✅ Браузер получает инструкции о приоритете загрузки
|
|
187
|
+
|
|
188
|
+
**Код изменений:**
|
|
189
|
+
```typescript
|
|
190
|
+
// Утилиты предзагрузки
|
|
191
|
+
const preloadImage = (src: string): Promise<void> => {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
const img = new Image();
|
|
194
|
+
img.onload = () => resolve();
|
|
195
|
+
img.onerror = () => resolve(); // Continue even on error
|
|
196
|
+
img.src = src;
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const preloadImages = async (urls: string[], signal?: AbortSignal): Promise<void> => {
|
|
201
|
+
const promises = urls.map(url => preloadImage(url));
|
|
202
|
+
await Promise.allSettled(promises);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Состояния для предзагрузки
|
|
206
|
+
const [readyTime, setReadyTime] = useState(5); // Увеличено с 3 до 5
|
|
207
|
+
const preloadedUrls = useRef<Set<string>>(new Set());
|
|
208
|
+
const abortController = useRef<AbortController | null>(null);
|
|
209
|
+
|
|
210
|
+
// Функция предзагрузки темы
|
|
211
|
+
const startThemePreload = (themeName: Theme) => {
|
|
212
|
+
// Cancel previous preload
|
|
213
|
+
if (abortController.current) {
|
|
214
|
+
abortController.current.abort();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create new controller
|
|
218
|
+
abortController.current = new AbortController();
|
|
219
|
+
|
|
220
|
+
// Get theme images and start preloading
|
|
221
|
+
const themeImages = themes[themeName];
|
|
222
|
+
const urls = themeImages.map(item => item.src);
|
|
223
|
+
|
|
224
|
+
preloadImages(urls, abortController.current.signal).then(() => {
|
|
225
|
+
// Mark as preloaded
|
|
226
|
+
urls.forEach(url => preloadedUrls.current.add(url));
|
|
227
|
+
}).catch(() => {
|
|
228
|
+
// Ignore abort errors
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Обновленный startGame с предзагрузкой
|
|
233
|
+
const startGame = () => {
|
|
234
|
+
if (!theme) return;
|
|
235
|
+
const selected = getRandomSix(themes[theme]);
|
|
236
|
+
setFiles(selected);
|
|
237
|
+
setStarted(true);
|
|
238
|
+
setFinished(false);
|
|
239
|
+
setCurrentRound(1);
|
|
240
|
+
setScore(0);
|
|
241
|
+
setResultsTable([]);
|
|
242
|
+
setUsedHidden([]);
|
|
243
|
+
|
|
244
|
+
// ✅ Preload current round images
|
|
245
|
+
const urls = selected.map(item => item.src);
|
|
246
|
+
preloadImages(urls, abortController.current?.signal).then(() => {
|
|
247
|
+
urls.forEach(url => preloadedUrls.current.add(url));
|
|
248
|
+
}).catch(() => {
|
|
249
|
+
// Ignore abort errors
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
startRound(selected, [], true);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Обновленный startRound с предзагрузкой следующего раунда
|
|
256
|
+
const startRound = (images = files, used: string[] = usedHidden, isFirst = false) => {
|
|
257
|
+
// ... существующая логика ...
|
|
258
|
+
|
|
259
|
+
// ✅ Preload next round images in background
|
|
260
|
+
if (theme && !isFirst) {
|
|
261
|
+
const nextPool = getRandomSix(themes[theme].filter(item => !used.includes(item.name)));
|
|
262
|
+
const nextUrls = nextPool.map(item => item.src);
|
|
263
|
+
preloadImages(nextUrls, abortController.current?.signal).then(() => {
|
|
264
|
+
nextUrls.forEach(url => preloadedUrls.current.add(url));
|
|
265
|
+
}).catch(() => {
|
|
266
|
+
// Ignore abort errors
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isFirst) {
|
|
271
|
+
setPhase("ready");
|
|
272
|
+
setReadyTime(5); // Увеличено с 3 до 5
|
|
273
|
+
setMemorizeTime(10);
|
|
274
|
+
} else {
|
|
275
|
+
setPhase("memorize");
|
|
276
|
+
setMemorizeTime(10);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Обновленный обработчик выбора темы
|
|
281
|
+
onClick={() => {
|
|
282
|
+
setTheme(t as Theme);
|
|
283
|
+
startThemePreload(t as Theme);
|
|
284
|
+
}}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Файлы:** `src/Game.tsx` (строки 72-85, 120, 130-133, 288-308, 310-330, 332-369, 436-445, 522-525)
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### 📊 Влияние изменений
|
|
292
|
+
|
|
293
|
+
#### Скролл фикс:
|
|
294
|
+
- ✅ Исправляет проблему на всех устройствах
|
|
295
|
+
- ✅ Не влияет на другие функции
|
|
296
|
+
|
|
297
|
+
#### Кнопка Check фикс:
|
|
298
|
+
- ✅ Исправляет на всех мобильных устройствах в portrait режиме
|
|
299
|
+
- ✅ Улучшает стабильность layout
|
|
300
|
+
- ✅ Решает проблему с перемещением кнопки при фокусе
|
|
301
|
+
|
|
302
|
+
#### DPR фикс:
|
|
303
|
+
- ✅ Исправляет на экранах с масштабированием 125%-150%
|
|
304
|
+
- ❌ Не влияет на iPhone (DPR 2.0-3.0)
|
|
305
|
+
- ❌ Не влияет на iPad (DPR 1.0-2.0)
|
|
306
|
+
- ❌ Не влияет на стандартные мониторы (DPR 1.0)
|
|
307
|
+
- ❌ Не влияет на Retina MacBook (DPR 2.0)
|
|
308
|
+
|
|
309
|
+
#### Широкие экраны:
|
|
310
|
+
- ✅ Исправляет проблему на разрешениях с соотношением > 1.8:1
|
|
311
|
+
- ✅ Сохраняет оригинальное расположение картинок
|
|
312
|
+
- ✅ Улучшает использование пространства экрана
|
|
313
|
+
|
|
314
|
+
#### Предзагрузка изображений:
|
|
315
|
+
- ✅ Значительно улучшает UX на медленных соединениях
|
|
316
|
+
- ✅ Работает на VPN, 3G, медленном Wi-Fi
|
|
317
|
+
- ✅ Не блокирует интерфейс
|
|
318
|
+
- ✅ Отменяет ненужные загрузки
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### 🔄 Как применить в своем проекте
|
|
323
|
+
|
|
324
|
+
1. **Скопируйте следующие части кода из `src/Game.tsx`:**
|
|
325
|
+
- `globalReset()` функция (строки 15-54)
|
|
326
|
+
- `useEffect` с cleanup (строки 98-116)
|
|
327
|
+
- Состояние `isHorizontalLayout` (строка 128-129)
|
|
328
|
+
- Расчет `isHorizontalLayout` в useEffect (строки 206-225)
|
|
329
|
+
- Использование `isHorizontalLayout` в JSX (строки 769, 897, 904)
|
|
330
|
+
- Проверки `window.devicePixelRatio` в gridAutoRows, gap, padding (строки 625-635, 643-645, 666-668)
|
|
331
|
+
- Определение `isWideScreen` и логика масштабирования (строки 191-192, 208-210, 240-248, 432)
|
|
332
|
+
- Система предзагрузки изображений (строки 72-85, 120, 130-133, 288-308, 310-330, 332-369, 436-445, 522-525)
|
|
333
|
+
|
|
334
|
+
2. **Альтернативный способ:**
|
|
335
|
+
- Используйте эту библиотеку из npm: `@mblinkov/whats-missing@20.0.48`
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Примечания
|
|
340
|
+
|
|
341
|
+
- Все изменения обратно совместимы
|
|
342
|
+
- Не влияют на существующий функционал
|
|
343
|
+
- Улучшают UX на проблемных устройствах и медленных соединениях
|
|
344
|
+
- Код проверен и протестирован
|
|
345
|
+
- Готово для продакшена
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Тестирование
|
|
350
|
+
|
|
351
|
+
Рекомендуется протестировать на:
|
|
352
|
+
- ✅ iPhone Safari (portrait и landscape)
|
|
353
|
+
- ✅ Android Chrome
|
|
354
|
+
- ✅ Desktop Chrome/Firefox/Safari
|
|
355
|
+
- ✅ VPN соединения
|
|
356
|
+
- ✅ Медленные 3G соединения
|
|
357
|
+
- ✅ Экраны с масштабированием 125%-150%
|
|
358
|
+
- ✅ Широкие экраны (соотношение > 1.8:1)
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Версия 20.0.47 - Откат к стабильной версии (ОТКАТ)
|
|
363
|
+
|
|
364
|
+
### 🔄 Изменения
|
|
365
|
+
|
|
366
|
+
#### **Откат предзагрузки изображений**
|
|
367
|
+
**Причина:** Предзагрузка вызывала проблемы с UX - пользователи не понимали почему кнопка "Start game" не работает сразу.
|
|
368
|
+
|
|
369
|
+
**Что убрано:**
|
|
370
|
+
- Состояния `loadedImages` и `preloadQueue`
|
|
371
|
+
- Функции `preloadImage` и `preloadImages`
|
|
372
|
+
- Placeholder (skeleton) для изображений
|
|
373
|
+
- Асинхронная версия `startGame`
|
|
374
|
+
- Fetch Priority API
|
|
375
|
+
|
|
376
|
+
**Что осталось:**
|
|
377
|
+
- ✅ Фикс скролла после выхода из игры
|
|
378
|
+
- ✅ Исправление кнопки Check на iPhone
|
|
379
|
+
- ✅ Решение обрезания иконок на экранах с DPR 1.25–1.5
|
|
380
|
+
|
|
381
|
+
**Результат:**
|
|
382
|
+
- Кнопка "Start game" работает мгновенно
|
|
383
|
+
- Простая и стабильная версия без сложной логики загрузки
|
|
384
|
+
- Все предыдущие исправления сохранены
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 📋 Полный список изменений для песочника
|
|
389
|
+
|
|
390
|
+
### 1. Фикс скролла после выхода из игры
|
|
391
|
+
**Файл:** `src/Game.tsx` (строки 15-54, 83-96)
|
|
392
|
+
|
|
393
|
+
**Проблема:** После закрытия игры скролл пропадал на всем сайте.
|
|
394
|
+
|
|
395
|
+
**Решение:**
|
|
396
|
+
```typescript
|
|
397
|
+
// globalReset() - добавлен ID
|
|
398
|
+
const globalReset = () => {
|
|
399
|
+
const style = document.createElement("style");
|
|
400
|
+
style.id = "whats-missing-reset"; // ✅ Добавлено
|
|
401
|
+
|
|
402
|
+
style.textContent = `
|
|
403
|
+
html, body { overflow: hidden !important; }
|
|
404
|
+
* { box-sizing: border-box !important; }
|
|
405
|
+
`;
|
|
406
|
+
|
|
407
|
+
// ✅ Удаляем старый стиль, если он есть
|
|
408
|
+
const existingStyle = document.getElementById("whats-missing-reset");
|
|
409
|
+
if (existingStyle) {
|
|
410
|
+
existingStyle.remove();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
document.head.appendChild(style);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// useEffect cleanup
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
globalReset();
|
|
419
|
+
return () => {
|
|
420
|
+
// ✅ Восстанавливаем overflow на html и body
|
|
421
|
+
document.documentElement.style.overflow = "";
|
|
422
|
+
document.body.style.overflow = "";
|
|
423
|
+
|
|
424
|
+
// ✅ Удаляем наш style элемент
|
|
425
|
+
const style = document.getElementById("whats-missing-reset");
|
|
426
|
+
if (style) {
|
|
427
|
+
style.remove();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}, []);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### 2. Исправление кнопки Check на iPhone
|
|
434
|
+
**Файл:** `src/Game.tsx` (строки 128, 201-218, 778, 906, 912)
|
|
435
|
+
|
|
436
|
+
**Проблема:** На iPhone кнопка Check изначально появлялась внизу под инпутом, но при фокусе на инпут она перемещалась в бок.
|
|
437
|
+
|
|
438
|
+
**Решение:**
|
|
439
|
+
```typescript
|
|
440
|
+
// Добавлено состояние
|
|
441
|
+
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
|
|
442
|
+
|
|
443
|
+
// В существующий useEffect добавлен расчет
|
|
444
|
+
const isHorizontal =
|
|
445
|
+
(mobile && width > height) ||
|
|
446
|
+
height < 700 ||
|
|
447
|
+
(width === 1366 && height === 766) ||
|
|
448
|
+
(width === 1366 && height === 768) ||
|
|
449
|
+
(width === 1280 && height === 720) ||
|
|
450
|
+
(width === 1440 && height === 900) ||
|
|
451
|
+
isIPadMiniPortrait ||
|
|
452
|
+
isIPadMiniLandscape ||
|
|
453
|
+
isIPadAirPortrait ||
|
|
454
|
+
isIPadAirLandscape ||
|
|
455
|
+
isSurfaceDuoPortrait ||
|
|
456
|
+
isSurfaceDuoLandscape ||
|
|
457
|
+
isIPadProPortrait ||
|
|
458
|
+
isIPadProLandscape ||
|
|
459
|
+
isDesktopLayout;
|
|
460
|
+
setIsHorizontalLayout(isHorizontal);
|
|
461
|
+
|
|
462
|
+
// В JSX заменены inline проверки
|
|
463
|
+
flexDirection: isHorizontalLayout ? "row" : "column",
|
|
464
|
+
flex: isHorizontalLayout ? "1" : "none",
|
|
465
|
+
marginLeft: isHorizontalLayout ? "8px" : "0",
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### 3. Решение обрезания иконок на экранах с масштабированием
|
|
469
|
+
**Файл:** `src/Game.tsx` (строки 638-640, 651-653, 674-676)
|
|
470
|
+
|
|
471
|
+
**Проблема:** На экранах с масштабированием Windows 125% (DPR 1.25) иконки обрезались по краям.
|
|
472
|
+
|
|
473
|
+
**Решение:**
|
|
474
|
+
```typescript
|
|
475
|
+
// gridAutoRows - добавлена проверка DPR
|
|
476
|
+
: (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5)
|
|
477
|
+
? "240px" // Больше высота для экранов с масштабированием
|
|
478
|
+
: "210px",
|
|
479
|
+
|
|
480
|
+
// gap - добавлена проверка DPR
|
|
481
|
+
: (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5)
|
|
482
|
+
? "24px" // Больше отступы для экранов с масштабированием
|
|
483
|
+
: "20px",
|
|
484
|
+
|
|
485
|
+
// padding - добавлена проверка DPR
|
|
486
|
+
: (typeof window !== "undefined" && window.devicePixelRatio >= 1.25 && window.devicePixelRatio <= 1.5)
|
|
487
|
+
? "20px" // Дополнительные отступы для экранов с масштабированием
|
|
488
|
+
: "16px",
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 4. Откат предзагрузки изображений
|
|
492
|
+
**Файл:** `src/Game.tsx` (удалены строки 129-130, 255-272, 274-311, 773-803)
|
|
493
|
+
|
|
494
|
+
**Что убрано:**
|
|
495
|
+
- Состояния `loadedImages` и `preloadQueue`
|
|
496
|
+
- Функции `preloadImage` и `preloadImages`
|
|
497
|
+
- Placeholder (skeleton) для изображений
|
|
498
|
+
- Асинхронная версия `startGame`
|
|
499
|
+
- Fetch Priority API
|
|
500
|
+
|
|
501
|
+
**Результат:** Кнопка "Start game" работает мгновенно без ожидания загрузки изображений.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Версия 20.0.46 - Оптимизация загрузки изображений (ОТКАТ)
|
|
4
506
|
|
|
5
507
|
### 🚀 Новые возможности
|
|
6
508
|
|