纯css可实现扇形展开按钮,但js必不可少,用于状态管理、焦点控制和可访问性(aria-expanded、inert);核心是transform+transform-origin精确定位,配合rotate()与translatex()模拟弧线布局,并手动抵消文字旋转。

纯 CSS 实现扇形展开按钮可行,但必须配合 transform + transform-origin 精确控制每个子项位置;JavaScript 不可省略——它负责状态管理、焦点控制和可访问性(aria-expanded、inert),否则键盘用户和屏幕阅读器会完全失控。
用 transform: rotate() + translateX() 定位菜单项
扇形不是靠“画弧”或堆 border-radius 拼出来的,而是把每个 <li> 当作一个独立元素,先绕圆心旋转角度,再沿 X 轴平移半径距离,最后反向旋转把文字扶正。常见错误是漏掉 transform-origin: 0 0,导致所有项绕自身中心转,而不是绕菜单中心点转。
- 设共
n个菜单项,则第i项(从 0 开始)的旋转角度为i * (360deg / n) -
translateX(120px)中的120px是圆半径,需与容器宽高匹配(例如父容器width: 240px; height: 240px;) - 文字歪斜必须手动抵消:在
translateX()后加rotate(-i * (360deg / n)) - 初始状态应为
transform: rotate(Xdeg) translateX(0),而非直接opacity: 0—— 否则动画起点错位
过渡动画不能写 transition: all
transition: all 会触发 layout(重排),尤其在移动端造成卡顿;更糟的是,它会让 transform 和 opacity 共享同一缓动函数,而二者物理意义不同——位移需要缓出,透明度适合线性或稍缓入。
- 正确写法:
transition: opacity 0.3s, transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) - 收起时若直接移除类名,浏览器可能跳过动画,应先强制 reflow(如读取
offsetHeight),再移类 - 逐个延迟飞出效果别硬编码
transition-delay,响应式下会失效;改用 JS 动态设置style.transitionDelay = `${i * 0.08}s`
JS 必须处理 aria-expanded 和 inert
纯 CSS 无法感知“当前是否展开”,而无障碍要求必须同步暴露状态给辅助技术。仅靠 display: none 或 visibility: hidden 不够——键盘 Tab 仍会聚焦到隐藏项,aria-hidden="true" 又不解决焦点管理问题。
- 点击按钮时,除了切换类名,必须同步设置:
button.setAttribute('aria-expanded', 'true')、menu.setAttribute('inert', '')、menu.setAttribute('aria-hidden', 'false') - 展开后,立即
menuItems[0].focus();按Esc键时,收起菜单并把焦点还给触发按钮 - 移动端要加
touch-action: manipulation防止 300ms 点击延迟
响应式降级不能只靠媒体查询
小屏下强行维持扇形布局会导致菜单项重叠或溢出视口,这不是调 font-size 或缩 translateX() 就能解决的。
- 建议在
max-width: 768px下直接切为垂直列表,用flex-direction: column+gap替代扇形逻辑 - 圆半径值推荐用
clamp(80px, 15vw, 120px),避免在超小屏上撑破容器 - 扇形角度计算要用 JS 动态重算(比如从 8 项降为 4 项),CSS 变量无法实时响应 DOM 变化
最易被忽略的点:扇形菜单不是“视觉动效炫技”,而是导航控件——它的焦点流、键盘操作路径、屏幕阅读器播报顺序,必须和线性菜单一致。动效只是锦上添花,状态同步和语义正确才是底线。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











