我有一個頁眉,它帶有一個來自右邊的非畫布菜單。它像預期的那樣工作,除了一個問題:當菜單可見并打開時,鼠標/觸摸事件會通過它到達下面的內容。
代碼如下:
import { forwardRef, useCallback, useEffect, useRef, useState } from "react"
import Link from "next/link"
type ScrollDir = 'UP' | 'DOWN'
const LandingHeader = forwardRef((_, ref: React.Ref<HTMLDivElement>) => {
const [activeLink, setActiveLink] = useState('')
const sectionsRef = useRef([])
const { clientHeight } = (ref as React.MutableRefObject<HTMLDivElement>)?.current ?? { clientHeight: 0 }
const [navShowing, setNavShowing] = useState<boolean>(true)
const lastScrollPos = useRef<number>(0)
const [uBound, setUBound] = useState<number>(0)
const [lBound, setLBound] = useState<number>(0)
const [mobileMenuOpen, setMobileMenuOpen] = useState<boolean>(false)
const handleMobileMenu = (shouldOpen?: boolean) => {
setMobileMenuOpen(prev => shouldOpen ?? !prev)
}
const trackScrollPos = useCallback(() => {
const { pageYOffset, scrollY } = window
const { clientHeight: headerHeight } = (ref as React.MutableRefObject<HTMLDivElement>)?.current ?? { clientHeight: 0 }
// Set active classes for nav links
sectionsRef.current.forEach(section => {
const sectionId = (section as HTMLElement).getAttribute('id')
const sectionOffsetTop = (section as HTMLElement).offsetTop - clientHeight
const sectionHeight = (section as HTMLElement).offsetHeight
if (
scrollY >= sectionOffsetTop &&
scrollY < sectionOffsetTop + sectionHeight
) {
setActiveLink(`/#${sectionId || ''}`);
}
});
// Base case (if pageYOffset is near top, show nav)
if ((pageYOffset <= headerHeight ?? 0) && !!!navShowing) {
//setNavShowing(true)
setUBound(pageYOffset)
setLBound(pageYOffset)
}
// Set Direction of scroll
const newDir: ScrollDir = pageYOffset > lastScrollPos.current ? 'DOWN' : 'UP'
// Check direction states
switch(newDir) {
case "DOWN":
setLBound(pageYOffset)
if (!mobileMenuOpen && pageYOffset >= (uBound + headerHeight) && !!navShowing) {
//setNavShowing(false)
}
break;
case "UP":
setUBound(pageYOffset)
if (pageYOffset <= (lBound - headerHeight) && !!!navShowing) {
//setNavShowing(true)
}
break;
}
// Always set lastScrollPos to current pageYOffset -- TICK
lastScrollPos.current = pageYOffset
}, [lBound, uBound, navShowing, mobileMenuOpen])
const headerLinks: { name: string, href: string }[] = [
{
name: "Mission",
href: "/#mission"
},
{
name: "Support",
href: "/#support"
},
{
name: "Causes",
href: "/#causes"
},
{
name: "Platform",
href: "/#how-it-works"
}
]
useEffect(() => {
document.documentElement.style.setProperty('scroll-padding-top', `${clientHeight - 1}px`)
}, [clientHeight])
useEffect(() => {
// Cache all sections
sectionsRef.current = Array.from(document.querySelectorAll('div[id]'))
// Header scroll tracker for hide/show
window.addEventListener('scroll', trackScrollPos)
// Cleanup function
return () => window.removeEventListener('scroll', trackScrollPos)
}, [trackScrollPos, activeLink])
return (
<div ref={ref} style={{transform: `translateY(${navShowing ? "0" : "-100%"})` }} className="z-50 py-6 px-[calc(min(10vw,1rem))] bg-[rgba(255,255,255,.85)] backdrop-blur-md sticky top-0 border-b-2 shadow-sm translate-all duration-300 overflow-x-clip">
<nav>
<div className="flex flex-1 justify-between items-center max-w-7xl m-auto gap-6">
<div className="max-w-[200px] flex-shrink-0 mr-5">
<Link href="/"><img className="w-full h-auto hidden md:block object-contain" src="/images/logos/solution_logo_black.svg" alt="logo" /></Link>
<Link href="/"><img className="w-[30px] h-auto block md:hidden object-contain" src="/images/logos/solution-circle.svg" alt="logo" /></Link>
</div>
{/* Full-size menu */}
<div className="hidden md:flex flex-1 ml-auto justify-around items-center max-w-2xl text-black font-normal whitespace-nowrap">
{
headerLinks.map((link) => (
<Link className={`${link.href == activeLink ? "!bg-neutralBlue" : ""} group relative font-bold hover:bg-neutralBlue transition-all duration-500 ease-in-out rounded-full px-3 py-3`} passHref href={link.href} key={link.name}>
{link.name}
<span className="absolute bottom-3 left-[14px] right-0 transform w-0 h-[1px] bg-black transition-all duration-500 ease-in-out group-hover:w-8/12"></span>
</Link>))
}
<Link href="/#get-involved">
<button className={`${activeLink == "/#get-involved" ? "bg-transparent !text-black" : ""} btn btn-outline rounded-3xl text-white active:text-black hover:text-black bg-black active:bg-transparent hover:bg-transparent font-normal`}>Get Involved</button>
</Link>
</div>
{/* Mobile menu */}
<div className="md:hidden flex justify-end gap-5 items-center flex-grow">
<Link href="#get-involved">
<button className={`px-[calc(max(1rem,2rem))] whitespace-nowrap max-w-[40vw] text-[calc(max(12px,2vmin))] ${activeLink == "/#get-involved" ? "bg-transparent !text-black" : ""} btn btn-outline rounded-3xl text-white active:text-black hover:text-black bg-black active:bg-transparent hover:bg-transparent font-normal white`}>Get Involved</button>
</Link>
<button className="flex-shrink-0" onClick={() => handleMobileMenu()}>
<img src="/images/icons/hamburger.svg" alt="hamburger menu" />
</button>
</div>
</div>
</nav>
<div className={`absolute ${ mobileMenuOpen ? "translate-x-0" : "translate-x-full" } md:hidden top-0 bottom-0 left-0 right-0 flex flex-col justify-stretch items-stretch bg-[rgba(255,255,255,0.75)] backdrop-blur-xl w-full h-screen transition-all duration-300 ease-in-out z-[9999]`}>
<div className="flex flex-col items-stretch h-screen max-w-md ml-auto bg-white px-5 py-6 z-[9999]">
<div className="flex justify-between items-center mb-10 w-full">
<img className="object-contain w-full h-auto max-w-[50%]" src="/images/logos/solution_logo_black.svg" alt="solution logo" />
<button className="btn btn-outline btn-sm btn-circle text-sm font-bold aspect-square" onClick={() => handleMobileMenu(false)}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
<div className="flex flex-col gap-10 text-3xl font-bold [&_a:hover]:opacity-50">
{
headerLinks.map(link => <Link className="w-full h-full" onClick={() => handleMobileMenu(false)} href={link.href} key={link.name}>{link.name}</Link>)
}
</div>
<div className="">Footer bottom</div>
</div>
</div>
</div>
)
})
export default LandingHeader
LandingHeader.displayName = "LandingHeader"
我已經多年沒有手工編寫過畫布外的菜單了,所以我確信我遺漏了一些東西。
# # #您可以嘗試以下步驟:
添加CSS屬性指針-事件:無;關閉時添加到非畫布菜單容器。這可以防止鼠標/觸摸事件在菜單上注冊,從而允許它們傳遞到底層內容。
當非畫布菜單打開時,設置指針事件:自動;以實現與菜單的交互。
將事件監聽器附加到畫布外菜單下面的內容區域。當用戶與內容交互時,這個偵聽器將關閉菜單。您可以通過檢測內容區域上的點擊或觸摸并觸發菜單關閉操作來實現這一點。
通過實現這些更改,您應該能夠防止鼠標/觸摸事件通過畫布外菜單并與其下的內容交互。