本文介绍一种基于事件委托的健壮方案,解决动态创建按钮后无法立即响应点击销毁的问题,避免重复绑定、事件冲突及内存泄漏。
在文本高亮场景中,动态创建按钮并确保其“点击即销毁”看似简单,实则容易陷入事件监听器嵌套、this/arguments.callee
误用、以及 mousedown/click 事件时序冲突等陷阱。原代码的核心问题在于:
✅ 正确解法:统一事件委托 + 单一事件源管理
我们改用全局事件委托(推荐 click 和 mousedown 分离职责),并通过 data-* 属性标记动态元素,避免反复绑定/解绑:
请选中这段文字来触发按钮 另一段可选中的内容
// 全局仅绑定一次,清晰可控
document.addEventListener('mouseup', handleSelection);
document.addEventListener('click', handleButtonClick);
document.addEventListener('mousedown', handleOutsideClick);
function handleSelection() {
const selection = window.getSelection();
const text = selection.toString().trim();
const existingBtn = document.querySelector('[data-dynamic="toolbar-btn"]');
if (text && !existingBtn) {
const rect = selection.getRangeAt(0).getBoundingClientRect();
const btn = document.createElement('button');
btn.textContent = '✏️';
btn.setAttribute('data-dynamic', 'toolbar-btn');
btn.style.cssText = `
position: fixed;
top: ${rect.top - 40}px;
left: ${rect.left}px;
padding: 6px 12px;
border: none;
border-radius: 4px;
background: #007bff;
color: white;
cursor: pointer;
z-index: 1000;
font-size: 14px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
`;
document.body.appendChild(btn);
}
}
function handleButtonClick(e) {
const btn = e.target.closest('[data-dynamic="toolbar-btn"]');
if (!btn) return;
// ✅ 点击按钮:立即销毁自身,并显示弹窗(此处简化为 console)
btn.remove();
console.log('弹窗已打开,处理文本:“' + window.getSelection().toString().trim() + '”');
// 实际中可调用 createPopup(...) 并插入 body
}
function handleOutsideClick(e) {
const btn = document.querySelector('[data-dynamic="toolbar-btn"]');
const isClickInsideBtn = btn && btn.contains(e.target);
// ✅ 点击非按钮区域:销毁按钮(但不干扰按钮自身的 click)
if (btn && !isClickInsideBtn) {
btn.remove();
}
}? 关键要点总结:
该方案兼顾简洁性与可维护性,彻底规避了原代码中的竞态与冗余绑定问题,是现代 Web 文本工具栏交互的推荐实践。