⭐ 推荐:社区规则条款 V1.0

button 比 div 按钮

满月居于空
满月居于空 版主超兽战士
发布于 2026-10-12 19:25 ·1 浏览 ·0 回复
内容摘要

把用于执行操作的 div 按钮替换为原生 button,跳转地址仍用 a,并显式设置 type 为 button 或 submit、重置默认样式,以获得键盘操作、读屏语义和表单联动等原生能力。

学完这篇,你能一眼认出代码里那些"长得像按钮的 div",把它安全地换成原生 <button>,并且知道哪几种情况下反而不能换。

第一步:先确认你要改的代码长什么样

这一步要做的是找到页面里所有"用 div 假装按钮"的地方,改完之后你手上会有一份明确的清单,而不是凭感觉零散地改。

典型的"div 按钮"长这样:

<div class="btn" onclick="submitForm()">提交</div>
<div class="btn primary" @click="handleSave">保存</div>

判断标准很简单:**它被点击后执行一个动作(保存、删除、展开、切换),而不是跳转到一个新地址。**

按动作类型分两类:

  • 跳转地址(打开详情页、下载文件、跳到外链)→ 应该用 <a href="...">,不是 <button>,也不是 <div>。
  • 执行操作(提交表单、打开弹窗、切换开关、删除一项)→ 应该用 <button>。

注意:<div onclick> 在真实项目里常常成片出现,因为它们都被同一个 .btn 样式类控制。改之前先把 .btn 样式在哪些文件里被引用搜一遍(VS Code 里 Ctrl+Shift+F 搜 .btn),否则改完 HTML 样式全散。

第二步:搞清楚 <button> 白送你多少东西

这一步不做任何代码修改,只是让你明白替换的价值,避免改到一半觉得"没区别"。

一个原生 <button> 自带这些能力,而 <div> 全都要你自己写 JS 补:

  1. 可聚焦:按 Tab 键能走到它,<div> 默认不在 Tab 序列里。
  2. 键盘触发:Enter 和空格键都能触发点击,<div> 一个都不支持。
  3. 无障碍语义:屏幕阅读器(NVDA、VoiceOver)会念出"按钮,提交",<div> 只会念出里面的文字,用户不知道这是可以按的。
  4. 表单联动:在 <form> 里,<button> 天然是提交按钮,不用写 JS。
  5. 状态表达:disabled 属性直接生效,读屏软件会念出"不可用"。
  6. 移动端行为:真实的按钮元素在触屏上有正确的点击反馈和焦点处理。

第三条是很多人低估的。你可以现在打开 Chrome,按 F12 打开 DevTools,切到 Elements 面板,选中一个 <div class="btn">,看右侧标签栏里点开 Accessibility 选项卡,Computed Properties 里的 Role 会显示 generic;换成 <button> 再看,Role 是 button,Name 是按钮里的文字。

第三步:动手把一个 div 按钮改成 button

这一步把清单上的第一个 div 按钮改掉,改完你会得到一个键盘可用、读屏可读的真按钮。

改法分三部分:改标签、补 type、重置默认样式。

1)改标签,把事件绑定保留

<!-- 改前 -->
<div class="btn" onclick="submitForm()">提交</div>

<!-- 改后 -->
<button type="button" class="btn" onclick="submitForm()">提交</button>

框架项目同理:

<!-- 改前 -->
<div class="btn primary" @click="handleSave">保存</div>

<!-- 改后 -->
<button type="button" class="btn primary" @click="handleSave">保存</button>

2)type 必须显式写

HTML 规范里 <button> 的默认 type 是 submit。也就是说,如果你在一个 <form> 里写了一个没写 type 的按钮,点它整个表单就提交了,还带着刷新页面的副作用。

规则记死:

  • 只是执行一个 JS 动作 → type="button"
  • 真的要提交表单 → type="submit"(并且不要给它绑 JS,让表单的 submit 事件统一处理)

3)重置浏览器默认样式

<button> 带着一套浏览器默认外观,直接加上你的 .btn 会发现颜色、圆角、字体都不对。在项目的基础样式里加一段全局重置:

button {
  font: inherit;          /* 关键:否则按钮内字体不继承父级,字体族和字号都会变 */
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  appearance: none;       /* 去掉 iOS Safari 的原生圆角和渐变 */
}

注意:font: inherit 这一行最容易被漏掉。漏了之后,即使你的 .btn 写了 font-size: 14px,按钮里如果放的是纯文字也可能显示成系统默认字号;而按钮里放 <span> 时字体又正常,于是出现"同一个按钮里文字大小不一致"的诡异现象。同一条规则也顺手解决 line-height 不继承的问题。

