mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
refactor: optimize initial loading screen layout and theme handling for improved PWA startup experience
This commit is contained in:
+135
-47
@@ -1,11 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN" style="
|
<html lang="zh-CN" style="
|
||||||
overflow: hidden auto;
|
overflow: hidden;
|
||||||
min-block-size: 100vh;
|
block-size: 100%;
|
||||||
min-block-size: 100dvh;
|
min-block-size: 100%;
|
||||||
--safe-area-inset-bottom: env(safe-area-inset-bottom);
|
--safe-area-inset-bottom: env(safe-area-inset-bottom);
|
||||||
--safe-area-inset-top: env(safe-area-inset-top);
|
--safe-area-inset-top: env(safe-area-inset-top);
|
||||||
background: var(--initial-loader-bg, #fff);
|
--initial-loader-bg: #0E1116;
|
||||||
|
--initial-loader-color: #9155FD;
|
||||||
|
--initial-loader-height: 100svh;
|
||||||
|
--initial-loader-width: 100vw;
|
||||||
|
background: var(--initial-loader-bg, #0E1116);
|
||||||
|
background-color: var(--initial-loader-bg, #0E1116);
|
||||||
">
|
">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@@ -94,54 +99,89 @@
|
|||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background: var(--initial-loader-bg, #fff);
|
background: var(--initial-loader-bg, #0E1116);
|
||||||
|
background-color: var(--initial-loader-bg, #0E1116);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-block-size: var(--initial-loader-height, 100svh);
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
min-block-size: 100%;
|
min-block-size: var(--initial-loader-height, 100svh);
|
||||||
|
background: var(--initial-loader-bg, #0E1116);
|
||||||
|
background-color: var(--initial-loader-bg, #0E1116);
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading-bg {
|
#loading-bg {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset-block-start: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
display: block;
|
display: block;
|
||||||
background: var(--initial-loader-bg, #fff);
|
overflow: hidden;
|
||||||
min-block-size: 100vh;
|
background: var(--initial-loader-bg, #0E1116);
|
||||||
min-block-size: 100dvh;
|
background-color: var(--initial-loader-bg, #0E1116);
|
||||||
transition: opacity 0.8s ease, transform 0.8s ease, filter 0.8s ease;
|
block-size: var(--initial-loader-height, 100svh);
|
||||||
|
inline-size: 100%;
|
||||||
|
transition: opacity 0.18s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-shell {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: minmax(0, 1fr) auto;
|
||||||
|
block-size: 100%;
|
||||||
|
inline-size: 100%;
|
||||||
|
padding:
|
||||||
|
calc(env(safe-area-inset-top, 0px) + 24px)
|
||||||
|
24px
|
||||||
|
calc(env(safe-area-inset-bottom, 0px) + 48px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-block-size: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-logo {
|
.loading-logo {
|
||||||
position: absolute;
|
display: flex;
|
||||||
inset-block-start: 35%;
|
align-items: center;
|
||||||
inset-inline-start: calc(50% - 5rem);
|
justify-content: center;
|
||||||
transition: opacity 0.8s ease, transform 0.8s ease, filter 0.8s ease;
|
inline-size: min(160px, 36vw);
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-complete .loading-logo {
|
.loading-logo img {
|
||||||
filter: blur(10px);
|
display: block;
|
||||||
opacity: 0;
|
block-size: auto;
|
||||||
transform: scale(1.5);
|
inline-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-block-size: clamp(72px, 14vh, 120px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-complete {
|
.loading-complete {
|
||||||
filter: blur(15px);
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
position: absolute;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 3px solid transparent;
|
border: 3px solid transparent;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
block-size: 55px;
|
block-size: 46px;
|
||||||
inline-size: 55px;
|
inline-size: 46px;
|
||||||
inset-block-start: 80%;
|
|
||||||
inset-inline-start: calc(50% - 27.5px);
|
|
||||||
transition: opacity 0.6s ease;
|
transition: opacity 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +244,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2500;
|
z-index: 2500;
|
||||||
display: none;
|
display: none;
|
||||||
inset-block-end: 20px;
|
inset-block-end: calc(env(safe-area-inset-bottom, 0px) + 24px);
|
||||||
inset-inline-start: 50%;
|
inset-inline-start: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background: rgba(0, 0, 0, 0.8);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
@@ -215,7 +255,8 @@
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
white-space: nowrap;
|
max-inline-size: calc(100% - 32px);
|
||||||
|
white-space: normal;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
@@ -239,25 +280,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主题色彩初始化
|
// 根据当前主题提前确定启动屏色彩,避免 iOS PWA 从原生启动图切到网页时露出默认白底。
|
||||||
let loaderColor = localStorage.getItem('materio-initial-loader-bg')
|
const launchThemeBackgrounds = {
|
||||||
let primaryColor = localStorage.getItem('materio-initial-loader-color')
|
light: '#F4F5FA',
|
||||||
|
dark: '#0E1116',
|
||||||
// 检查主题设置
|
purple: '#28243D',
|
||||||
const savedTheme = localStorage.getItem('theme') || 'auto'
|
transparent: '#1C1C1C',
|
||||||
const isAutoTheme = savedTheme === 'auto'
|
default: '#F4F5FA',
|
||||||
|
|
||||||
// 如果是自动主题或者没有保存的背景色,根据系统主题设置背景色
|
|
||||||
if (isAutoTheme || !loaderColor) {
|
|
||||||
loaderColor = checkPrefersColorSchemeIsDark() ? '#0E1116' : '#FFFFFF'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const savedTheme = localStorage.getItem('theme') || 'auto'
|
||||||
|
const resolvedLaunchTheme = savedTheme === 'auto'
|
||||||
|
? (checkPrefersColorSchemeIsDark() ? 'dark' : 'light')
|
||||||
|
: savedTheme
|
||||||
|
|
||||||
|
let loaderColor = localStorage.getItem('materio-initial-loader-bg')
|
||||||
|
|| launchThemeBackgrounds[resolvedLaunchTheme]
|
||||||
|
|| launchThemeBackgrounds.light
|
||||||
|
|
||||||
|
let primaryColor = localStorage.getItem('materio-initial-loader-color')
|
||||||
if (!primaryColor) {
|
if (!primaryColor) {
|
||||||
primaryColor = '#9155FD'
|
primaryColor = '#9155FD'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在应用脚本接管前锁定一次启动期视口,避免 iOS 独立模式首次重算 safe area 时把 logo 顶下去。
|
||||||
|
function syncInitialViewport(force) {
|
||||||
|
const viewport = window.visualViewport
|
||||||
|
const nextHeight = Math.round(viewport?.height || window.innerHeight || document.documentElement.clientHeight || 0)
|
||||||
|
const nextWidth = Math.round(viewport?.width || window.innerWidth || document.documentElement.clientWidth || 0)
|
||||||
|
const currentHeight = parseInt(
|
||||||
|
document.documentElement.style.getPropertyValue('--initial-loader-height') || '0',
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!nextHeight || !nextWidth) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force && currentHeight && Math.abs(nextHeight - currentHeight) < 120) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--initial-loader-height', `${nextHeight}px`)
|
||||||
|
document.documentElement.style.setProperty('--initial-loader-width', `${nextWidth}px`)
|
||||||
|
document.documentElement.style.setProperty('--vh', `${nextHeight * 0.01}px`)
|
||||||
|
}
|
||||||
|
|
||||||
// 应用主题色彩
|
// 应用主题色彩
|
||||||
|
document.documentElement.setAttribute('data-launch-theme', resolvedLaunchTheme)
|
||||||
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
|
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
|
||||||
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
|
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
|
||||||
|
document.documentElement.style.backgroundColor = loaderColor
|
||||||
|
syncInitialViewport(true)
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.body.style.backgroundColor = loaderColor
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('orientationchange', () => {
|
||||||
|
window.setTimeout(() => syncInitialViewport(true), 160)
|
||||||
|
})
|
||||||
|
|
||||||
// 状态栏适配
|
// 状态栏适配
|
||||||
if (window.navigator.standalone) {
|
if (window.navigator.standalone) {
|
||||||
@@ -349,14 +431,20 @@
|
|||||||
|
|
||||||
<body style="margin: 0; overflow: hidden; overscroll-behavior: none; -webkit-overflow-scrolling: touch">
|
<body style="margin: 0; overflow: hidden; overscroll-behavior: none; -webkit-overflow-scrolling: touch">
|
||||||
<div id="loading-bg">
|
<div id="loading-bg">
|
||||||
<div class="loading-logo">
|
<div class="loading-shell">
|
||||||
<!-- Logo -->
|
<div class="loading-main">
|
||||||
<img src="/logo.svg" alt="MoviePilot" width="160px" height="160px" />
|
<div class="loading-logo">
|
||||||
</div>
|
<!-- Logo -->
|
||||||
<div class="loading">
|
<img src="/logo.svg" alt="MoviePilot" width="160" height="160" />
|
||||||
<div class="effect-1 effects"></div>
|
</div>
|
||||||
<div class="effect-2 effects"></div>
|
</div>
|
||||||
<div class="effect-3 effects"></div>
|
<div class="loading-footer">
|
||||||
|
<div class="loading">
|
||||||
|
<div class="effect-1 effects"></div>
|
||||||
|
<div class="effect-2 effects"></div>
|
||||||
|
<div class="effect-3 effects"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 超时提示 - 默认隐藏 -->
|
<!-- 超时提示 - 默认隐藏 -->
|
||||||
<div id="loading-timeout"></div>
|
<div id="loading-timeout"></div>
|
||||||
|
|||||||
+20
-2
@@ -20,6 +20,16 @@ let themeValue = localStorage.getItem('theme') || 'auto'
|
|||||||
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
||||||
globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
||||||
|
|
||||||
|
// 启动屏和 iOS safe area 在同一层显示,根节点底色需要尽早和当前主题保持一致。
|
||||||
|
function syncRootLaunchPalette() {
|
||||||
|
const { background, primary } = globalTheme.current.value.colors
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty('--initial-loader-bg', background)
|
||||||
|
document.documentElement.style.setProperty('--initial-loader-color', primary)
|
||||||
|
document.documentElement.style.backgroundColor = background
|
||||||
|
document.body.style.backgroundColor = background
|
||||||
|
}
|
||||||
|
|
||||||
// 生效语言
|
// 生效语言
|
||||||
const localeValue = getBrowserLocale()
|
const localeValue = getBrowserLocale()
|
||||||
setI18nLanguage(localeValue as SupportedLocale)
|
setI18nLanguage(localeValue as SupportedLocale)
|
||||||
@@ -73,6 +83,7 @@ const stopHeartbeat = () => {
|
|||||||
function updateHtmlThemeAttribute(themeName: string) {
|
function updateHtmlThemeAttribute(themeName: string) {
|
||||||
document.documentElement.setAttribute('data-theme', themeName)
|
document.documentElement.setAttribute('data-theme', themeName)
|
||||||
document.body.setAttribute('data-theme', themeName)
|
document.body.setAttribute('data-theme', themeName)
|
||||||
|
syncRootLaunchPalette()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取背景图片
|
// 获取背景图片
|
||||||
@@ -126,8 +137,15 @@ function startBackgroundRotation() {
|
|||||||
function animateAndRemoveLoader() {
|
function animateAndRemoveLoader() {
|
||||||
const loadingBg = document.querySelector('#loading-bg') as HTMLElement
|
const loadingBg = document.querySelector('#loading-bg') as HTMLElement
|
||||||
if (loadingBg) {
|
if (loadingBg) {
|
||||||
removeEl('#loading-bg')
|
// 先淡出启动层,再移除节点,避免 iOS 在主题容器完全接管前露出底部空白。
|
||||||
document.documentElement.style.removeProperty('background')
|
loadingBg.classList.add('loading-complete')
|
||||||
|
window.setTimeout(() => {
|
||||||
|
removeEl('#loading-bg')
|
||||||
|
|
||||||
|
// 启动阶段会锁定根节点滚动,待应用布局接管后再恢复,避免首屏出现瞬时纵向滚动条。
|
||||||
|
document.documentElement.style.removeProperty('overflow')
|
||||||
|
document.body.style.removeProperty('overflow')
|
||||||
|
}, 180)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user