vivid 手机端导航重做:底部玻璃标签栏一次直达,替掉 dock + 汉堡 + 上拉菜单
- 五栏目平铺常驻拇指区,当前项分段高亮(与首页工具条分段控件同语言), 44px 触达高度 + :active 缩放反馈,safe-area 适配保留 - 去掉菜单里的 01-05 编号、逐行分隔线与站点描述,导航只做导航 - 明暗切换:手机端挪到页脚 foot-nav 末尾的 ◐(≤720px 显示), 桌面仍走顶栏主题键;QuickShort 的 FAB 底部给标签栏让位
This commit is contained in:
1 parent
2bd38551a4
commit
8fb15861fe
3 files changed
+73
-179
No files matched your search
@@ -1,31 +1,26 @@
|
||||
<script setup>
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
import { site } from '../../site'
|
||||
import NavBurger from '../../components/NavBurger.vue'
|
||||
import AuthorAvatar from '../../components/AuthorAvatar.vue'
|
||||
|
||||
// 余白顶栏。对应设计稿的 <header class="top">:品牌 / 导航 / 操作三块,
|
||||
// 滚动过 8px 加 .scrolled(设计稿的「四态里的第一态」)。
|
||||
// 样式在 ui/yohaku/chrome.css,选择器已收敛到 html[data-ui="vivid"] 下。
|
||||
//
|
||||
// 窄屏例外:设计稿在窄屏把导航整个 display:none 掉了,手机上会没有任何导航入口。
|
||||
// 这里补一个汉堡钮 + 全屏菜单(chrome.css 的 .menu)。菜单 z-index 压在顶栏
|
||||
// 之下:顶栏(品牌 + 汉堡)浮在菜单上不消失,汉堡原位并成 ✕,不再需要一个
|
||||
// 重复的菜单头。
|
||||
// 窄屏例外:顶栏整条退场,导航收进底部玻璃标签栏(.v-tabbar,chrome.css)——
|
||||
// 五个栏目一次点击直达,不再需要汉堡 + 上拉菜单。明暗切换在手机上挪到页脚
|
||||
// (YohakuFoot 的 .theme-toggle),桌面仍走顶栏这枚 ◐。
|
||||
|
||||
const route = useRoute()
|
||||
const scrolled = ref(false)
|
||||
const menuOpen = ref(false)
|
||||
const burger = ref(null)
|
||||
const menuEl = ref(null)
|
||||
|
||||
const links = [
|
||||
{ to: '/', label: '时间线', en: 'Timeline' },
|
||||
{ to: '/archive', label: '归档', en: 'Archive' },
|
||||
{ to: '/tags', label: '标签', en: 'Tags' },
|
||||
{ to: '/projects', label: '作品', en: 'Projects' },
|
||||
{ to: '/about', label: '关于', en: 'About' }
|
||||
{ to: '/', label: '时间线' },
|
||||
{ to: '/archive', label: '归档' },
|
||||
{ to: '/tags', label: '标签' },
|
||||
{ to: '/projects', label: '作品' },
|
||||
{ to: '/about', label: '关于' }
|
||||
]
|
||||
|
||||
const title = () => site.site_title?.split(/[·|]/)[0]?.trim() || 'ONE'
|
||||
@@ -38,50 +33,15 @@ function onScroll() {
|
||||
scrolled.value = (window.scrollY || 0) > 8
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
menuOpen.value ? closeMenu() : openMenu()
|
||||
}
|
||||
function openMenu() {
|
||||
menuOpen.value = true
|
||||
// 焦点交给对话框本身(aria-modal 承诺过),Tab 第一下才落到「时间线」。
|
||||
// 不直接 focus 第一个链接:即使 touch 上不长出焦点环,也会让首项看起来「被选中」,
|
||||
// 和 .on 的高亮抢意思。preventScroll 防止 iOS 顺手滚背景。
|
||||
nextTick(() => menuEl.value?.focus({ preventScroll: true }))
|
||||
}
|
||||
function closeMenu() {
|
||||
menuOpen.value = false
|
||||
const b = burger.value?.$el || burger.value
|
||||
nextTick(() => b?.focus?.({ preventScroll: true }))
|
||||
}
|
||||
|
||||
// 菜单铺满全屏时锁住底层滚动(iOS 上不锁背景会跟着手指滑)
|
||||
watch(menuOpen, (v) => { document.body.style.overflow = v ? 'hidden' : '' })
|
||||
// 点了链接 → 路由变了 → 菜单收起
|
||||
watch(() => route.fullPath, () => { menuOpen.value = false })
|
||||
|
||||
function onKey(e) {
|
||||
if (e.key === 'Escape') closeMenu()
|
||||
}
|
||||
|
||||
// 窗口拉宽回桌面必须自己退场:v-if 是 JS 状态,CSS 媒体查询管不着它
|
||||
const mqWide = window.matchMedia('(min-width: 721px)')
|
||||
function onWide(e) { if (e.matches) menuOpen.value = false }
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
window.addEventListener('keydown', onKey)
|
||||
mqWide.addEventListener('change', onWide)
|
||||
onScroll()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
window.removeEventListener('keydown', onKey)
|
||||
mqWide.removeEventListener('change', onWide)
|
||||
document.body.style.overflow = ''
|
||||
menuOpen.value = false
|
||||
})
|
||||
|
||||
// 设计稿里主题键是 ◐ / ◑:亮色时显示 ◐(点它去暗),暗色时显示 ◑。
|
||||
// 设计稿里主题键是 ◐:亮色时显示(点它去暗),暗色时切回亮色。
|
||||
function toggleTheme() {
|
||||
// site.themeMode 是站点级偏好(auto / light / dark),site.js 里有 watcher 负责落盘与写 data-theme。
|
||||
// 这里直接改它,不自己碰 document : 单一数据源。
|
||||
@@ -111,54 +71,16 @@ function toggleTheme() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 窄屏:底部悬浮 dock(站名 + 汉堡),常驻拇指区。桌面 display:none。
|
||||
必须待在 .top 外面:.top 有 backdrop-filter,会给 fixed 后代当包含块。
|
||||
站标「一横」不在这里出现——手机上导航只要文字和按钮,圆标只留桌面顶栏。 -->
|
||||
<div class="dock">
|
||||
<RouterLink class="dock-brand" to="/">
|
||||
<span>{{ title() }}</span>
|
||||
</RouterLink>
|
||||
<NavBurger
|
||||
ref="burger"
|
||||
:open="menuOpen"
|
||||
aria-controls="v-menu"
|
||||
:aria-label="menuOpen ? '关闭菜单' : '打开菜单'"
|
||||
@toggle="toggleMenu"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 窄屏上拉菜单:scrim + 圆角毛玻璃 sheet,从 dock 上方长出来 -->
|
||||
<Transition name="v-menu">
|
||||
<div
|
||||
v-if="menuOpen"
|
||||
id="v-menu"
|
||||
ref="menuEl"
|
||||
class="menu-root"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="站点导航"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="scrim" @click="closeMenu" />
|
||||
<div class="menu">
|
||||
<nav class="menu-in" aria-label="站点导航">
|
||||
<RouterLink
|
||||
v-for="(l, i) in links"
|
||||
:key="l.to"
|
||||
:to="l.to"
|
||||
:class="{ on: isOn(l) }"
|
||||
:style="{ '--i': i }"
|
||||
>
|
||||
<span class="idx">{{ String(i + 1).padStart(2, '0') }}</span>
|
||||
<span class="zh">{{ l.label }}</span>
|
||||
<em>{{ l.en }}</em>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
<div class="menu-foot">
|
||||
<button class="chip" type="button" @click="toggleTheme">◐ 切换明暗</button>
|
||||
<p v-if="site.site_desc" class="m-desc">{{ site.site_desc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<!-- 窄屏:底部玻璃标签栏。五个栏目平铺一次直达,当前项用分段高亮
|
||||
(与首页工具条「全部 / 长文 / 短文」的分段控件同一语言)。
|
||||
必须待在 .top 外面:.top 有 backdrop-filter,会给 fixed 后代当包含块。 -->
|
||||
<nav class="v-tabbar" aria-label="站点导航">
|
||||
<RouterLink
|
||||
v-for="l in links"
|
||||
:key="l.to"
|
||||
:to="l.to"
|
||||
:class="{ on: isOn(l) }"
|
||||
:aria-current="isOn(l) ? 'page' : undefined"
|
||||
>{{ l.label }}</RouterLink>
|
||||
</nav>
|
||||
</template>
|
||||
Reference in new issue
Block a user