頁面內容含特殊標記 `#`,需在每個 `#` 後插入一個按鈕:
// 原始內容
xxxxx
#target#
yyyyy
// 期望結果
xxxxx
#target#
yyyyy
直接操作 `innerHTML` 會破壞事件綁定、選區、輸入框狀態。最佳實踐:取得文字 → 正則替換 → 寫回 innerHTML → 事件委託。
function injectButtonsAfterHash(containerSelector, buttonHtml) {
const container = document.querySelector(containerSelector);
if (!container) return;
// 1. 獲取純文字內容 (避免破壞現有標籤)
// 若容器含複雜結構,建議用 TreeWalker 遍歷文字節點
let text = container.textContent || container.innerText;
// 2. 正則替換:匹配 # 後插入按鈕 HTML
// 使用佔位符避免重複替換已插入的按鈕內的 #
const placeholder = '___HASH_PLACEHOLDER___';
text = text.replace(/#/g, placeholder); // 先保護所有 #
text = text.replace(new RegExp(placeholder, 'g'), `#${buttonHtml}`);
// 3. 寫回 (僅演示,實際建議針對文字節點替換)
container.innerHTML = text; // 注意:會移除容器內原有標籤與事件
// 4. 事件委託 (容器層級綁定一次)
container.addEventListener('click', e => {
if (e.target.matches('.injected-btn')) {
const hashText = e.target.previousSibling?.textContent?.trim() || '';
console.log('按鈕點擊,關聯標記:', hashText);
// 業務邏輯...
}
});
}
// 使用
const btnHtml = '';
injectButtonsAfterHash('#content-area', btnHtml);
function injectInTextNodes(root, targetChar, injectHtml) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
const textNodes = [];
while (walker.nextNode()) textNodes.push(walker.currentNode);
textNodes.forEach(node => {
if (node.textContent.includes(targetChar)) {
const span = document.createElement('span');
// 將文字按 targetChar 分割,交替插入文字節點與按鈕
const parts = node.textContent.split(new RegExp(`(${targetChar})`));
parts.forEach((part, i) => {
if (part === targetChar) {
span.appendChild(document.createTextNode(part));
const btn = document.createElement('span');
btn.innerHTML = injectHtml;
btn.className = 'injected-btn-wrapper';
span.appendChild(btn);
} else if (part) {
span.appendChild(document.createTextNode(part));
}
});
node.parentNode.replaceChild(span, node);
}
});
}
// 使用
injectInTextNodes(document.getElementById('content'), '#', '');
| 方法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| innerHTML 替換 | 代碼簡單 | 破壞現有事件/輸入狀態、XSS 風險 | 純靜態內容、初始化渲染 |
| TreeWalker 文字節點替換 | 保留原有 DOM/事件、安全 | 代碼較複雜 | 富文本編輯器、動態頁面 |
| 虛擬 DOM (React/Vue) | 框架層面解決 | 需框架支持 | 現代前端專案 |
若標記內容來自用戶輸入,務必對 `buttonHtml` 及周圍文字進行 HTML 編碼,防止 XSS:
function escapeHtml(str) {
return str.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
}