mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 11:37:29 +08:00
fix: stabilize iOS Safari mobile navigation
This commit is contained in:
@@ -170,6 +170,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layout-wrapper.layout-nav-type-vertical {
|
.layout-wrapper.layout-nav-type-vertical {
|
||||||
|
--layout-navbar-block-size: calc(
|
||||||
|
env(safe-area-inset-top, 0px) + #{variables.$layout-vertical-nav-navbar-height} + var(--navbar-tab-height)
|
||||||
|
);
|
||||||
|
|
||||||
// TODO(v2): Check why we need height in vertical nav & min-height in horizontal nav
|
// TODO(v2): Check why we need height in vertical nav & min-height in horizontal nav
|
||||||
min-block-size: 100%;
|
min-block-size: 100%;
|
||||||
|
|
||||||
@@ -185,13 +189,16 @@ export default defineComponent({
|
|||||||
.layout-navbar {
|
.layout-navbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
|
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
|
||||||
|
// iOS Safari 在地址栏收起和惯性滚动时可能把 fixed 顶栏和页面滚动层合成到一起,
|
||||||
|
// 单独提升顶栏图层可避免导航栏短暂上移到安全区下方。
|
||||||
|
backface-visibility: hidden;
|
||||||
|
block-size: var(--layout-navbar-block-size);
|
||||||
inline-size: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
|
inline-size: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
|
||||||
inset-block-start: 0;
|
inset-block-start: 0;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
|
||||||
.navbar-content-container {
|
.navbar-content-container {
|
||||||
block-size: calc(
|
block-size: var(--layout-navbar-block-size);
|
||||||
env(safe-area-inset-top) + variables.$layout-vertical-nav-navbar-height + var(--navbar-tab-height)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@at-root {
|
@at-root {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ body {
|
|||||||
background: rgb(var(--v-theme-background));
|
background: rgb(var(--v-theme-background));
|
||||||
overscroll-behavior-y: contain;
|
overscroll-behavior-y: contain;
|
||||||
|
|
||||||
--webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { checkPWAStatus, isPWADisplayMode } from '@/@core/utils/navigator'
|
import { checkPWAStatus, isMobileDevice, isPWADisplayMode } from '@/@core/utils/navigator'
|
||||||
|
|
||||||
// 全局PWA状态,确保只初始化一次
|
// 全局PWA状态,确保只初始化一次
|
||||||
const globalPwaStatus = ref<{
|
const globalPwaStatus = ref<{
|
||||||
@@ -34,11 +34,14 @@ async function initializePWAGlobally() {
|
|||||||
globalPwaStatus.value = await checkPWAStatus()
|
globalPwaStatus.value = await checkPWAStatus()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to detect PWA status', error)
|
console.error('Failed to detect PWA status', error)
|
||||||
|
const isStandaloneMode = isPWADisplayMode()
|
||||||
|
|
||||||
// 即使检测失败,也设置一个合理的默认值
|
// 即使检测失败,也设置一个合理的默认值
|
||||||
globalPwaStatus.value = {
|
globalPwaStatus.value = {
|
||||||
hasPWAFeatures: false,
|
hasPWAFeatures: false,
|
||||||
isStandaloneMode: isPWADisplayMode(),
|
isStandaloneMode,
|
||||||
isPWAEnvironment: isPWADisplayMode(),
|
// iOS Safari 浏览器模式可能取不到 Service Worker 注册信息,但移动端仍应使用 App 交互。
|
||||||
|
isPWAEnvironment: isStandaloneMode || isMobileDevice(),
|
||||||
isFullPWA: false,
|
isFullPWA: false,
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -56,7 +59,8 @@ export function usePWA() {
|
|||||||
|
|
||||||
// 基于新的PWA状态结构
|
// 基于新的PWA状态结构
|
||||||
const pwaMode = computed(() => {
|
const pwaMode = computed(() => {
|
||||||
return globalPwaStatus.value?.isPWAEnvironment ?? false
|
// PWA 状态异步恢复前先用移动端特征兜底,避免 Safari 浏览器首屏阶段缺少移动端交互。
|
||||||
|
return globalPwaStatus.value?.isPWAEnvironment ?? isMobileDevice()
|
||||||
})
|
})
|
||||||
|
|
||||||
const appMode = computed(() => {
|
const appMode = computed(() => {
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const indicatorTransform = computed(() => {
|
const indicatorTransform = computed(() => {
|
||||||
return `translate(-50%, ${Math.min(60 + pullDistance.value - config.SHOW_INDICATOR, 70)}px)`
|
// 顶部基准位置由布局 CSS 负责,这里只让指示器跟随下拉手势轻微移动。
|
||||||
|
const followOffset = Math.min(Math.max(pullDistance.value - config.SHOW_INDICATOR, 0), 16)
|
||||||
|
|
||||||
|
return `translate3d(-50%, ${followOffset}px, 0)`
|
||||||
})
|
})
|
||||||
|
|
||||||
// 弹窗检测函数
|
// 弹窗检测函数
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ onMounted(async () => {
|
|||||||
v-if="appMode && showPullIndicator"
|
v-if="appMode && showPullIndicator"
|
||||||
class="pull-indicator"
|
class="pull-indicator"
|
||||||
:style="{
|
:style="{
|
||||||
|
'--pull-indicator-navbar-extra-height': navbarExtraHeight,
|
||||||
opacity: indicatorOpacity,
|
opacity: indicatorOpacity,
|
||||||
transform: indicatorTransform,
|
transform: indicatorTransform,
|
||||||
}"
|
}"
|
||||||
@@ -475,7 +476,7 @@ onMounted(async () => {
|
|||||||
<!-- 👉 Navbar -->
|
<!-- 👉 Navbar -->
|
||||||
<template #navbar="{ toggleVerticalOverlayNavActive }">
|
<template #navbar="{ toggleVerticalOverlayNavActive }">
|
||||||
<div
|
<div
|
||||||
class="theme-navbar-row d-flex h-14 align-center mx-1"
|
class="theme-navbar-row d-flex h-full align-center mx-1"
|
||||||
:class="{ 'theme-navbar-row--horizontal': showHorizontalThemeNav }"
|
:class="{ 'theme-navbar-row--horizontal': showHorizontalThemeNav }"
|
||||||
>
|
>
|
||||||
<RouterLink v-if="showHorizontalThemeNav" :to="canAdmin ? '/dashboard' : '/apps'" class="theme-horizontal-logo">
|
<RouterLink v-if="showHorizontalThemeNav" :to="canAdmin ? '/dashboard' : '/apps'" class="theme-horizontal-logo">
|
||||||
@@ -801,6 +802,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.pull-indicator {
|
.pull-indicator {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
z-index: 20;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -809,11 +811,14 @@ onMounted(async () => {
|
|||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
background: rgba(var(--v-theme-surface), 0.3);
|
background: rgba(var(--v-theme-surface), 0.3);
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 10%), 0 1px 3px rgba(0, 0, 0, 6%);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 10%), 0 1px 3px rgba(0, 0, 0, 6%);
|
||||||
inset-block-start: 80px;
|
inset-block-start: calc(
|
||||||
|
env(safe-area-inset-top, 0px) + 4rem + var(--pull-indicator-navbar-extra-height, 0rem) + 0.75rem
|
||||||
|
);
|
||||||
inset-inline-start: 50%;
|
inset-inline-start: 50%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform: translateX(-50%);
|
transform: translate3d(-50%, 0, 0);
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
will-change: opacity, transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator-icon {
|
.indicator-icon {
|
||||||
|
|||||||
Reference in New Issue
Block a user