稳妥做法是分离input与div标签容器,输入框始终在末尾,按空格/逗号/回车时切分、去空、去重、添加并清空;标签支持键盘(Delete/Backspace)和鼠标双路径删除;失焦时仅trim非空才提交;兼顾无障碍与iOS兼容。

用 input + div 实现标签输入框,别碰 contenteditable
原生 input 不支持内嵌标签,强行用 contenteditable 会引发光标错位、iOS 输入法异常、回车行为失控。稳妥做法是分离输入与渲染:一个 input 专管输入,一个 div 专管显示标签。
关键约束有三条:
• input 始终置于标签列表末尾;
• 用户按 Enter / Space / , 时才触发添加;
• 标签容器用 div[role="list"],每个标签用 span[role="listitem"],保障可访问性。
示例片段:
const inputEl = document.querySelector('#tag-input');
const container = document.querySelector('#tag-container');
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ' || e.key === ',') {
e.preventDefault();
const raw = inputEl.value;
const tags = raw.split(/[\s,,]+/).map(t => t.trim()).filter(t => t);
tags.forEach(tag => addTag(tag)); // addTag 需自行实现去重插入
inputEl.value = '';
}
});
表格行删除必须用事件委托 + data-id
直接给每行按钮绑定事件,增删行后要反复重绑,易内存泄漏;用 rowIndex 当标识更危险——删掉第 2 行,后续所有 rowIndex 全乱套。
立即学习“前端免费学习笔记(深入)”;
正确姿势:
• 在 tbody 上监听 click;
• 用 event.target.matches('button[data-action="delete"]') 判断是否点中删除按钮;
• 从按钮的 dataset.id 拿真实 ID(来自后端或初始化时生成);
• 删 DOM 前先同步 JS 数据数组,比如 items = items.filter(item => item.id !== id)。
务必写 type="button"——否则在 form 内点击会触发表单提交,页面刷新。这个坑在 Chrome/Firefox/Safari 上表现一致,但后果严重。
标签删除要同时支持键盘和鼠标双路径
只放 × 图标不够:屏幕阅读器用户无法聚焦,Power User 无法用 Backspace 删除末尾标签,也无法聚焦某标签后按 Delete 精准移除。
实操要点:
• 每个标签元素加 tabindex="0";
• 绑定 keydown:聚焦时按 Delete 或 Backspace 就删当前标签;
• 鼠标点击 × 时调用 event.stopPropagation(),防止意外触发父级 focus;
• 删除后若无剩余标签,自动 inputEl.focus(),保持操作流不中断;
• × 图标不能加 aria-hidden="true",而应设 role="button" 和 aria-label="删除标签 {label}"。
移动端按钮点击区域太小,padding 不够用
button 默认 hit area 只覆盖文字区域,手指按在周围空白处常无响应。单纯加大 padding 有时仍不可靠,尤其在高密度布局中。
更稳方案:
• 用 ::before 或 ::after 伪元素撑开点击热区;
• 或外层包一层 div 并设 cursor: pointer,把事件委托过去;
• 确保最小点击区域 ≥ 44×44px(iOS 规范),且相邻按钮间距 ≥ 8px。
别忽略视觉反馈:加 :active 样式或轻微缩放,让用户确认操作已触发。



















