Skip to content

Commit a037cae

Browse files
committed
Fix mobile PWA navigation behavior
1 parent 3ffc759 commit a037cae

6 files changed

Lines changed: 69 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- 新安装用户默认自动模式:PWA 独立窗口使用底部导航,普通浏览器使用顶部导航。
77
- 已有本地设置的 0.1.0 用户默认保留底部导航,避免升级后突然改变操作习惯。
88
- 配置导入导出和恢复默认同步支持移动端导航模式。
9+
- 修复 iOS PWA 底部导航栏安全区空白,并在下滑阅读时自动隐藏底部导航。
10+
- iOS 添加到主屏幕提示关闭后会记住选择,刷新后不再重复显示。
911
- 更新 Service Worker 静态缓存名到 `60s-web-static-v0.1.1`
1012

1113
## 0.1.0

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ PWA 只缓存应用壳和静态资源,新闻、天气、热榜等 API 数据
186186
- 设置页新增“移动端导航”,可选择自动、底部导航或顶部导航。
187187
- 配置导入导出和恢复默认同步支持移动端导航模式。
188188
- 顶部导航模式释放底部空间,底部导航模式保留单手操作体验。
189+
- 底部导航修复 iOS PWA 安全区空白,并在下滑阅读时自动隐藏。
190+
- 添加到主屏幕提示关闭后会记住选择,刷新后不再重复显示。
189191
- Service Worker 静态缓存名更新到 `60s-web-static-v0.1.1`
190192