第四步:处理"按钮里有复杂内容"的情况

这一步解决你改造时会碰到的最常见的阻力:原本的 div 里塞了图标、多层 span、甚至用 flex 排版,换成 button 后布局变了。

推荐写法是外面用 <button>,里面包一层 <span> 承担 flex 布局:

<button type="button" class="btn">
  <span class="btn__inner">
    <svg class="icon" aria-hidden="true">...</svg>
    保存
  </span>
</button>
.btn__inner {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

之所以绕一层,是因为 <button> 在浏览器里是特殊的替换元素,历史上 Firefox 和 Safari 直接把 display: flex 写在 <button> 上有过布局异常,Chrome 在按钮内部作为 flex 容器时也出现过子元素点击不冒泡到按钮的问题。包一层 <span> 就完全绕开了这些历史包袱,多写一行换来长期稳定。

另外两条硬规则:

  • <button> 里不能再放 <button>、<a>、<input> 这类本身可聚焦的元素,嵌套会导致焦点混乱,屏幕阅读器读出来的结构也是错的。
  • 图标要标 aria-hidden="true";如果按钮里只有图标没有文字,按钮上加 aria-label="关闭",否则读屏只会念出"按钮",用户不知道是什么按钮。

第五步:自测,确认改对了

这一步在浏览器里做四项检查,全部通过就说明这个按钮改合格了。

  1. Tab 键:按 Tab 依次走一遍页面,每个按钮都能聚焦,并且焦点框肉眼可见。如果看不到,检查你的 CSS 里是不是写了 outline: none。要自定义焦点样式,用 :focus-visible,不要全局干掉 outline。
  1. 键盘触发:聚焦到按钮上,按 Enter 能触发,按空格也能触发。(如果你偷懒用了 <div role="button" tabindex="0">,就必须自己写 keydown 同时监听 Enter 和 Space,而且 Space 还要 e.preventDefault() 防止页面滚动——这些 <button> 全都免费。)
  1. 读屏确认:DevTools 的 Elements 面板 → Accessibility 选项卡,看 Role 是不是 button、Name 是不是你想要的名字。
  1. 自动扫描:DevTools 顶部标签栏找 Lighthouse 面板,只勾选 Accessibility,点 Analyze page load,跑完后看 "Buttons do not have an accessible name" 这类条目。或者在 Chrome 应用商店装 axe DevTools 扩展,点扩展图标 → Scan ALL of my page,它会直接列出缺名字、对比度不足的按钮。

第六步:认清不能换成 button 的三种情况

这一步帮你避免"一刀切"改错。

  1. 导航跳转:会改变 URL 的都用 <a href>。<a> 支持中键新标签页打开、右键"复制链接"、被搜索引擎抓取,<button> 都不支持。
  2. 整块卡片可点击且内部还有链接:比如商品卡片整体可点,右上角还有个"收藏"。这种不要用 button 包 a,正确做法是卡片里放一个绝对定位铺满的 <a>("拉伸链接"模式),收藏按钮用 z-index 排在它上面。
  3. 纯装饰或语义是别的角色:比如折叠面板的标题本身是标题,交互应该由标题内的一个 <button aria-expanded="true" aria-controls="panel-1"> 承担,而不是把 <h2> 整个变成按钮。

还有一个小坑:真的需要"看起来能点但点不了"的状态时,优先用 disabled 属性;但如果你的场景要求"禁用状态仍能被 Tab 聚焦并提示原因"(比如表单校验未通过),改用 aria-disabled="true" 加 JS 拦截,因为 disabled 的按钮会被直接踢出 Tab 序列,键盘用户会完全找不到它。

小结

  • 执行动作的用 <button>,跳转地址的用 <a href>,<div onclick> 只在无法改造的历史代码里留。
  • <button> 白送键盘聚焦、Enter/空格触发、读屏语义、表单提交、disabled 状态,<div role="button"> 全部要手写而且容易漏。
  • 永远显式写 type,表单里写 type="submit",其余一律 type="button",否则会莫名其妙触发表单提交。
  • 基础样式重置里必须有 font: inherit,否则按钮内文字字体不继承。
  • 按钮内需要 flex 布局时,外面用 <button>,里面包一层 <span> 做 flex 容器,绕开浏览器历史兼容问题。
  • 按钮里不要嵌套 <button> / <a> / <input>;纯图标按钮要加 aria-label。
  • 改完必须用 Tab 键、Enter/空格、DevTools 的 Accessibility 面板和 Lighthouse 四项检查验收。
版权声明:本文来自 GJ站长论坛《button 比 div 按钮》
原文链接:https://www.gj0.com/thread-1796.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~