无障碍与动效
无障碍与动效
这一章讲主题在键盘操作和动画上的行为。这些默认就已启用,不需要配置,但如果你要改样式或写新组件,得知道它们存在,免得不小心破坏掉。
键盘可达性
焦点环
用键盘 Tab 浏览时,当前聚焦的元素会有一圈强调色描边。规则定义在 src/styles/utilities/a11y.css:
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible { outline: 2px solid var(--accent-color); outline-offset: 2px; border-radius: var(--radius-xs);}用 :where() 包住是为了把优先级压到 0,你的组件想覆盖它随时可以,不需要 !important。
用的是 :focus-visible 而不是 :focus,所以鼠标点击不会出现描边,只有键盘操作才显示。
如果你为了去掉浏览器默认描边写了 outline: none,键盘用户就看不到焦点在哪了。正确做法是去掉默认的同时补回 :focus-visible 的:
.my-input { outline: none; /* 去掉鼠标点击时的描边 */} .my-input:focus-visible { outline: 2px solid var(--accent-color); outline-offset: 2px;}跳过导航
每个页面的第一个可聚焦元素是一个「跳到主要内容」链接,平时藏在视口外,按 Tab 才滑出来。点它直接跳到正文,不用一路 Tab 穿过整个侧边栏。
正文容器都带 id="main-content"。如果你新建了布局,记得给主内容区加上这个 id,并在 <body> 开头放上跳转链接:
<body> <a class="skip-link" href="#main-content">{t.a11y.skipToContent}</a> <!-- ... --> <main id="main-content"> <slot /> </main></body>标题锚点
文章里的 h2 到 h4 标题末尾都有一个锚点链接,平时透明,鼠标悬停或键盘聚焦时才出现,点了可以复制那一节的地址。
这是 src/plugins/rehype-heading-anchors.mjs 自动加的,不需要在 Markdown 里做任何事。
动效
减少动态效果
系统里开启了「减少动态效果」的用户(macOS 的辅助功能设置、Windows 的动画设置),会自动关掉装饰性动画:滚动显现、页面转场、悬停缩放、循环动画、平滑滚动,全部停掉。
实现在 src/styles/animations.css 末尾的 @media (prefers-reduced-motion: reduce) 块里。它同时保证内容仍然可见——滚动显现的元素直接落到终态,不会因为动画被关掉而一片空白。
写新动画时不用单独处理,那个块用通配选择器覆盖了所有元素。
动效尺度
主题的动效遵循一个原则:运动量和元素大小成反比。小图标可以放大 10%,一张几百像素宽的卡片放大 4% 就已经很明显了。
现成的时长 token 在 src/styles/base/tokens.css:
| Token | 值 | 用在哪 |
|---|---|---|
--duration-fast | 150ms | 悬停变色、小图标 |
--duration-base | 200ms | 卡片抬升、展开收起 |
--duration-slow | 300ms | 页面级转场、弹窗 |
参考幅度:小图标 scale(1.06),卡片 translateY(-4px),图片 scale(1.03)。
按压反馈
按钮按下时会缩到 97%,链接则不缩放——整块卡片链接或正文里的文字链接如果跟着缩,视觉上会很怪,文字还会发虚。
可点击的卡片有完整的「抬起—按下」反馈:悬停抬升 4px,按下回落原位。给卡片加 card-interactive 类即可:
<div class="card-interactive hover:-translate-y-1 hover:shadow-md"> <!-- ... --></div>如果同一个元素上有多个类都写了 transition,后面的那条会把前面的整个重置掉,而不是叠加。
主题里的卡片常常同时带 reveal(滚动显现)和 card-interactive(悬停抬升),两者要过渡的属性不同。animations.css 里为此单独写了一条 .reveal.card-interactive 把四个属性合并声明。
你给元素加多个带 transition 的类时,注意同样的问题。
浮层层级
右下角的浮动元素(回到顶部、音频播放器、移动端目录按钮)用共享槽位排列,不会互相遮挡。槽位定义在 src/styles/utilities/floating-stack.css:
.floating-slot-1 { bottom: var(--floating-slot-1); } /* 最靠近角落 */.floating-slot-2 { bottom: var(--floating-slot-2); }.floating-slot-3 { bottom: var(--floating-slot-3); }新增浮动元素时挑一个没被占用的槽位,不要另写 bottom 值。
跨组件的层级同样有 token,从 --z-raised(10)到 --z-lightbox(300),定义在 tokens.css。不要再写 z-index: 9999。