191193
## 0.1.0 发布说明
@@ -344,6 +346,8 @@ server {
344346
- 工具页常用接口收藏能够在刷新后保留,并能快速切换到对应接口。
345347
- iPhone Safari 可以添加到主屏幕,打开后显示独立窗口和底部导航。
346348
- 设置页切换移动端导航模式后,顶部导航和底部导航显示符合预期。
349+
- 下滑阅读时底部导航自动隐藏,上滑或回到顶部后恢复显示。
350+
- 关闭 iOS 添加到主屏幕提示后,刷新页面不会再次显示。
347351
- 移动端底部导航切换页面后回到顶部,点击当前入口也能回到顶部。
348352
- 新版本 Service Worker 安装后能够显示更新提示并刷新到最新应用壳。
349353
- 断网后应用壳能够打开,API 内容显示现有失败提示。

src/App.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ export function App() {
359359
useState<ServiceWorkerRegistration | null>(null);
360360
const [showInstallHint, setShowInstallHint] = useState(false);
361361
const [isStandalone, setIsStandalone] = useState(isStandaloneDisplay);
362+
const [bottomNavHidden, setBottomNavHidden] = useState(false);
362363

363364
const daily = useApi<DailyNews>(
364365
apiBase,
@@ -519,7 +520,9 @@ export function App() {
519520
useEffect(() => registerServiceWorker(setServiceWorkerUpdate), []);
520521

521522
useEffect(() => {
522-
setShowInstallHint(shouldShowIosInstallHint());
523+
const dismissed =
524+
readStoredValue(STORAGE_KEYS.iosInstallHintDismissed, "false") === "true";
525+
setShowInstallHint(!dismissed && shouldShowIosInstallHint());
523526
}, []);
524527

525528
useEffect(() => {
@@ -657,6 +660,41 @@ export function App() {
657660
: "top"
658661
: mobileNavMode;
659662

663+
useEffect(() => {
664+
if (resolvedMobileNav !== "bottom") {
665+
setBottomNavHidden(false);
666+
return;
667+
}
668+
669+
let lastScrollY = window.scrollY;
670+
let ticking = false;
671+
672+
const updateNavVisibility = () => {
673+
const currentScrollY = window.scrollY;
674+
const delta = currentScrollY - lastScrollY;
675+
676+
if (currentScrollY < 80) {
677+
setBottomNavHidden(false);
678+
} else if (delta > 12) {
679+
setBottomNavHidden(true);
680+
} else if (delta < -12) {
681+
setBottomNavHidden(false);
682+
}
683+
684+
lastScrollY = currentScrollY;
685+
ticking = false;
686+
};
687+
688+
const handleScroll = () => {
689+
if (ticking) return;
690+
ticking = true;
691+
window.requestAnimationFrame(updateNavVisibility);
692+
};
693+
694+
window.addEventListener("scroll", handleScroll, { passive: true });
695+
return () => window.removeEventListener("scroll", handleScroll);
696+
}, [resolvedMobileNav]);
697+
660698
const reloadAll = () => {
661699
daily.reload();
662700
weather.reload();
@@ -715,7 +753,10 @@ export function App() {
715753
applyServiceWorkerUpdate(serviceWorkerUpdate);
716754
setServiceWorkerUpdate(null);
717755
}}
718-
onDismissInstallHint={() => setShowInstallHint(false)}
756+
onDismissInstallHint={() => {
757+
writeStoredValue(STORAGE_KEYS.iosInstallHintDismissed, "true");
758+
setShowInstallHint(false);
759+
}}
719760
/>
720761

721762
<main>
@@ -853,6 +894,7 @@ export function App() {
853894
activePage={activePage}
854895
setActivePage={setActivePage}
855896
variant="bottom"
897+
hidden={bottomNavHidden}
856898
/>
857899
)}
858900
</div>

src/components/MobileBottomNav.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ export function MobileNav({
77
activePage,
88
setActivePage,
99
variant,
10+
hidden = false,
1011
}: {
1112
activePage: PageId;
1213
setActivePage: (page: PageId) => void;
1314
variant: "bottom" | "top";
15+
hidden?: boolean;
1416
}) {
1517
return (
1618
<nav
17-
className={`mobile-nav mobile-${variant}-nav`}
19+
className={`mobile-nav mobile-${variant}-nav ${hidden ? "is-hidden" : ""}`}
1820
aria-label="移动端主导航"
1921
>
2022
{nav

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const STORAGE_KEYS = {
4141
chromeTheme: "60s-web:chrome-theme",
4242
colorTheme: "60s-web:color-theme",
4343
mobileNavMode: "60s-web:mobile-nav-mode",
44+
iosInstallHintDismissed: "60s-web:ios-install-hint-dismissed",
4445
homeCardLayout: "60s-web:home-card-layout",
4546
endpointFavorites: "60s-web:endpoint-favorites",
4647
quickFavorites: "60s-web:quick-favorites",

src/styles.css

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3473,7 +3473,7 @@ footer strong {
34733473
}
34743474

34753475
.app-shell.mobile-nav-bottom {
3476-
--mobile-nav-height: 78px;
3476+
--mobile-nav-height: calc(78px + env(safe-area-inset-bottom, 0px));
34773477
}
34783478

34793479
.topbar {
@@ -3588,17 +3588,29 @@ footer strong {
35883588
left: 10px;
35893589
right: 10px;
35903590
bottom: 8px;
3591+
bottom: max(8px, env(safe-area-inset-bottom, 0px));
35913592
z-index: 30;
35923593
display: grid;
35933594
grid-template-columns: repeat(5, minmax(0, 1fr));
35943595
gap: 4px;
3595-
min-height: calc(58px + env(safe-area-inset-bottom, 0px));
3596-
padding: 6px 7px calc(6px + env(safe-area-inset-bottom, 0px));
3596+
min-height: 58px;
3597+
padding: 6px 7px;
35973598
border: 1px solid rgba(15, 155, 142, 0.16);
35983599
border-radius: 18px;
35993600
background: rgba(255, 255, 255, 0.9);
36003601
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.16);
36013602
backdrop-filter: blur(18px);
3603+
transform: translateY(0);
3604+
transition:
3605+
opacity 0.22s ease,
3606+
transform 0.22s ease;
3607+
will-change: transform;
3608+
}
3609+
3610+
.mobile-bottom-nav.is-hidden {
3611+
opacity: 0;
3612+
pointer-events: none;
3613+
transform: translateY(calc(100% + 20px + env(safe-area-inset-bottom, 0px)));
36023614
}
36033615

36043616
.mobile-bottom-nav button {

0 commit comments

Comments
 (0)