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

如何在Next.js+Tailwind CSS中实现指定布局的环形悬浮按钮菜单?

解决方案:实现环形半圆菜单布局

要实现菜单项的环形半圆排列,通过CSS绝对定位+角度计算可精准定位每个菜单项,结合Tailwind和HeadlessUI的Transition组件实现流畅的展开/收起动画。以下是修改后的完整代码:

'use client'
import { Menu, Transition } from '@headlessui/react'
import {
  Bars3Icon,
  HomeIcon,
  PhotoIcon,
  PuzzlePieceIcon,
  TicketIcon,
  WrenchIcon,
} from '@heroicons/react/24/solid'
import { Fragment } from 'react'

export default function Home() {
  const menus = [
    { name: 'Home', icon: HomeIcon },
    { name: 'Gallery', icon: PhotoIcon },
    { name: 'Games', icon: PuzzlePieceIcon },
    { name: 'Ticket', icon: TicketIcon },
    { name: 'Settings', icon: WrenchIcon },
  ]
  // 计算半圆内菜单项的角度间隔(90度半圆分成4个间隔)
  const angleStep = 90 / (menus.length - 1)

  return (
    <div className="relative flex h-screen w-screen bg-cyan-500">
      {/* 左上角半圆背景 */}
      <div className="absolute left-4 top-4 h-52 w-52 rounded-br-full bg-amber-500"></div>

      {/* 左上角菜单 */}
      <Menu as="div" className="absolute left-4 top-4">
        <Menu.Button className="flex h-12 w-12 items-center justify-center rounded-full bg-black/20 hover:bg-amber-900/70 z-10">
          <Bars3Icon className="h-10 w-10 text-white" />
        </Menu.Button>
        <Transition
          as={Fragment}
          enter="transition-all duration-300 ease-out"
          enterFrom="opacity-0 scale-95"
          enterTo="opacity-100 scale-100"
          leave="transition-all duration-200 ease-in"
          leaveFrom="opacity-100 scale-100"
          leaveTo="opacity-0 scale-95"
        >
          <Menu.Items className="absolute top-0 left-0 h-52 w-52 overflow-hidden">
            {menus.map((menu, index) => {
              // 3点钟到6点钟的角度计算:0°到90°
              const angle = angleStep * index
              return (
                <Menu.Item key={index}>
                  {({ active }) => (
                    <button
                      className={`absolute flex h-12 w-12 items-center justify-center rounded-full border-2 ${active ? 'border-amber-900/70' : 'hover:border-amber-900/70'}`}
                      style={{
                        transformOrigin: 'top left',
                        top: '0',
                        left: '100%',
                        transform: `translate(-50%, -50%) rotate(${angle}deg) translate(80px) rotate(-${angle}deg)`,
                      }}
                    >
                      <menu.icon className="h-8 w-8 text-white" />
                    </button>
                  )}
                </Menu.Item>
              )
            })}
          </Menu.Items>
        </Transition>
      </Menu>

      {/* 右下角半圆背景 */}
      <div className="absolute bottom-4 right-4 h-52 w-52 rounded-tl-full bg-amber-500"></div>

      {/* 右下角菜单 */}
      <Menu as="div" className="absolute bottom-4 right-4">
        <Transition
          as={Fragment}
          enter="transition-all duration-300 ease-out"
          enterFrom="opacity-0 scale-95"
          enterTo="opacity-100 scale-100"
          leave="transition-all duration-200 ease-in"
          leaveFrom="opacity-100 scale-100"
          leaveTo="opacity-0 scale-95"
        >
          <Menu.Items className="absolute bottom-0 right-0 h-52 w-52 overflow-hidden">
            {menus.map((menu, index) => {
              // 9点钟到12点钟的角度计算:180°到270°
              const angle = 180 + angleStep * index
              return (
                <Menu.Item key={index}>
                  {({ active }) => (
                    <button
                      className={`absolute flex h-12 w-12 items-center justify-center rounded-full border-2 ${active ? 'border-amber-900/70' : 'hover:border-amber-900/70'}`}
                      style={{
                        transformOrigin: 'bottom right',
                        bottom: '0',
                        right: '100%',
                        transform: `translate(50%, 50%) rotate(${angle}deg) translate(80px) rotate(-${angle}deg)`,
                      }}
                    >
                      <menu.icon className="h-8 w-8 text-white" />
                    </button>
                  )}
                </Menu.Item>
              )
            })}
          </Menu.Items>
        </Transition>
        <Menu.Button className="flex h-12 w-12 items-center justify-center rounded-full bg-black/20 hover:bg-amber-900/70 z-10">
          <Bars3Icon className="h-10 w-10 text-white" />
        </Menu.Button>
      </Menu>
    </div>
  )
}

关键修改说明:

  • 角度分配:
    • 左上角菜单:将90度半圆(3点→6点)平均分配给所有菜单项,每个项的角度为90/(菜单数量-1)*index
    • 右下角菜单:从180度(9点)开始到270度(12点),角度为180 + 90/(菜单数量-1)*index
  • 定位逻辑:
    • Menu.Items设置与背景半圆一致的尺寸并隐藏溢出,确保菜单项不会超出半圆范围
    • 通过transform-origin设置旋转原点,结合translate+rotate组合实现环形排列,同时保持图标正向显示
  • 动画优化:给Transition组件添加明确的过渡类,让菜单展开/收起过程更顺滑

内容的提问来源于stack exchange,提问作者Mamas Panjul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 12:07:55