嵌套DOM替换恢复BUG求助:网页翻译程序DOM处理问题
问题复现
测试HTML结构:
<div class="content"> <p>需要翻译的文本</p> <pre> <code>const test = "这段代码不需要翻译"</code> </pre> <img src="test.jpg" alt="图片不需要翻译"> </div>
存在问题的原body_dom_del函数:
function body_dom_del() { const skipTags = ['pre', 'code', 'img']; const elements = document.querySelectorAll(skipTags.join(',')); const placeholders = []; elements.forEach(el => { const placeholder = document.createElement('span'); placeholder.className = 'translate-placeholder'; el.replaceWith(placeholder); placeholders.push(el.outerHTML); }); return placeholders; }
运行该函数后,querySelectorAll会选中3个目标元素(pre、code、img),但实际仅生成2个占位符:处理pre时,其内部的code元素会随pre一起被替换,导致后续遍历不到code,最终placeholders数组只有2项,还原时无法匹配所有原DOM。
问题原因
直接按querySelectorAll返回的顺序处理元素时,嵌套场景下父元素被替换后,其子元素会从DOM树中移除,后续遍历会跳过这些子元素,导致占位符数量与目标DOM数量不匹配。
解决方案
- 按DOM深度倒序处理:先替换深层子元素,再替换父元素,避免父元素替换时包含未处理的子元素。
- 唯一索引绑定占位符:为每个占位符添加
data-index属性,对应存储原DOM的数组索引,确保还原时精准匹配。
修改后的完整实现:
// 闭包存储原DOM序列化内容,避免全局污染 const domStore = (() => { let store = []; return { save: (html) => { const index = store.length; store.push(html); return index; }, get: (index) => store[index], clear: () => { store = []; } }; })(); function body_dom_del() { const skipTags = ['pre', 'code', 'img']; const elements = Array.from(document.querySelectorAll(skipTags.join(','))); // 按元素DOM深度从大到小排序,子元素优先处理 elements.sort((a, b) => { const depthA = getElementDepth(a); const depthB = getElementDepth(b); return depthB - depthA; }); elements.forEach(el => { const index = domStore.save(el.outerHTML); const placeholder = document.createElement('span'); placeholder.className = 'translate-placeholder'; placeholder.dataset.index = index; el.replaceWith(placeholder); }); } function body_dom_add() { const placeholders = document.querySelectorAll('.translate-placeholder'); placeholders.forEach(placeholder => { const index = placeholder.dataset.index; const originalHtml = domStore.get(index); const tempDiv = document.createElement('div'); tempDiv.innerHTML = originalHtml; const originalEl = tempDiv.firstElementChild; placeholder.replaceWith(originalEl); }); domStore.clear(); // 还原后清空存储 } // 辅助函数:计算元素的DOM深度 function getElementDepth(el) { let depth = 0; while (el.parentElement) { depth++; el = el.parentElement; } return depth; }
测试验证
测试代码:
// 保存初始HTML用于对比 const initialHtml = ` <div class="content"> <p>需要翻译的文本</p> <pre> <code>const test = "这段代码不需要翻译"</code> </pre> <img src="test.jpg" alt="图片不需要翻译"> </div> `; document.body.innerHTML = initialHtml; console.log('初始目标DOM数量:', document.querySelectorAll('pre,code,img').length); // 输出3 // 执行替换 body_dom_del(); console.log('替换后占位符数量:', document.querySelectorAll('.translate-placeholder').length); // 输出3 // 执行还原 body_dom_add(); console.log('还原后目标DOM数量:', document.querySelectorAll('pre,code,img').length); // 输出3 console.log('还原后HTML与初始一致:', document.body.innerHTML.trim() === initialHtml.trim()); // 输出true
运行测试后,替换和还原过程中占位符数量与目标DOM完全匹配,最终HTML与初始状态一致,解决了嵌套DOM的替换与还原问题。
内容的提问来源于stack exchange,提问作者dong
相关产品推荐
相关产品推荐

