NextJS Tailwind网站offcanvas菜单悬停/点击事件不在父维度之外发生

92vpleto  于 2023-06-22  发布在  其他
关注(0)|答案(1)|浏览(83)

我有一个标题与画布外的菜单,从右边进来。它按预期工作,除了一个问题:当菜单可见并打开时,鼠标/触摸事件将直接通过它到达下面的内容。
下面是代码:

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"

我已经很多年没有手工编写画布外的菜单了,所以我肯定我错过了一些东西。

20jt8wwn

20jt8wwn1#

您可以尝试以下步骤:
1.关闭时,将CSS属性pointer-events: none;添加到画布外菜单容器。这可以防止鼠标/触摸事件被注册到菜单上,从而允许它们传递到底层内容。
1.打开画布外菜单时,设置pointer-events: auto;以启用与菜单的交互。
1.将事件侦听器附加到画布外菜单下方的内容区域。当用户与内容交互时,此侦听器将关闭菜单。您可以通过检测内容区域上的点击或触摸并触发菜单关闭操作来实现这一点。
通过实现这些更改,您应该能够防止鼠标/触摸事件通过画布外菜单并与其下方的内容交互。

相关问题