inert 非交互
inert 是一个布尔型全局属性,用于使元素及其所有后代元素变为非交互状态。设置 inert 后,元素虽然在页面上可见,但用户无法通过鼠标、键盘或辅助技术与其中任何内容进行交互。这在模态对话框背景、页面加载中、禁用表单区域等场景下非常实用。
前置知识
阅读本节前,建议先了解:popover 弹出层
基础概念
什么是 inert 属性
inert 属性让元素"存在但不可操作"。与 hidden 不同,inert 元素在页面上仍然可见,但完全阻止交互:
- 点击、输入、选择等鼠标操作无效
- Tab 键无法聚焦到其中的元素
- 屏幕阅读器会跳过这些内容
- 表单控件不会被提交
html
<!-- 基本用法 -->
<div inert>
<p>这段文字可见但不可交互。</p>
<button>这个按钮无法点击</button>
<input type="text" placeholder="无法输入">
</div>inert 与其他禁用方式对比
| 方式 | 可见性 | 鼠标交互 | 键盘聚焦 | 辅助技术 | 适用场景 |
|---|---|---|---|---|---|
inert | 可见 | 全部阻止 | 无法聚焦 | 跳过 | 模态背景、临时禁用 |
hidden | 不可见 | N/A | N/A | 跳过 | 内容不相关 |
disabled (表单) | 可见(变灰) | 阻止 | 跳过 | 提示禁用 | 单个表单控件 |
pointer-events: none | 可见 | 阻止点击 | 可聚焦 | 仍可读 | CSS 层面禁用 |
aria-disabled="true" | 可见 | 允许 | 可聚焦 | 提示禁用 | 仅语义标记 |
语法
html
<!-- 布尔属性:有值即为 true -->
<div inert>内容不可交互</div>
<div inert="">内容不可交互</div>
<div inert="inert">内容不可交互</div>
<!-- 移除 inert 属性恢复交互 -->
<div>内容可交互</div>JavaScript 操作
javascript
const section = document.getElementById('disabled-section');
// 设置为非交互
section.inert = true;
// 恢复交互
section.inert = false;
// 检查状态
console.log(section.inert); // true 或 false详细说明
典型应用:模态对话框背景
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>inert 模态背景示例</title>
<style>
/* 页面内容 */
.page-content {
padding: 20px;
transition: opacity 0.3s;
}
.page-content[dimmed] {
opacity: 0.5;
pointer-events: none; /* 额外的视觉反馈 */
}
/* 模态对话框 */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
align-items: center;
justify-content: center;
}
.modal-overlay.active {
display: flex;
}
.modal-box {
background: white;
padding: 32px;
border-radius: 12px;
max-width: 400px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body>
<!-- 页面主体内容 -->
<main id="main-content" class="page-content">
<h1>网站首页</h1>
<nav>
<a href="#">首页</a>
<a href="#">关于</a>
<a href="#">联系</a>
</nav>
<form>
<input type="text" placeholder="搜索...">
<button type="submit">搜索</button>
</form>
<p>这里有很多可交互的内容...</p>
</main>
<!-- 模态对话框 -->
<div class="modal-overlay" id="modal">
<div class="modal-box" role="dialog" aria-labelledby="modal-title">
<h2 id="modal-title">确认操作</h2>
<p>确定要删除这条记录吗?此操作不可撤销。</p>
<button id="btn-cancel">取消</button>
<button id="btn-confirm">确认删除</button>
</div>
</div>
<button id="btn-open-modal">打开模态框</button>
<script>
const mainContent = document.getElementById('main-content');
const modal = document.getElementById('modal');
// 打开模态框:使页面内容变为 inert
document.getElementById('btn-open-modal').addEventListener('click', () => {
modal.classList.add('active');
mainContent.inert = true; // 关键:禁用背景交互
mainContent.classList.add('dimmed');
document.getElementById('btn-confirm').focus();
});
// 关闭模态框:恢复页面内容交互
function closeModal() {
modal.classList.remove('active');
mainContent.inert = false;
mainContent.classList.remove('dimmed');
document.getElementById('btn-open-modal').focus();
}
document.getElementById('btn-cancel').addEventListener('click', closeModal);
document.getElementById('btn-confirm').addEventListener('click', closeModal);
// ESC 关闭
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.classList.contains('active')) {
closeModal();
}
});
</script>
</body>
</html>典型应用:分步表单禁用
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>inert 分步表单</title>
<style>
.step {
padding: 20px;
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-bottom: 16px;
}
.step[inert] {
opacity: 0.4;
}
.step-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.step-number {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
background: #e2e8f0;
color: #64748b;
}
.step.active .step-number {
background: #3b82f6;
color: white;
}
.step.done .step-number {
background: #22c55e;
color: white;
}
label {
display: block;
margin-bottom: 8px;
}
input {
width: 100%;
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 4px;
margin-bottom: 12px;
}
</style>
</head>
<body>
<h1>用户注册</h1>
<form id="registration-form">
<!-- 步骤 1:激活 -->
<fieldset class="step active" id="step-1">
<legend>基本信息</legend>
<label>用户名:<input type="text" name="username" required></label>
<label>邮箱:<input type="email" name="email" required></label>
<button type="button" onclick="goToStep(2)">下一步</button>
</fieldset>
<!-- 步骤 2:禁用 -->
<fieldset class="step" id="step-2" inert>
<legend>详细资料</legend>
<label>手机号:<input type="tel" name="phone"></label>
<label>职业:<input type="text" name="occupation"></label>
<button type="button" onclick="goToStep(1)">上一步</button>
<button type="button" onclick="goToStep(3)">下一步</button>
</fieldset>
<!-- 步骤 3:禁用 -->
<fieldset class="step" id="step-3" inert>
<legend>确认提交</legend>
<div id="summary"></div>
<button type="button" onclick="goToStep(2)">上一步</button>
<button type="submit">提交注册</button>
</fieldset>
</form>
<script>
let currentStep = 1;
function goToStep(step) {
// 禁用当前步骤
const currentEl = document.getElementById(`step-${currentStep}`);
currentEl.inert = true;
currentEl.classList.remove('active');
// 启用目标步骤
const targetEl = document.getElementById(`step-${step}`);
targetEl.inert = false;
targetEl.classList.add('active');
// 标记已完成的步骤
for (let i = 1; i < step; i++) {
document.getElementById(`step-${i}`).classList.add('done');
}
currentStep = step;
// 如果到最后一步,生成摘要
if (step === 3) {
const form = document.getElementById('registration-form');
document.getElementById('summary').innerHTML = `
<p>用户名:${form.username.value}</p>
<p>邮箱:${form.email.value}</p>
<p>手机号:${form.phone.value || '未填写'}</p>
<p>职业:${form.occupation.value || '未填写'}</p>
`;
}
}
</script>
</body>
</html>inert 与 CSS 配合
为 inert 元素添加视觉反馈,让用户知道这些内容不可交互:
css
/* 为 inert 元素添加视觉反馈 */
[inert] {
opacity: 0.5;
cursor: not-allowed;
filter: grayscale(0.5);
pointer-events: none; /* 双重保障 */
}
/* 表单区域中的 inert 效果 */
.form-section[inert] {
position: relative;
}
.form-section[inert]::after {
content: '请先完成上一步';
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.7);
color: #64748b;
font-size: 14px;
border-radius: inherit;
}inert 对辅助技术的影响
html
<!-- inert 区域对屏幕阅读器的影响 -->
<div inert>
<!-- 屏幕阅读器会完全跳过这些内容 -->
<p>这段文字屏幕阅读器不会读出。</p>
<button>这个按钮不会被识别。</button>
<a href="#">这个链接不可访问。</a>
</div>
<!-- inert 不影响搜索索引 -->
<!-- 搜索引擎仍然可以索引 inert 元素中的内容 -->实战示例
页面加载状态
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>inert 加载状态</title>
<style>
.page-wrapper {
position: relative;
min-height: 100vh;
}
.loading-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(255, 255, 255, 0.8);
z-index: 9999;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 16px;
}
.loading-overlay.active {
display: flex;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e2e8f0;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
[inert] {
opacity: 0.6;
pointer-events: none;
}
</style>
</head>
<body>
<div class="loading-overlay" id="loading" role="alert" aria-live="assertive">
<div class="spinner"></div>
<p>数据加载中,请稍候...</p>
</div>
<div id="app" inert>
<h1>数据仪表盘</h1>
<nav>
<a href="#">概览</a>
<a href="#">分析</a>
<a href="#">设置</a>
</nav>
<section>
<table>
<tr><th>日期</th><th>访问量</th><th>转化率</th></tr>
<!-- 动态数据行 -->
</table>
</section>
</div>
<script>
const app = document.getElementById('app');
const loading = document.getElementById('loading');
// 模拟数据加载
async function loadData() {
loading.classList.add('active');
app.inert = true;
try {
// 模拟 API 请求
await new Promise(resolve => setTimeout(resolve, 2000));
// 处理数据...
} finally {
loading.classList.remove('active');
app.inert = false;
}
}
loadData();
</script>
</body>
</html>注意事项
inert 与 disabled 的区别
html
<!-- inert:阻止元素及所有后代的交互 -->
<div inert>
<input type="text" placeholder="不可输入"> <!-- 不可交互 -->
<button>不可点击</button> <!-- 不可交互 -->
<a href="#">不可导航</a> <!-- 不可交互 -->
<div>
<input type="checkbox"> <!-- 不可交互 -->
</div>
</div>
<!-- disabled:只对表单控件有效 -->
<fieldset disabled>
<input type="text" placeholder="变灰不可输入"> <!-- 不可交互 -->
<button>变灰不可点击</button> <!-- 不可交互 -->
<a href="#">仍然可以点击!</a> <!-- 注意:仍可点击 -->
<div>
<p>这段文字仍然可以选择和复制。</p> <!-- 注意:可选择 -->
</div>
</fieldset>不要用于 SEO 操控
html
<!-- 错误:不要用 inert 隐藏不想让用户看到的内容 -->
<div inert>
<p>这段垃圾内容用来堆砌关键词...</p>
</div>
<!-- inert 是功能性的,不是用来欺骗搜索引擎的 -->浏览器兼容性
| 浏览器 | 支持版本 |
|---|---|
| Chrome | 102+ |
| Edge | 102+ |
| Safari | 15.5+ |
| Firefox | 112+ |
对于旧浏览器的 polyfill:
html
<!-- inert 属性 polyfill -->
<script src="https://unpkg.com/wicg-inert/dist/inert.min.js"></script>最佳实践
1. 模态对话框优先使用 inert 而非自定义焦点管理
javascript
// 推荐:使用 inert 禁用背景
function openModal() {
document.getElementById('main-content').inert = true;
modal.showModal();
}
function closeModal() {
modal.close();
document.getElementById('main-content').inert = false;
}
// 不推荐:手动实现焦点陷阱
// function trapFocus(modal) { ... 复杂代码 ... }2. 为 inert 元素提供视觉反馈
css
/* 始终为 inert 元素提供视觉状态变化 */
[inert] {
opacity: 0.5;
pointer-events: none;
user-select: none;
filter: saturate(0.3);
}3. 配合 ARIA 使用
html
<!-- 为使用 inert 的区域添加说明 -->
<div id="page-content" inert aria-hidden="true">
<!-- 背景内容被禁用 -->
</div>
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">对话框标题</h2>
<!-- 对话框内容 -->
</div>下一节
继续学习:鼠标事件