|
11 | 11 | body { |
12 | 12 | margin: 0; |
13 | 13 | background: black; |
14 | | - } |
15 | | - |
16 | | - html { |
17 | | - --ios-safe-area-top: env(safe-area-inset-top, 0px); |
18 | | - --ios-safe-area-right: env(safe-area-inset-right, 0px); |
19 | | - --ios-safe-area-bottom: env(safe-area-inset-bottom, 0px); |
20 | | - --ios-safe-area-left: env(safe-area-inset-left, 0px); |
| 14 | + width: 100%; |
| 15 | + height: 100%; |
| 16 | + overflow: hidden; |
21 | 17 | } |
22 | 18 |
|
23 | 19 | .html-body__effect-background { |
|
139 | 135 | padding: 0 0 0.25em 0; |
140 | 136 | } |
141 | 137 |
|
142 | | - html.ios-phone-viewport, |
143 | | - html.ios-phone-viewport body { |
144 | | - width: 100%; |
145 | | - height: 100%; |
146 | | - overflow: hidden; |
147 | | - overscroll-behavior: none; |
148 | | - background: black; |
149 | | - } |
150 | | - |
151 | | - html.ios-phone-viewport body { |
152 | | - position: fixed; |
153 | | - inset: 0; |
154 | | - } |
155 | | - |
156 | | - html.ios-phone-viewport #ebg { |
157 | | - display: none; |
158 | | - } |
159 | | - |
160 | | - html.ios-phone-viewport #root, |
161 | | - html.ios-phone-viewport .html-body__title-enter { |
162 | | - width: 2560px; |
163 | | - height: 1440px; |
164 | | - position: absolute; |
165 | | - top: 0; |
166 | | - left: 0; |
167 | | - transform-origin: top left; |
168 | | - } |
169 | 138 | </style> |
170 | 139 | </head> |
171 | 140 | <body> |
|
193 | 162 | <!-- 在窗口大小改变时进行强制缩放 --> |
194 | 163 | <script> |
195 | 164 | (() => { |
196 | | - const targetHeight = 1440; |
197 | | - const targetWidth = 2560; |
198 | 165 | const userAgent = navigator.userAgent; |
199 | 166 | const platform = navigator.platform; |
200 | 167 | const maxTouchPoints = navigator.maxTouchPoints || 0; |
201 | 168 | const isIPhoneLike = /iPhone|iPod/i.test(userAgent); |
202 | 169 | const isIPadLike = /iPad/i.test(userAgent) || (platform === 'MacIntel' && maxTouchPoints > 1); |
203 | 170 | const isIOS = isIPhoneLike || isIPadLike; |
204 | 171 | const isIOSPhone = isIOS && isIPhoneLike; |
205 | | - const html = document.documentElement; |
206 | | - const body = document.body; |
207 | 172 | const root = document.querySelector('#root'); |
208 | 173 | const titleEnter = document.querySelector('.html-body__title-enter'); |
209 | 174 | const effectBackground = document.querySelector('.html-body__effect-background'); |
210 | 175 | let viewportMeta = document.querySelector('meta[name="viewport"]'); |
211 | | - let iosPhoneLayoutFrame = 0; |
212 | | - let touchStartX = 0; |
213 | | - let touchStartY = 0; |
214 | 176 |
|
215 | 177 | window.__WEBGAL_DEVICE_INFO__ = { isIOS, isIOSPhone }; |
216 | 178 |
|
|
223 | 185 | return viewportMeta; |
224 | 186 | }; |
225 | 187 |
|
226 | | - const parseInset = (value) => { |
227 | | - const parsed = Number.parseFloat(value); |
228 | | - return Number.isFinite(parsed) ? parsed : 0; |
229 | | - }; |
230 | | - |
231 | | - const getSafeAreaInsets = () => { |
232 | | - const computedStyle = window.getComputedStyle(html); |
233 | | - return { |
234 | | - top: parseInset(computedStyle.getPropertyValue('--ios-safe-area-top')), |
235 | | - right: parseInset(computedStyle.getPropertyValue('--ios-safe-area-right')), |
236 | | - bottom: parseInset(computedStyle.getPropertyValue('--ios-safe-area-bottom')), |
237 | | - left: parseInset(computedStyle.getPropertyValue('--ios-safe-area-left')), |
238 | | - }; |
239 | | - }; |
240 | | - |
241 | | - const applyIOSPhoneLayout = () => { |
242 | | - const safeArea = getSafeAreaInsets(); |
243 | | - const viewportWidth = window.innerWidth; |
244 | | - const viewportHeight = window.innerHeight; |
245 | | - const usableWidth = Math.max(viewportWidth - safeArea.left - safeArea.right, 1); |
246 | | - const usableHeight = Math.max(viewportHeight - safeArea.top - safeArea.bottom, 1); |
247 | | - const scale = Math.max(Math.min(usableWidth / targetWidth, usableHeight / targetHeight), 0.01); |
248 | | - const left = safeArea.left + Math.max((usableWidth - targetWidth * scale) / 2, 0); |
249 | | - const top = safeArea.top + Math.max((usableHeight - targetHeight * scale) / 2, 0); |
250 | | - const transform = `translate(${left}px, ${top}px) scale(${scale})`; |
251 | | - |
252 | | - if (effectBackground) { |
253 | | - effectBackground.style.transform = ''; |
254 | | - } |
255 | | - for (const element of [root, titleEnter]) { |
256 | | - if (element) { |
257 | | - element.style.transform = transform; |
258 | | - } |
259 | | - } |
260 | | - }; |
261 | | - |
262 | | - const scheduleIOSPhoneLayout = () => { |
263 | | - if (iosPhoneLayoutFrame) { |
264 | | - window.cancelAnimationFrame(iosPhoneLayoutFrame); |
265 | | - } |
266 | | - iosPhoneLayoutFrame = window.requestAnimationFrame(() => { |
267 | | - iosPhoneLayoutFrame = 0; |
268 | | - applyIOSPhoneLayout(); |
269 | | - }); |
270 | | - }; |
271 | | - |
272 | | - const canScrollInDirection = (element, deltaX, deltaY) => { |
273 | | - const style = window.getComputedStyle(element); |
274 | | - const overflowY = style.overflowY; |
275 | | - const overflowX = style.overflowX; |
276 | | - const canScrollY = /(auto|scroll|overlay)/.test(overflowY) && element.scrollHeight > element.clientHeight + 1; |
277 | | - const canScrollX = /(auto|scroll|overlay)/.test(overflowX) && element.scrollWidth > element.clientWidth + 1; |
278 | | - |
279 | | - if (Math.abs(deltaY) >= Math.abs(deltaX) && canScrollY) { |
280 | | - const maxScrollTop = element.scrollHeight - element.clientHeight; |
281 | | - if (deltaY > 0 && element.scrollTop > 0) return true; |
282 | | - if (deltaY < 0 && element.scrollTop < maxScrollTop) return true; |
283 | | - } |
284 | | - |
285 | | - if (Math.abs(deltaX) > Math.abs(deltaY) && canScrollX) { |
286 | | - const maxScrollLeft = element.scrollWidth - element.clientWidth; |
287 | | - if (deltaX > 0 && element.scrollLeft > 0) return true; |
288 | | - if (deltaX < 0 && element.scrollLeft < maxScrollLeft) return true; |
289 | | - } |
290 | | - |
291 | | - return false; |
292 | | - }; |
293 | | - |
294 | | - const findScrollableAncestor = (startElement, deltaX, deltaY) => { |
295 | | - let current = startElement; |
296 | | - while (current && current !== body) { |
297 | | - if (current instanceof HTMLElement && canScrollInDirection(current, deltaX, deltaY)) { |
298 | | - return current; |
299 | | - } |
300 | | - current = current.parentElement; |
301 | | - } |
302 | | - return null; |
303 | | - }; |
304 | | - |
305 | | - const installIOSPhoneGestureGuards = () => { |
306 | | - const blockEvent = (event) => { |
307 | | - event.preventDefault(); |
308 | | - }; |
309 | | - |
310 | | - document.addEventListener( |
311 | | - 'gesturestart', |
312 | | - (event) => { |
313 | | - blockEvent(event); |
314 | | - }, |
315 | | - { passive: false }, |
316 | | - ); |
317 | | - document.addEventListener( |
318 | | - 'gesturechange', |
319 | | - (event) => { |
320 | | - blockEvent(event); |
321 | | - }, |
322 | | - { passive: false }, |
323 | | - ); |
324 | | - document.addEventListener( |
325 | | - 'gestureend', |
326 | | - (event) => { |
327 | | - blockEvent(event); |
328 | | - }, |
329 | | - { passive: false }, |
330 | | - ); |
331 | | - |
332 | | - document.addEventListener( |
333 | | - 'touchstart', |
334 | | - (event) => { |
335 | | - if (event.touches.length > 1) { |
336 | | - blockEvent(event); |
337 | | - return; |
338 | | - } |
339 | | - const touch = event.touches[0]; |
340 | | - if (touch) { |
341 | | - touchStartX = touch.clientX; |
342 | | - touchStartY = touch.clientY; |
343 | | - } |
344 | | - }, |
345 | | - { passive: false }, |
346 | | - ); |
347 | | - |
348 | | - document.addEventListener( |
349 | | - 'touchmove', |
350 | | - (event) => { |
351 | | - if (event.touches.length > 1) { |
352 | | - blockEvent(event); |
353 | | - return; |
354 | | - } |
355 | | - const touch = event.touches[0]; |
356 | | - if (!touch) { |
357 | | - return; |
358 | | - } |
359 | | - const deltaX = touch.clientX - touchStartX; |
360 | | - const deltaY = touch.clientY - touchStartY; |
361 | | - const target = event.target instanceof Element ? event.target : null; |
362 | | - const scrollableAncestor = target ? findScrollableAncestor(target, deltaX, deltaY) : null; |
363 | | - |
364 | | - if (!scrollableAncestor) { |
365 | | - blockEvent(event); |
366 | | - } |
367 | | - }, |
368 | | - { passive: false }, |
369 | | - ); |
370 | | - }; |
371 | | - |
372 | 188 | const resize = () => { |
| 189 | + const targetHeight = 1440; // 目标高度 |
| 190 | + const targetWidth = 2560; // 目标宽度 |
373 | 191 | const h = window.innerHeight; // 窗口高度 |
374 | 192 | const w = window.innerWidth; // 窗口宽度 |
375 | | - const zoomH = h / targetHeight; // 以窗口高度为基准的变换比 |
| 193 | + const iosBottomInset = isIOS ? 15 : 0; |
| 194 | + const layoutHeight = Math.max(h - iosBottomInset, 1); |
| 195 | + const zoomH = layoutHeight / targetHeight; // 以窗口高度为基准的变换比 |
376 | 196 | const zoomW = w / targetWidth; // 以窗口宽度为基准的变换比 |
377 | 197 | const zoomH2 = w / targetHeight; // 竖屏时以窗口高度为基础的变换比 |
378 | | - const zoomW2 = h / targetWidth; // 竖屏时以窗口宽度为基础的变换比 |
379 | | - let mh = (targetHeight - h) / 2; // y轴移动距离 |
| 198 | + const zoomW2 = layoutHeight / targetWidth; // 竖屏时以窗口宽度为基础的变换比 |
| 199 | + let mh = (targetHeight - layoutHeight) / 2; // y轴移动距离 |
380 | 200 | let mw = (targetWidth - w) / 2; // x轴移动距离 |
381 | 201 | let mh2os = targetWidth / 2 - w / 2; // 竖屏时 y轴移动距离 |
382 | | - let mw2os = targetHeight / 2 - h / 2; // 竖屏时 x轴移动距离 |
| 202 | + let mw2os = targetHeight / 2 - layoutHeight / 2; // 竖屏时 x轴移动距离 |
383 | 203 | let transform = ''; |
384 | 204 | let effectBackgroundTransform = ''; |
385 | 205 | const elements = [root, titleEnter]; |
|
417 | 237 | // iOS 不强制旋转 |
418 | 238 | if (isIOS) { |
419 | 239 | const zoomWi = w / targetWidth; |
| 240 | + if (effectBackground) { |
| 241 | + effectBackground.style.height = `100vh`; |
| 242 | + effectBackground.style.width = `100vw`; |
| 243 | + effectBackgroundTransform = ''; |
| 244 | + } |
420 | 245 | transform = `translate(${-mw}px, ${-mh}px) ` + `scale(${zoomWi},${zoomWi}) `; |
421 | 246 | } |
422 | 247 | } |
|
429 | 254 | } |
430 | 255 | } |
431 | 256 | }; |
432 | | - if (!isIOS) { |
433 | | - ensureViewportMeta().content = 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; |
434 | | - resize(); |
435 | | - window.onload = resize; |
436 | | - window.onresize = resize; |
437 | | - // 监听键盘 F11 键按下事件,全屏时触发页面调整 |
438 | | - document.onkeydown = (e) => { |
439 | | - if (e && e.key === 'F11') { |
440 | | - setTimeout(() => { |
441 | | - resize(); |
442 | | - }, 100); |
| 257 | + let iosResizeTimer = null; |
| 258 | + const scheduleResize = () => { |
| 259 | + if (isIOS) { |
| 260 | + if (iosResizeTimer) { |
| 261 | + window.clearTimeout(iosResizeTimer); |
443 | 262 | } |
444 | | - }; |
445 | | - } else { |
| 263 | + iosResizeTimer = window.setTimeout(() => { |
| 264 | + resize(); |
| 265 | + iosResizeTimer = null; |
| 266 | + }, 500); |
| 267 | + return; |
| 268 | + } |
| 269 | + resize(); |
| 270 | + }; |
| 271 | + if (isIOS) { |
446 | 272 | const styleTag = document.createElement('style'); |
447 | | - styleTag.textContent = '* { font-synthesis: none !important; }'; |
| 273 | + styleTag.textContent = ` |
| 274 | + * { font-synthesis: none !important; } |
| 275 | + #ebg, |
| 276 | + #ebgOverlay { |
| 277 | + display: none !important; |
| 278 | + } |
| 279 | + `; |
448 | 280 | document.head.appendChild(styleTag); |
449 | | - if (isIOSPhone) { |
450 | | - html.classList.add('ios-phone-viewport'); |
451 | | - body.classList.add('ios-phone-viewport'); |
452 | | - ensureViewportMeta().content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover'; |
453 | | - installIOSPhoneGestureGuards(); |
454 | | - scheduleIOSPhoneLayout(); |
455 | | - window.addEventListener('load', scheduleIOSPhoneLayout); |
456 | | - window.addEventListener('resize', scheduleIOSPhoneLayout); |
457 | | - window.addEventListener('orientationchange', () => { |
458 | | - scheduleIOSPhoneLayout(); |
459 | | - setTimeout(scheduleIOSPhoneLayout, 300); |
460 | | - }); |
461 | | - } else { |
462 | | - ensureViewportMeta().content = 'width=device-width, initial-scale=0.22, minimum-scale=0.01, maximum-scale=1'; |
463 | | - } |
| 281 | + ensureViewportMeta().content = |
| 282 | + 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover'; |
| 283 | + } else { |
| 284 | + ensureViewportMeta().content = 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no'; |
464 | 285 | } |
| 286 | + scheduleResize(); |
| 287 | + window.onload = scheduleResize; |
| 288 | + window.onresize = scheduleResize; |
| 289 | + // 监听键盘 F11 键按下事件,全屏时触发页面调整 |
| 290 | + document.onkeydown = (e) => { |
| 291 | + if (e && e.key === 'F11') { |
| 292 | + setTimeout(() => { |
| 293 | + scheduleResize(); |
| 294 | + }, 100); |
| 295 | + } |
| 296 | + }; |
465 | 297 | })(); |
466 | 298 | </script> |
467 | 299 | <!-- 注册 Service Worker --> |
|
508 | 340 | }); |
509 | 341 | /** 点击屏幕,进入引擎主界面 */ |
510 | 342 | const enter = () => { |
| 343 | + const titleEnter = document.querySelector('.html-body__title-enter'); |
| 344 | + if (isIOS) { |
| 345 | + if (titleEnter) { |
| 346 | + titleEnter.style.pointerEvents = 'none'; |
| 347 | + titleEnter.style.transition = 'none'; |
| 348 | + titleEnter.style.opacity = '0'; |
| 349 | + titleEnter.style.display = 'none'; |
| 350 | + } |
| 351 | + enterPromiseResolve(); |
| 352 | + return; |
| 353 | + } |
511 | 354 | const initialBackground = document.querySelector('.title-enter__initial-background'); |
512 | 355 | if (initialBackground) { |
513 | 356 | initialBackground.style.opacity = '0'; |
|
522 | 365 | whiteBackground.style.opacity = '1'; |
523 | 366 | } |
524 | 367 | }, 50); // 在50ms后开始显示白色渐变 |
525 | | - const titleEnter = document.querySelector('.html-body__title-enter'); |
526 | 368 | setTimeout(() => { |
527 | 369 | if (titleEnter) titleEnter.style.opacity = '0'; |
528 | 370 | }, 500); // 500ms后开始降低落地页透明度 |
|
0 commit comments