最轻量且语义正确的方案是使用 stretched-link 类:在卡片末尾添加空 <a> 标签并赋予该类,它通过绝对定位覆盖整个卡片区域,保持 DOM 结构纯净、支持无障碍访问、不干扰内部交互,且无需额外 JavaScript。
stretched-link 类是最轻量、语义正确的方案bootstrap 5 官方不推荐给 .card 加 onclick 或包裹 <a> 标签,因为会破坏语义结构、影响无障碍访问(如屏幕阅读器无法识别可点击区域),还可能干扰卡片内按钮或链接的事件冒泡。
正确做法是在卡片内部末尾插入一个空的、带 href 和 stretched-link 类的 <a> 标签。它通过绝对定位填满整个卡片内容区,但 DOM 中仍保持卡片结构干净:
<div class="card"> <img src="..." class="card-img-top"> <div class="card-body"> <h5 class="card-title">标题</h5> <p class="card-text">描述文字</p> </div> <a href="/detail/123" class="stretched-link"></a></div>
stretched-link 必须放在卡片最内层容器里(如 .card-body 内或紧贴 .card 结束标签前),不能放在 .card-header 或 .card-footer 外部stretched-link 的 z-index 默认较低,不会拦截它们的点击<a> 包裹整个 .card —— 会出问题常见错误是把整个 <div class="card">...</div> 套进 <a href="..."> 里。这在 HTML5 中虽不报错,但会导致:
<a> 默认为 inline)click 事件被 <a> 拦截,需额外 e.stopPropagation() 处理target="_blank" 和 rel
如果跳转目标是外部链接或需新开页面,直接在 stretched-link 上加属性即可:
<a href="https://example.com" target="_blank" rel="noopener noreferrer" class="stretched-link"></a>
target="_blank" 必须搭配 rel="noopener noreferrer",否则存在安全风险(新页面可通过 window.opener 控制原页面)rel="noopener",noreferrer 还能防止 Referer 泄露敏感路径当跳转目标需动态计算(如根据数据属性拼接 URL)、或要配合表单验证、埋点统计时,stretched-link 就不够用了。此时应在卡片上监听 click,并排除子元素干扰:
<div class="card" data-id="123" data-base-url="/item/"> <!-- 卡片内容 --></div>
对应 JS:
document.querySelectorAll('.card[data-id]').forEach(card => { card.addEventListener('click', function(e) { // 只响应卡片本体点击,不响应内部按钮/链接 if (e.target === this || e.target.closest('.card-body') === this) { const url = this.dataset.baseUrl + this.dataset.id; window.location.href = url; } });});
e.target 是否为卡片本身,避免点击标题、按钮时也触发跳转<button> 或 <a href="#"> 等元素,建议统一加 data-no-redirect 并在 JS 中 if (e.target.hasAttribute('data-no-redirect')) return
stretched-link,但需自行处理键盘访问(如监听 keydown 的 Enter/Space 键)实际项目中最容易被忽略的是:卡片内如果有 .card-link 或自定义按钮,它们的 z-index 可能被 stretched-link 覆盖——不是 bug,是设计使然。解决办法只有显式提升这些元素的 z-index,或者改用 JS 方案接管全部逻辑。