Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update sticky header scroll behavior #236

Merged
merged 6 commits into from
Oct 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 131 additions & 101 deletions packages/frontendmu-nuxt/components/site/Menu.vue
Original file line number Diff line number Diff line change
@@ -1,161 +1,195 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { onMounted } from "vue";
Saamiyah marked this conversation as resolved.
Show resolved Hide resolved

const currentPath = computed(() => useRoute().path)

const router = useRouter()
const router = useRouter();

interface TMenuItem {
title: string
href: string
class?: string
children?: TMenuItem[]
title: string;
href: string;
class?: string;
children?: TMenuItem[];
}

interface TMenu {
[key: string]: TMenuItem
[key: string]: TMenuItem;
}

const links: TMenu = {
about: {
title: 'About',
href: '/about',
class: 'hidden md:block',
title: "About",
href: "/about",
class: "hidden md:block",
children: [
// {
// title: 'FAQ',
// href: '/faq',
// class: '',
// },
{
title: 'History',
href: '/history',
class: '',
title: "History",
href: "/history",
class: "",
},
{
title: 'Contribute',
href: '/contribute',
class: '',
title: "Contribute",
href: "/contribute",
class: "",
},
{
title: 'Code of conduct',
href: '/code_of_conduct',
class: '',
title: "Code of conduct",
href: "/code_of_conduct",
class: "",
},
{
title: 'Coding Guidelines',
href: '/coding_guidelines',
class: '',
title: "Coding Guidelines",
href: "/coding_guidelines",
class: "",
},
{
title: 'WhatsApp',
href: 'https://chat.whatsapp.com/invite/0kQ2QX0ZQ0j1YQ4X6Q4Q4Q',
class: '',
title: "WhatsApp",
href: "https://chat.whatsapp.com/invite/0kQ2QX0ZQ0j1YQ4X6Q4Q4Q",
class: "",
},
{
title: 'Instagram',
href: 'https://www.instagram.com/frontend.mu/?ref=frontend.mu',
class: '',
title: "Instagram",
href: "https://www.instagram.com/frontend.mu/?ref=frontend.mu",
class: "",
},
{
title: 'LinkedIn',
href: 'https://www.linkedin.com/company/81846464/admin/?ref=frontend.mu',
class: '',
title: "LinkedIn",
href: "https://www.linkedin.com/company/81846464/admin/?ref=frontend.mu",
class: "",
},
{
title: 'Join Discord',
href: 'https://discord.gg/WxXW9Jvv6k?ref=frontend.mu',
class: '',
title: "Join Discord",
href: "https://discord.gg/WxXW9Jvv6k?ref=frontend.mu",
class: "",
},
{
title: 'GitHub',
href: 'https://github.com/Front-End-Coders-Mauritius?ref=frontend.mu',
class: '',
title: "GitHub",
href: "https://github.com/Front-End-Coders-Mauritius?ref=frontend.mu",
class: "",
},
{
title: 'Twitter',
href: 'https://twitter.com/frontendmu?ref=frontend.mu',
class: '',
title: "Twitter",
href: "https://twitter.com/frontendmu?ref=frontend.mu",
class: "",
},
],
},
meetups: {
title: 'Meetups',
href: '/meetups',
title: "Meetups",
href: "/meetups",
},
community: {
title: 'Community',
href: '/community',
title: "Community",
href: "/community",
},
team: {
title: 'Team',
href: '/team',
class: 'hidden md:block',
title: "Team",
href: "/team",
class: "hidden md:block",
},
sponsors: {
title: 'Sponsors',
href: '/sponsors',
class: 'hidden md:block',
title: "Sponsors",
href: "/sponsors",
class: "hidden md:block",
},
}

