You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React TypeScript+TailwindCSS中居中全屏下拉菜单

问题

使用React TypeScript + TailwindCSS开发个人作品集网站的下拉菜单时遇到样式问题:

  • 采用hover触发下拉,悬停展开行为正常,但添加多个导航链接后,下拉菜单以上方文本为中心且设置w-screen导致溢出屏幕宽度
  • 需求:下拉菜单既能居中显示,又能全屏宽度无溢出

当前代码片段:

import React, { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { DownOutlined } from "@ant-design/icons";
import { Link } from "react-router-dom";
import profilePicture from "../../assets/Profile-picture-without-me.jpg";
import MenuIcon from "./MenuIcon.tsx";
import Menu from "./Menu.tsx";

interface NavbarProps {
    children: React.ReactNode;
    FlyoutContent?: React.FC;
};

const Example = () => {
    const [isOpen, setIsOpen] = useState(false);

    const toggleMenu = () => {
        setIsOpen(!isOpen);
    };

    return (
        <div className="navigation-container">
            <nav className="navigation-inner-container">
                <div className="flex justify-between items-center p-3 mx-auto w-[90%] max-w-[1340px] lg:w-[95%]">

                    {/* Logo Section */}
                    <div className="navigation-logo flex justify-start items-center h-10 w-10">
                        <img className="rounded-sm" src={profilePicture} alt="Profile picture"></img>
                    </div>

                    <div className="navbar-links-container hidden lg:block">
                        <div className="navigation-links-wrapper flex gap-x-8 w-auto h-auto">

                            {/* Projects 1 */}
                            <FlyoutLink FlyoutContent={ProjectTypes}>
                                <Link to="/projects" className="font-semibold text-xl">Projects</Link>
                                <DownOutlined className="py-auto" />
                            </FlyoutLink>

                            {/* Projects 2 */}
                            <FlyoutLink FlyoutContent={ProjectTypes}>
                                <Link to="/projects" className="font-semibold text-xl">Projects</Link>
                                <DownOutlined className="py-auto" />
                            </FlyoutLink>
                        </div>
                    </div>

                    {/* Menu Button for Mobile */}
                    <div className="menu-button lg:hidden flex justify-end items-center">
                        <button onClick={toggleMenu}>
                            <MenuIcon />
                        </button>
                    </div>
                </div>
            </nav>

            {/* Mobile Menu Display */}
            {
                isOpen && (
                    <div className="mobile-menu flex flex-col items-start bg-white shadow-md p-4 absolute top-16 left-0 w-full z-50 lg:hidden">
                        <Menu />
                    </div>
                )
            }
        </div >
    );
};

const FlyoutLink: React.FC<NavbarProps> = ({ children, FlyoutContent }) => {
    const [open, setOpen] = useState(false);

    const showFlyout = FlyoutContent && open;

    return (
        <div
            onMouseEnter={() => setOpen(true)}
            onMouseLeave={() => setOpen(false)}
            className="relative w-fit h-fit"
        >
            <div className="relative">
                {children}
                <span
                    style={{
                        transform: showFlyout ? "scaleX(1)" : "scaleX(0)",
                    }}
                    className="absolute -bottom-2 -left-2 -right-2 h-1 origin-left scale-x-0 rounded-full bg-indigo-300 transition-transform duration-300 ease-out"
                />
            </div>
            <AnimatePresence>
                {showFlyout && (
                    <motion.div
                        initial={{ opacity: 0, y: 15 }}
                        animate={{ opacity: 1, y: 0 }}
                        exit={{ opacity: 0, y: 15 }}
                        transition={{ duration: 0.3, ease: "easeOut" }}
                        className="absolute left-1/2 top-12 w-[calc(100vw-17px)] !-translate-x-1/2 bg-white z-10"
                    >
                        <div className="absolute -top-6 left-0 right-0 h-6 bg-transparent" />
                        <div className="absolute left-1/2 top-0 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rotate-45" />
                        <FlyoutContent />
                    </motion.div>
                )}
            </AnimatePresence>
        </div>
    );
};

const ProjectTypes = () => {
    return (
        <div className="menu-projects-container">
            <div className="menu-projects-wrapper flex justify-evenly shadow-2xl pb-8">
                <div className="subproject-container flex gap-x-2 border-b-2 border-b-black py-4">
                    <h3 className="text-black/50">/01</h3>
                    <h1 className="text-2xl font-medium">GitHub Projects</h1>
                    <div className="arrow-container flex justify-center items-center w-8 h-8">
                        <img src="https://cdn.prod.website-files.com/66bf407255e2bbac50fde356/66bf9e7b27401f68266d92b9_Arrow%20-%20Right.svg" alt="Arrow"></img>
                    </div>
                </div>
                <div className="subproject-container flex gap-x-2 border-b-2 border-b-black py-4">
                    <h3 className="text-black/50">/02</h3>
                    <h1 className="text-2xl font-medium">School Projects</h1>
                    <div className="arrow-container flex justify-center items-center w-8 h-8">
                        <img src="https://cdn.prod.website-files.com/66bf407255e2bbac50fde356/66bf9e7b27401f68266d92b9_Arrow%20-%20Right.svg" alt="Arrow"></img>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default Example;

问题核心:单个导航元素时下拉菜单能正确居中,多个元素时菜单以当前触发文本为基准居中,导致溢出屏幕。


解决方案

问题根源在于下拉菜单的定位父级是单个FlyoutLink组件(relative定位),导致left-1/2 + -translate-x-1/2是相对于父级元素而非屏幕居中。要实现全屏居中,需要:

  1. 将下拉菜单的定位上下文提升到导航栏的最外层容器(而非单个链接),让菜单相对于屏幕/视口居中
  2. 调整下拉菜单的定位样式,确保宽度铺满视口且无溢出
  3. 保留每个链接的hover触发逻辑,同时让菜单全局居中

修改后的关键代码

步骤1:重构导航栏结构,添加全局定位容器

const Example = () => {
    const [isOpen, setIsOpen] = useState(false);
    // 跟踪当前激活的下拉内容
    const [activeFlyout, setActiveFlyout] = useState<React.FC | null>(null);

    const toggleMenu = () => {
        setIsOpen(!isOpen);
    };

    return (
        <div className="navigation-container relative"> {/* 设置为relative,作为下拉菜单的定位父级 */}
            <nav className="navigation-inner-container">
                {/* 原有导航内容不变 */}
                <div className="flex justify-between items-center p-3 mx-auto w-[90%] max-w-[1340px] lg:w-[95%]">
                    {/* Logo Section */}
                    <div className="navigation-logo flex justify-start items-center h-10 w-10">
                        <img className="rounded-sm" src={profilePicture} alt="Profile picture"></img>
                    </div>

                    <div className="navbar-links-container hidden lg:block">
                        <div className="navigation-links-wrapper flex gap-x-8 w-auto h-auto">
                            {/* 修改FlyoutLink的props,传递触发/关闭菜单的方法 */}
                            <FlyoutLink 
                                onMouseEnter={() => setActiveFlyout(ProjectTypes)}
                                onMouseLeave={() => setActiveFlyout(null)}
                            >
                                <Link to="/projects" className="font-semibold text-xl">Projects</Link>
                                <DownOutlined className="py-auto" />
                            </FlyoutLink>

                            <FlyoutLink 
                                onMouseEnter={() => setActiveFlyout(ProjectTypes)}
                                onMouseLeave={() => setActiveFlyout(null)}
                            >
                                <Link to="/projects" className="font-semibold text-xl">Projects</Link>
                                <DownOutlined className="py-auto" />
                            </FlyoutLink>
                        </div>
                    </div>

                    {/* Menu Button for Mobile */}
                    <div className="menu-button lg:hidden flex justify-end items-center">
                        <button onClick={toggleMenu}>
                            <MenuIcon />
                        </button>
                    </div>
                </div>
            </nav>

            {/* 全局下拉菜单容器 - 放在导航栏外层 */}
            <AnimatePresence>
                {activeFlyout && (
                    <motion.div
                        initial={{ opacity: 0, y: 15 }}
                        animate={{ opacity: 1, y: 0 }}
                        exit={{ opacity: 0, y: 15 }}
                        transition={{ duration: 0.3, ease: "easeOut" }}
                        className="absolute left-0 top-[60px] w-screen bg-white z-10"
                    >
                        <div className="absolute -top-6 left-0 right-0 h-6 bg-transparent" />
                        <activeFlyout />
                    </motion.div>
                )}
            </AnimatePresence>

            {/* Mobile Menu Display */}
            {
                isOpen && (
                    <div className="mobile-menu flex flex-col items-start bg-white shadow-md p-4 absolute top-16 left-0 w-full z-50 lg:hidden">
                        <Menu />
                    </div>
                )
            }
        </div >
    );
};

步骤2:简化FlyoutLink组件

移除原FlyoutLink内的下拉菜单,只保留hover状态和下划线效果:

interface FlyoutLinkProps {
    children: React.ReactNode;
    onMouseEnter: () => void;
    onMouseLeave: () => void;
};

const FlyoutLink: React.FC<FlyoutLinkProps> = ({ children, onMouseEnter, onMouseLeave }) => {
    const [open, setOpen] = useState(false);

    return (
        <div
            onMouseEnter={() => {
                setOpen(true);
                onMouseEnter();
            }}
            onMouseLeave={() => {
                setOpen(false);
                onMouseLeave();
            }}
            className="relative w-fit h-fit"
        >
            <div className="relative">
                {children}
                <span
                    style={{
                        transform: open ? "scaleX(1)" : "scaleX(0)",
                    }}
                    className="absolute -bottom-2 -left-2 -right-2 h-1 origin-left scale-x-0 rounded-full bg-indigo-300 transition-transform duration-300 ease-out"
                />
            </div>
        </div>
    );
};

步骤3:调整下拉菜单的内容约束

确保菜单内容和导航栏视觉对齐,避免溢出:

const ProjectTypes = () => {
    return (
        <div className="menu-projects-container w-full">
            <div className="menu-projects-wrapper flex justify-evenly shadow-2xl pb-8 mx-auto max-w-[1340px]"> {/* 对齐导航栏的最大宽度 */}
                <div className="subproject-container flex gap-x-2 border-b-2 border-b-black py-4">
                    <h3 className="text-black/50">/01</h3>
                    <h1 className="text-2xl font-medium">GitHub Projects</h1>
                    <div className="arrow-container flex justify-center items-center w-8 h-8">
                        <img src="https://cdn.prod.website-files.com/66bf407255e2bbac50fde356/66bf9e7b27401f68266d92b9_Arrow%20-%20Right.svg" alt="Arrow"></img>
                    </div>
                </div>
                <div className="subproject-container flex gap-x-2 border-b-2 border-b-black py-4">
                    <h3 className="text-black/50">/02</h3>
                    <h1 className="text-2xl font-medium">School Projects</h1>
                    <div className="arrow-container flex justify-center items-center w-8 h-8">
                        <img src="https://cdn.prod.website-files.com/66bf407255e2bbac50fde356/66bf9e7b27401f68266d92b9_Arrow%20-%20Right.svg" alt="Arrow"></img>
                    </div>
                </div>
            </div>
        </div>
    );
};

关键说明

  • 下拉菜单现在相对于整个导航容器定位,left-0 w-screen确保铺满视口无溢出
  • 菜单内容通过max-w-[1340px] mx-auto和导航栏保持一致的最大宽度,视觉上对齐
  • 每个FlyoutLink只负责触发全局菜单的显示/隐藏,避免了单个链接作为定位父级的问题

内容的提问来源于stack exchange,提问作者Patrik Bajzík

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:12:02