personal-website/src/components/Header.vue
2024-05-15 20:42:00 +08:00

150 lines
4 KiB
Vue

<script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core'
import { computed, onMounted, ref, unref } from 'vue'
import ThemeToggle from './ThemeToggle.vue'
import siteConfig from '@/site-config'
import { getLinkTarget } from '@/utils/link'
const navLinks = siteConfig.header.navLinks || []
const socialLinks = computed(() => {
return siteConfig.socialLinks.filter((link: Record<string, any>) => {
if (link.header && typeof link.header === 'boolean') {
return link
}
else if (link.header && typeof link.header === 'string') {
link.icon = link.header.includes('i-') ? link.header : link.icon
return link
}
else {
return false
}
})
})
const { y: scroll } = useWindowScroll()
const oldScroll = ref(unref(scroll))
onMounted(() => {
const navMask = document.querySelector('.nav-drawer-mask') as HTMLElement
navMask?.addEventListener('touchmove', (event) => {
event.preventDefault()
})
const headerEl = document.querySelector('#header') as HTMLElement
if (!headerEl)
return
if (document.documentElement.scrollTop > 100)
headerEl.classList.add('header-bg-blur')
window.addEventListener('scroll', () => {
if (scroll.value < 150) {
headerEl.classList.remove('header-hide')
return
}
if (scroll.value - oldScroll.value > 150) {
headerEl.classList.add('header-hide')
oldScroll.value = scroll.value
}
if (oldScroll.value - scroll.value > 150) {
headerEl.classList.remove('header-hide')
oldScroll.value = scroll.value
}
})
})
function toggleNavDrawer() {
const drawer = document.querySelector('.nav-drawer') as HTMLElement
const mask = document.querySelector('.nav-drawer-mask') as HTMLElement
if (!drawer || !mask)
return
if (drawer.style.transform === `translateX(0%)`) {
drawer.style.transform = `translateX(-100%)`
mask.style.display = `none`
}
else {
drawer.style.transform = `translateX(0%)`
mask.style.display = `block`
}
}
</script>
<template>
<header
id="header" :class="{ 'header-bg-blur': scroll > 20 }"
class="!fixed bg-transparent z-899 w-screen h-20 px-6 flex justify-between items-center relative"
>
<div class="flex items-center h-full">
<a href="/" mr-6 aria-label="Header Logo Image">
<img width="32" height="32" :src="siteConfig.header.logo.src" :alt="siteConfig.header.logo.alt">
</a>
<nav class="sm:flex hidden flex-wrap gap-x-6 position-initial flex-row">
<a
v-for="link in navLinks" :key="link.text" :aria-label="`${link.text}`" :target="getLinkTarget(link.href)"
nav-link :href="link.href"
>
{{ link.text }}
</a>
</nav>
<div sm:hidden h-full flex items-center @click="toggleNavDrawer()">
<menu i-ri-menu-2-fill />
</div>
</div>
<div class="flex gap-x-6">
<a
v-for="link in socialLinks" :key="link.text" :aria-label="`${link.text}`" :class="link.icon" nav-link
:target="getLinkTarget(link.href)" :href="link.href"
/>
<a nav-link target="_blank" href="/rss.xml" i-ri-rss-line aria-label="RSS" />
<ThemeToggle />
</div>
</header>
<nav
class="nav-drawer sm:hidden"
>
<i i-ri-menu-2-fill />
<a
v-for="link in navLinks" :key="link.text" :aria-label="`${link.text}`" :target="getLinkTarget(link.href)"
nav-link :href="link.href" @click="toggleNavDrawer()"
>
{{ link.text }}
</a>
</nav>
<div class="nav-drawer-mask" @click="toggleNavDrawer()" />
</template>
<style scoped>
.header-hide {
transform: translateY(-100%);
transition: transform 0.4s ease;
}
.header-bg-blur {
--at-apply: backdrop-blur-sm;
}
.nav-drawer {
transform: translateX(-100%);
--at-apply: box-border fixed h-screen z-999 left-0 top-0 min-w-32vw max-w-50vw
bg-main p-6 text-lg flex flex-col gap-5 transition-all;
}
.nav-drawer-mask {
display: none;
--at-apply: transition-all;
content: '';
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
z-index: 998;
}
</style>