按钮操场:13 种常用网页按钮交互图鉴
AI文章总结
文章定义了网页按钮的统一术语,指出其是用户触发动作的核心入口,但命名混乱易致设计开发协作问题。作者将常见按钮按视觉权重与交互行为分为13类,涵盖主/次/文字、危险、幽灵、图标、FAB及开关、分段、下拉、加载、禁用等状态,并提供可交互的“按钮操场”演示。文中强调主按钮一屏仅一个,危险按钮需红色警示,同时附完整源码供复用,旨在建立清晰的设计规范。
什么是按钮?为什么需要统一的术语
在网页与 App 界面里,按钮(Button)是用户触发动作最直接的入口:提交表单、确认删除、切换状态、打开菜单……几乎所有交互都绕不开它。但同一个「按钮」在不同设计系统里叫法不一——主按钮、Primary、CTA、行动点……概念混乱会让设计与开发反复扯皮。
这篇文章把常见的网页按钮按视觉权重与交互行为拆成 13 类,并做成一个可直接点击把玩的「按钮操场」。你可以把下方演示当成一份活的设计规范:看样式、点交互、读源码。
13 种常见按钮一览
下面这张卡片汇集了 13 种常用按钮类型:从主 / 次 / 文字按钮,到幽灵、图标、FAB、开关、分段、下拉、加载、禁用等状态与特殊样式。点击任意按钮,右下角的操作反馈区会实时显示你触发了什么。
按钮操场交互演示
<div class="container">
<h2 class="section-title">① 主要按钮 <span>Primary</span></h2>
<p class="section-desc">页面中最突出的行动号召,用于最重要的操作(如提交、确认、购买)。</p>
<div class="btn-grid">
<div class="btn-item">
<button class="btn btn-primary" data-action="primary">主按钮</button>
<span class="label">Primary</span>
</div>
<div class="btn-item">
<button class="btn btn-secondary" data-action="secondary">次按钮</button>
<span class="label">Secondary</span>
</div>
<div class="btn-item">
<button class="btn btn-tertiary" data-action="tertiary">第三按钮</button>
<span class="label">Tertiary</span>
</div>
<div class="btn-item">
<button class="btn btn-danger" data-action="danger">危险按钮</button>
<span class="label">Danger</span>
</div>
</div>
<!-- ===== 2. 特殊样式 ===== -->
<h2 class="section-title">② 特殊样式 <span>Style</span></h2>
<p class="section-desc">适用于不同视觉场景的按钮变体。</p>
<div class="btn-grid">
<div class="btn-item">
<button class="btn btn-ghost" data-action="ghost">幽灵按钮</button>
<span class="label">Ghost</span>
</div>
<div class="btn-item">
<button class="btn btn-icon" data-action="icon" aria-label="搜索">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</button>
<span class="label">Icon <span class="note">(搜索)</span></span>
</div>
<div class="btn-item">
<button class="btn btn-icon" data-action="icon-heart" aria-label="收藏">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
</button>
<span class="label">Icon <span class="note">(收藏)</span></span>
</div>
<div class="btn-item">
<button class="btn btn-fab" data-action="fab" aria-label="添加">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<span class="label">FAB <span class="note">(浮动)</span></span>
</div>
</div>
<!-- ===== 3. 状态按钮 ===== -->
<h2 class="section-title">③ 状态与交互 <span>State</span></h2>
<p class="section-desc">带有状态切换、加载、下拉菜单等交互行为的按钮。</p>
<div class="btn-grid" style="flex-wrap: wrap; gap: 30px 48px;">
<!-- 开关 -->
<div class="btn-item">
<button class="btn-toggle" data-action="toggle" aria-label="开关">
<span class="toggle-knob"></span>
</button>
<span class="label">Toggle <span class="note">(开关)</span></span>
</div>
<!-- 分段 -->
<div class="btn-item">
<div class="btn-segmented" data-action="segmented">
<button class="seg-item active" data-value="list">列表</button>
<button class="seg-item" data-value="grid">网格</button>
<button class="seg-item" data-value="card">卡片</button>
</div>
<span class="label">Segmented <span class="note">(分段)</span></span>
</div>
<!-- 下拉 -->
<div class="btn-item">
<div class="btn-dropdown">
<button class="btn btn-secondary" data-action="dropdown">
操作 <span class="arrow">▼</span>
</button>
<ul class="dropdown-menu">
<li data-value="edit"><span class="label-icon">✎</span> 编辑</li>
<li data-value="duplicate"><span class="label-icon">⊞</span> 复制</li>
<li class="divider"></li>
<li data-value="delete" style="color:#dc2626;"><span class="label-icon">✕</span> 删除</li>
</ul>
</div>
<span class="label">Dropdown <span class="note">(下拉)</span></span>
</div>
<!-- 加载 -->
<div class="btn-item">
<button class="btn btn-primary btn-loading" data-action="loading">
<span class="loader"></span>
<span class="btn-text">提交</span>
</button>
<span class="label">Loading <span class="note">(加载)</span></span>
</div>
<!-- 禁用 -->
<div class="btn-item">
<button class="btn btn-secondary btn-disabled" data-action="disabled" disabled>
禁用状态
</button>
<span class="label">Disabled <span class="note">(禁用)</span></span>
</div>
</div>
<!-- ===== 4. 文字按钮 ===== -->
<h2 class="section-title">④ 文字按钮 <span>Text</span></h2>
<p class="section-desc">最轻量的按钮形式,常用于辅助操作或链接样式。</p>
<div class="btn-grid" style="gap: 12px 28px;">
<div class="btn-item" style="flex-direction:row; gap:12px;">
<button class="btn btn-tertiary" data-action="text-cancel" style="font-size:0.9rem;">取消</button>
<button class="btn btn-tertiary" data-action="text-confirm" style="font-size:0.9rem; color:#2563eb; font-weight:600;">确认</button>
<button class="btn btn-tertiary" data-action="text-learn" style="font-size:0.9rem; color:#2563eb;">
了解更多 →
</button>
<span class="label" style="margin-left:4px;">Text Button</span>
</div>
</div>
<!-- ===== 交互反馈区 ===== -->
<div class="feedback-area">
<span class="fb-label">操作反馈</span>
<div class="fb-log" id="feedbackLog">
点击任意按钮,此处显示交互信息…
</div>
<button class="btn btn-secondary" id="clearLog" style="padding:6px 16px; min-height:36px; font-size:0.8rem; flex-shrink:0;">
清空
</button>
</div>
<!-- ===== 场景示例 ===== -->
<div class="scene-example">
<h3>场景组合示例 — 对话框 / 表单底部</h3>
<div class="scene-buttons">
<span class="scene-label">操作:</span>
<button class="btn btn-secondary" data-action="scene-cancel" style="min-width:80px;">取消</button>
<button class="btn btn-danger" data-action="scene-delete" style="min-width:80px;">删除</button>
<button class="btn btn-primary" data-action="scene-confirm" style="min-width:100px;">确认提交</button>
</div>
<div style="margin-top:16px; display:flex; flex-wrap:wrap; gap:12px; align-items:center;">
<span class="scene-label">工具栏:</span>
<button class="btn btn-icon" data-action="toolbar-edit" aria-label="编辑">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
</button>
<button class="btn btn-icon" data-action="toolbar-download" aria-label="下载">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</button>
<button class="btn btn-secondary" data-action="toolbar-save" style="padding:6px 18px; min-height:36px; font-size:0.85rem;">保存</button>
</div>
</div>
</div>/* ===== 全局重置 & 基础 ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f8fafc;
color: #1e293b;
padding: 40px 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
max-width: 1200px;
width: 100%;
background: #ffffff;
border-radius: 32px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
padding: 48px 40px 56px;
}
/* ===== 标题区 ===== */
.page-header {
margin-bottom: 48px;
border-bottom: 2px solid #f1f5f9;
padding-bottom: 28px;
}
.page-header h1 {
font-size: 2rem;
font-weight: 700;
letter-spacing: -0.02em;
background: linear-gradient(135deg, #0f172a, #3b82f6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 8px;
}
.page-header p {
color: #64748b;
font-size: 1.05rem;
max-width: 680px;
}
.badge {
display: inline-block;
background: #eef2ff;
color: #4338ca;
font-size: 0.75rem;
font-weight: 600;
padding: 4px 14px;
border-radius: 100px;
letter-spacing: 0.3px;
margin-top: 8px;
}
/* ===== 分组标题 ===== */
.section-title {
font-size: 1.25rem;
font-weight: 600;
margin: 40px 0 20px 0;
display: flex;
align-items: center;
gap: 12px;
}
.section-title span {
background: #e2e8f0;
color: #475569;
font-size: 0.7rem;
font-weight: 700;
padding: 2px 12px;
border-radius: 100px;
letter-spacing: 0.5px;
}
.section-desc {
color: #64748b;
font-size: 0.95rem;
margin-bottom: 20px;
}
/* ===== 按钮网格 ===== */
.btn-grid {
display: flex;
flex-wrap: wrap;
gap: 24px 40px;
align-items: center;
padding: 16px 0 8px 0;
}
.btn-grid.column {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.btn-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
min-width: 100px;
}
.btn-item .label {
font-size: 0.75rem;
font-weight: 500;
color: #64748b;
letter-spacing: 0.2px;
text-align: center;
}
.btn-item .label .note {
font-weight: 400;
color: #94a3b8;
font-size: 0.7rem;
}
/* ===== 基础按钮样式 ===== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: inherit;
font-size: 0.95rem;
font-weight: 500;
padding: 10px 24px;
border-radius: 10px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
text-decoration: none;
white-space: nowrap;
user-select: none;
position: relative;
outline: none;
min-height: 48px;
min-width: 80px;
background: transparent;
color: #1e293b;
}
.btn:focus-visible {
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.3);
}
/* 点击波纹 */
.btn .ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.35);
transform: scale(0);
animation: rippleAnim 0.6s linear;
pointer-events: none;
}
.btn-dark .ripple {
background: rgba(255, 255, 255, 0.25);
}
@keyframes rippleAnim {
to {
transform: scale(4);
opacity: 0;
}
}
/* ===== 按钮变体 ===== */
/* 主按钮 */
.btn-primary {
background: #2563eb;
color: #fff;
border-color: #2563eb;
}
.btn-primary:hover {
background: #1d4ed8;
border-color: #1d4ed8;
transform: translateY(-1px);
box-shadow: 0 8px 20px -6px rgba(37, 99, 235, 0.35);
}
.btn-primary:active {
transform: scale(0.97);
}
/* 次按钮 */
.btn-secondary {
background: #fff;
color: #1e293b;
border-color: #cbd5e1;
}
.btn-secondary:hover {
background: #f8fafc;
border-color: #94a3b8;
}
.btn-secondary:active {
transform: scale(0.97);
}
/* 第三按钮 (文字按钮) */
.btn-tertiary {
background: transparent;
color: #475569;
border-color: transparent;
padding: 10px 16px;
min-width: auto;
}
.btn-tertiary:hover {
background: #f1f5f9;
color: #0f172a;
}
.btn-tertiary:active {
transform: scale(0.97);
}
/* 危险按钮 */
.btn-danger {
background: #dc2626;
color: #fff;
border-color: #dc2626;
}
.btn-danger:hover {
background: #b91c1c;
border-color: #b91c1c;
box-shadow: 0 8px 20px -6px rgba(220, 38, 38, 0.35);
transform: translateY(-1px);
}
.btn-danger:active {
transform: scale(0.97);
}
/* 幽灵按钮 */
.btn-ghost {
background: transparent;
color: #2563eb;
border-color: #2563eb;
}
.btn-ghost:hover {
background: #eff6ff;
border-color: #1d4ed8;
}
.btn-ghost:active {
transform: scale(0.97);
}
/* 图标按钮 */
.btn-icon {
background: transparent;
color: #475569;
border-color: transparent;
padding: 10px 12px;
min-width: auto;
border-radius: 10px;
font-size: 1.2rem;
}
.btn-icon:hover {
background: #f1f5f9;
color: #0f172a;
}
.btn-icon:active {
transform: scale(0.92);
}
/* 浮动按钮 (FAB) */
.btn-fab {
background: #2563eb;
color: #fff;
border-color: #2563eb;
border-radius: 9999px;
width: 56px;
height: 56px;
padding: 0;
min-width: auto;
font-size: 1.5rem;
box-shadow: 0 8px 24px rgba(37, 99, 235, 0.3);
}
.btn-fab:hover {
background: #1d4ed8;
border-color: #1d4ed8;
transform: translateY(-2px) scale(1.02);
box-shadow: 0 12px 32px -8px rgba(37, 99, 235, 0.4);
}
.btn-fab:active {
transform: scale(0.94);
}
/* ===== 开关按钮 ===== */
.btn-toggle {
background: #e2e8f0;
border: none;
border-radius: 9999px;
width: 52px;
height: 28px;
padding: 0;
min-width: auto;
cursor: pointer;
position: relative;
transition: background 0.25s ease;
flex-shrink: 0;
}
.btn-toggle .toggle-knob {
display: block;
width: 22px;
height: 22px;
background: #fff;
border-radius: 50%;
position: absolute;
top: 3px;
left: 3px;
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.btn-toggle.active {
background: #2563eb;
}
.btn-toggle.active .toggle-knob {
transform: translateX(24px);
}
.btn-toggle:hover {
opacity: 0.85;
}
.btn-toggle:active .toggle-knob {
transform: scale(0.92);
}
.btn-toggle.active:active .toggle-knob {
transform: translateX(24px) scale(0.92);
}
/* ===== 下拉按钮 ===== */
.btn-dropdown {
position: relative;
}
.btn-dropdown .dropdown-menu {
position: absolute;
top: calc(100% + 8px);
left: 0;
min-width: 180px;
background: #fff;
border-radius: 12px;
box-shadow: 0 16px 48px -12px rgba(0, 0, 0, 0.2);
border: 1px solid #e2e8f0;
padding: 6px 0;
opacity: 0;
visibility: hidden;
transform: translateY(-8px) scale(0.96);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 100;
list-style: none;
}
.btn-dropdown .dropdown-menu.open {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
.btn-dropdown .dropdown-menu li {
padding: 10px 20px;
font-size: 0.9rem;
color: #1e293b;
cursor: pointer;
transition: background 0.15s;
display: flex;
align-items: center;
gap: 10px;
}
.btn-dropdown .dropdown-menu li:hover {
background: #f1f5f9;
}
.btn-dropdown .dropdown-menu li:active {
background: #e2e8f0;
}
.btn-dropdown .dropdown-menu .divider {
height: 1px;
background: #e2e8f0;
margin: 4px 12px;
padding: 0;
}
.btn-dropdown .dropdown-menu .label-icon {
font-size: 1rem;
width: 20px;
text-align: center;
}
.btn-dropdown .arrow {
font-size: 0.7rem;
transition: transform 0.2s;
margin-left: 4px;
}
.btn-dropdown .arrow.open {
transform: rotate(180deg);
}
/* ===== 分段按钮 ===== */
.btn-segmented {
display: inline-flex;
background: #f1f5f9;
border-radius: 10px;
padding: 4px;
gap: 4px;
border: 1px solid #e2e8f0;
}
.btn-segmented .seg-item {
padding: 8px 20px;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
color: #64748b;
cursor: pointer;
transition: all 0.2s;
border: none;
background: transparent;
font-family: inherit;
white-space: nowrap;
}
.btn-segmented .seg-item:hover {
color: #0f172a;
}
.btn-segmented .seg-item.active {
background: #fff;
color: #1e293b;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.btn-segmented .seg-item:active {
transform: scale(0.95);
}
/* ===== 加载按钮 ===== */
.btn-loading {
position: relative;
}
.btn-loading .loader {
display: none;
width: 20px;
height: 20px;
border: 2.5px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
.btn-loading.loading .loader {
display: inline-block;
}
.btn-loading.loading .btn-text {
visibility: hidden;
opacity: 0;
width: 0;
display: inline-block;
overflow: hidden;
}
.btn-loading.loading {
gap: 0;
padding: 10px 20px;
}
.btn-loading.loading .loader {
margin: 0 2px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ===== 禁用按钮 ===== */
.btn-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
filter: grayscale(0.2);
}
/* ===== 交互反馈区 ===== */
.feedback-area {
margin-top: 40px;
padding: 24px 28px;
background: #f8fafc;
border-radius: 16px;
border: 1px solid #e2e8f0;
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.feedback-area .fb-label {
font-size: 0.9rem;
font-weight: 500;
color: #475569;
min-width: 100px;
}
.feedback-area .fb-log {
font-size: 0.9rem;
color: #1e293b;
background: #fff;
padding: 8px 18px;
border-radius: 8px;
border: 1px solid #e2e8f0;
min-height: 44px;
display: flex;
align-items: center;
flex: 1;
min-width: 200px;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.85rem;
}
.feedback-area .fb-log .highlight {
color: #2563eb;
font-weight: 600;
}
/* ===== 场景示例 ===== */
.scene-example {
margin-top: 48px;
padding: 28px 32px;
background: #f1f5f9;
border-radius: 16px;
border: 1px solid #e2e8f0;
}
.scene-example h3 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 16px;
color: #0f172a;
}
.scene-example .scene-buttons {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
}
.scene-example .scene-label {
font-size: 0.8rem;
color: #64748b;
font-weight: 500;
margin-right: 8px;
}
/* ===== 响应式 ===== */
@media (max-width: 720px) {
.container {
padding: 28px 18px 36px;
}
.page-header h1 {
font-size: 1.5rem;
}
.btn-grid {
gap: 16px 24px;
}
.btn {
font-size: 0.85rem;
padding: 8px 18px;
min-height: 42px;
min-width: 64px;
}
.btn-fab {
width: 48px;
height: 48px;
font-size: 1.2rem;
}
.feedback-area {
flex-direction: column;
align-items: stretch;
}
.feedback-area .fb-label {
min-width: auto;
}
.btn-segmented .seg-item {
padding: 6px 14px;
font-size: 0.8rem;
}
.scene-example {
padding: 20px 16px;
}
}
@media (max-width: 480px) {
body {
padding: 16px 8px;
}
.container {
padding: 20px 12px 28px;
border-radius: 20px;
}
.btn-grid {
gap: 12px 16px;
justify-content: center;
}
.btn-item {
min-width: 70px;
}
.btn {
font-size: 0.8rem;
padding: 6px 14px;
min-height: 36px;
min-width: 56px;
border-radius: 8px;
}
.btn-fab {
width: 44px;
height: 44px;
font-size: 1.1rem;
}
.btn-toggle {
width: 44px;
height: 24px;
}
.btn-toggle .toggle-knob {
width: 18px;
height: 18px;
top: 3px;
left: 3px;
}
.btn-toggle.active .toggle-knob {
transform: translateX(20px);
}
.btn-dropdown .dropdown-menu {
min-width: 150px;
left: -8px;
}
.btn-dropdown .dropdown-menu li {
padding: 8px 16px;
font-size: 0.8rem;
}
.feedback-area .fb-log {
font-size: 0.75rem;
min-height: 36px;
padding: 6px 14px;
}
}
/* ===== 工具类 ===== */
.mt-8 {
margin-top: 8px;
}
.mt-16 {
margin-top: 16px;
}
.flex-center {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.gap-8 {
gap: 8px;
}
.gap-4 {
gap: 4px;
}
/* 自定义滚动 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 100px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}(function() {
'use strict';
const log = document.getElementById('feedbackLog');
function writeLog(msg, type) {
const now = new Date();
const time = now.toLocaleTimeString('zh-CN', { hour12: false });
const icon = type === 'action' ? '⚡' :
type === 'toggle' ? '🔄' :
type === 'seg' ? '📐' :
type === 'dropdown' ? '📂' :
type === 'loading' ? '⏳' : '📌';
log.innerHTML = `<span style="color:#94a3b8;font-weight:400;">${time}</span> ${icon} ${msg}`;
}
// ---- 波纹效果 ----
function createRipple(e, btn) {
const rect = btn.getBoundingClientRect();
const size = Math.max(rect.width, rect.height) * 0.6;
const x = (e.clientX || e.touches?.[0]?.clientX || rect.left + rect.width / 2) - rect.left - size / 2;
const y = (e.clientY || e.touches?.[0]?.clientY || rect.top + rect.height / 2) - rect.top - size / 2;
const ripple = document.createElement('span');
ripple.className = 'ripple';
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
btn.style.overflow = 'hidden';
btn.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
}
// ---- 通用点击处理 ----
document.addEventListener('click', function(e) {
const btn = e.target.closest('.btn, .btn-toggle, .seg-item, .btn-dropdown .dropdown-menu li');
if (!btn) return;
// 禁用按钮不处理
if (btn.classList.contains('btn-disabled') || btn.disabled) {
writeLog('⛔ 按钮处于禁用状态,无法交互', 'action');
return;
}
// ---- 开关 ----
if (btn.classList.contains('btn-toggle')) {
e.preventDefault();
btn.classList.toggle('active');
const state = btn.classList.contains('active') ? '开启' : '关闭';
writeLog(`开关切换为 <span class="highlight">${state}</span>`, 'toggle');
createRipple(e, btn);
return;
}
// ---- 分段按钮 ----
if (btn.classList.contains('seg-item')) {
const parent = btn.closest('.btn-segmented');
if (!parent) return;
parent.querySelectorAll('.seg-item').forEach(item => item.classList.remove('active'));
btn.classList.add('active');
const val = btn.dataset.value || btn.textContent.trim();
writeLog(`视图切换为 <span class="highlight">${val}</span>`, 'seg');
createRipple(e, btn);
return;
}
// ---- 下拉菜单项 ----
if (btn.closest('.dropdown-menu')) {
const menu = btn.closest('.dropdown-menu');
const container = menu.closest('.btn-dropdown');
const trigger = container?.querySelector('.btn');
const val = btn.dataset.value || btn.textContent.trim();
writeLog(`下拉选择 <span class="highlight">${val}</span>`, 'dropdown');
menu.classList.remove('open');
if (trigger) {
const arrow = trigger.querySelector('.arrow');
if (arrow) arrow.classList.remove('open');
}
createRipple(e, btn);
return;
}
// ---- 下拉触发器 ----
if (btn.dataset.action === 'dropdown') {
const container = btn.closest('.btn-dropdown');
if (!container) return;
const menu = container.querySelector('.dropdown-menu');
const arrow = btn.querySelector('.arrow');
if (!menu) return;
const isOpen = menu.classList.contains('open');
// 关闭所有其他下拉
document.querySelectorAll('.btn-dropdown .dropdown-menu.open').forEach(m => {
if (m !== menu) {
m.classList.remove('open');
const trig = m.closest('.btn-dropdown')?.querySelector('.btn');
const a = trig?.querySelector('.arrow');
if (a) a.classList.remove('open');
}
});
if (isOpen) {
menu.classList.remove('open');
if (arrow) arrow.classList.remove('open');
} else {
menu.classList.add('open');
if (arrow) arrow.classList.add('open');
}
createRipple(e, btn);
return;
}
// ---- 加载按钮 ----
if (btn.dataset.action === 'loading') {
if (btn.classList.contains('loading')) return;
btn.classList.add('loading');
writeLog('⏳ 加载中...', 'loading');
createRipple(e, btn);
setTimeout(() => {
btn.classList.remove('loading');
writeLog('✅ 加载完成', 'loading');
}, 2000);
return;
}
// ---- 其他所有按钮 ----
const action = btn.dataset.action || 'button';
const label = btn.textContent.trim().slice(0, 20) || action;
writeLog(`点击 <span class="highlight">${label}</span> (${action})`, 'action');
createRipple(e, btn);
// 特定动作的额外反馈
if (action === 'scene-confirm') {
writeLog('✅ 表单已提交', 'action');
}
if (action === 'scene-delete') {
writeLog('🗑️ 删除操作已触发 (演示)', 'action');
}
if (action === 'scene-cancel') {
writeLog('✖️ 已取消', 'action');
}
if (action === 'text-confirm') {
writeLog('✅ 确认操作', 'action');
}
if (action === 'text-cancel') {
writeLog('✖️ 已取消', 'action');
}
if (action === 'text-learn') {
writeLog('📖 了解更多', 'action');
}
if (action === 'fab') {
writeLog('➕ 浮动操作按钮 (FAB)', 'action');
}
if (action === 'icon' || action === 'icon-heart') {
const name = action === 'icon' ? '搜索' : '收藏';
writeLog(`❤️ 图标按钮 — ${name}`, 'action');
}
if (action === 'toolbar-edit') {
writeLog('✎ 编辑模式', 'action');
}
if (action === 'toolbar-download') {
writeLog('⬇️ 下载开始', 'action');
}
if (action === 'toolbar-save') {
writeLog('💾 已保存', 'action');
}
});
// ---- 点击外部关闭下拉 ----
document.addEventListener('click', function(e) {
if (!e.target.closest('.btn-dropdown')) {
document.querySelectorAll('.btn-dropdown .dropdown-menu.open').forEach(menu => {
menu.classList.remove('open');
const trig = menu.closest('.btn-dropdown')?.querySelector('.btn');
const arrow = trig?.querySelector('.arrow');
if (arrow) arrow.classList.remove('open');
});
}
});
// ---- 清空日志 ----
document.getElementById('clearLog').addEventListener('click', function(e) {
log.innerHTML = '日志已清空';
createRipple(e, this);
});
// ---- 键盘支持:Enter/Space 触发按钮 ----
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
const target = e.target;
if (target.closest && target.closest('.btn, .btn-toggle, .seg-item')) {
const btn = target.closest('.btn, .btn-toggle, .seg-item');
if (btn && !btn.disabled && !btn.classList.contains('btn-disabled')) {
// 模拟点击
btn.click();
e.preventDefault();
}
}
}
});
// ---- 初始提示 ----
setTimeout(() => {
writeLog('点击任意按钮体验交互反馈', 'action');
}, 300);
})();怎么选按钮
- 主按钮(Primary):一屏只放一个,承载页面最重要的动作,比如「确认提交」。
- 次按钮(Secondary):与主按钮搭配,做次要或取消操作。
- 文字按钮(Text):最轻量,用于辅助链接式操作,如「了解更多」。
- 危险按钮(Danger):删除、退出等不可逆动作,用红色强化警示。
- 幽灵 / 图标 / FAB:在空间紧张或移动端用图标、浮动按钮替代文字。
- 开关 / 分段 / 下拉 / 加载 / 禁用:表达状态与交互过程,注意加载与禁用态的反馈。
完整的 HTML / CSS / JS 都附在上方卡片的「源码」标签页里,复制即可复用。
评论 (0)