function makeHeaderSticky() {
const header = document.querySelector('.menu-wrapper')
if (!header)
return

function handleIntersection(entries: IntersectionObserverEntry[]) {
entries.forEach((entry) => {
if (!header)
return
if (entry.isIntersecting) {
header.classList.remove('intersect')
}
else {
header.classList.add('intersect')
}
})
}

const options = {
root: null,
rootMargin: '-200px 0px 0px 0px',
threshold: 0,
};

function toggleHeader() {
const headerElement = document.querySelector(".menu-wrapper") as HTMLElement;

// headerOffset tracks how much the header has been moved vertically
let headerOffset = 0;

// This keeps track of the previous scroll position to calculate whether the user is scrolling up or down.
let previousScrollPosition = 0;

if (headerElement) {
const handleScroll = () => {
window.requestAnimationFrame(() => {
const headerHeight = headerElement.clientHeight;

// the current vertical scroll position of the page
const currentScrollPosition =
window.scrollY || document.documentElement.scrollTop;

// the distance that the user has scrolled since the last scroll event
const distance = currentScrollPosition - previousScrollPosition;

// New vertical position of the header
const nextHeaderOffset = Math.min(
Math.max(headerOffset + distance, 0),
headerHeight
);

// checks if the user has scrolled past the header and nextHeaderOffset differs from the current position
if (
currentScrollPosition >= headerHeight &&
nextHeaderOffset !== headerOffset
) {
headerOffset = nextHeaderOffset;
headerElement.style.transform = `translateY(-${headerOffset}px)`;
}

// if the user has scrolled past the header, we add these classes
if (currentScrollPosition > headerHeight) {
headerElement.classList.add(
"intersect",
"shadow-sm",
"dark:bg-verse-900/50",
"bg-verse-50/50"
);
} else {
headerElement.classList.remove(
"intersect",
"shadow-sm",
"dark:bg-verse-900/50",
"bg-verse-50/50"
);
}

previousScrollPosition = currentScrollPosition;
});
};

window.addEventListener("scroll", handleScroll, { passive: true });
}

const observer = new IntersectionObserver(handleIntersection, options)
const target = document.querySelector('#sticky-observer')

if (!target)
return

observer.observe(target)
}

function handleRightClick(event: MouseEvent) {
// prevent default and navigate to /branding
event.preventDefault()
router.push('/branding')
event.preventDefault();
router.push("/branding");
}

onMounted(makeHeaderSticky)
onMounted(toggleHeader);
</script>

<template>
<div class="menu-wrapper w-full flex justify-between contain sticky top-0 z-30 h-32 items-center">
<div class="menu-wrapper w-full flex sticky top-0 z-30 h-20 items-center">
<div class="menu theme-light w-full">
<div class="flex justify-between items-center">
<div class="flex justify-between items-center contain">
<div class="flex">
<NuxtLink href="/" class="flex gap-2 text-verse-500 dark:text-verse-200" title="frontend.mu"
@contextmenu="handleRightClick">
<NuxtLink
href="/"
class="flex gap-2 text-verse-500 dark:text-verse-200"
title="frontend.mu"
@contextmenu="handleRightClick"
>
<SiteLogo class="w-10" />
<span class="hidden text-lg font-bold leading-none tracking-tighter md:text-3xl md:block">
<span
class="hidden text-lg font-bold leading-none tracking-tighter md:text-3xl md:block"
>
frontend.mu
</span>
</NuxtLink>
</div>
<nav>
<ul class="nav-links text-sm md:text-sm lg:text-base flex md:gap-4 font-medium font-heading">
<ul
class="nav-links text-sm md:text-sm lg:text-base flex md:gap-4 font-medium font-heading"
>
<template v-for="item of Object.keys(links)" :key="item">
<SiteMenuItem :links="links" :item="item" />
</template>
Expand All @@ -170,7 +204,9 @@ onMounted(makeHeaderSticky)
<slot name="dock-right" />
</div>
</div>
<div class="absolute right-10 top-10 rounded-lg px-4 bg-white/20 shadow-[0px_0px_2px_var(--color-verse-500)]">
<div
class="absolute right-10 top-10 rounded-lg px-4 bg-white/20 shadow-[0px_0px_2px_var(--color-verse-500)]"
>
<slot name="dock-right-bottom" />
</div>
</div>
Expand All @@ -188,13 +224,7 @@ onMounted(makeHeaderSticky)
}

.intersect {
@apply h-16;
}

.intersect .menu {
padding: 4px 7px;
border-radius: 24px;
box-shadow: 0px 0px 2px var(--color-verse-500);
backdrop-filter: brightness(0.8) blur(20px);
backdrop-filter: brightness(1) blur(20px);
}
</style>
Loading