应直接操作文本节点而非innerHTML,因后者会重绘DOM导致节点引用丢失、结构损坏、高亮错位及事件失效;需用TreeWalker遍历并过滤script/style/textarea等危险区域,仅处理可见纯文本。

直接操作文本节点,别碰 innerHTML —— 否则错题位置会偏移、事件丢失、甚至标签被截断。
为什么用 innerHTML.replace() 会标错位置
试卷 HTML 里常混着题干、选项、script 和 textarea,如果对整个容器的 innerHTML 做正则替换,关键词可能落在标签属性里(比如 data-answer="B")、或被 <b></b> 包裹一半(如“选项A”中搜“项A”,结果变成“选项A”),导致 DOM 结构损坏、高亮错位、点击失效。
- 浏览器解析时把
innerHTML当作字符串重绘,原始节点引用全部丢失 -
textarea和script内容也会被误匹配,破坏逻辑或暴露敏感值 - 中文题干中“服务”匹配到“服务器”“微服务”,必须加词边界限定
只遍历文本节点 + 过滤危险区域
用 TreeWalker 或递归 childNodes,跳过非文本节点和禁用区域,确保只改“人眼看到的文字”:
function highlightMistakes(container, keywords) {
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
// 跳过 script/style/textarea 的文本子节点
const parent = node.parentElement;
if (parent && ['SCRIPT', 'STYLE', 'TEXTAREA'].includes(parent.tagName)) {
return NodeFilter.FILTER_REJECT;
}
return node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
}
);
let node;
while (node = walker.nextNode()) {
const text = node.textContent;
keywords.forEach(keyword => {
const escaped = keyword.replace(/[.*+?^${}()|[]\]/g, '\$&');
const reg = new RegExp(`(?<=[\s\u4e00-\u9fa5]|^)${escaped}(?=[\s\u4e00-\u9fa5]|$)`, 'gi');
if (reg.test(text)) {
const span = document.createElement('span');
span.className = 'error-highlight';
span.innerHTML = text.replace(reg, '<mark class="error">$&</mark>');
node.parentNode.replaceChild(span, node);
}
});
}
}
-
TreeWalker比递归更稳,自动跳过注释、CDATA 等干扰节点 -
acceptNode显式过滤SCRIPT/STYLE/TEXTAREA,避免执行逻辑被污染 -
escaped对关键词做正则转义,防止用户输入1+1导致语法错误
高亮后焦点和选区不崩溃的关键动作
学生做完题点“批改”,页面要滚动到第一处错题并聚焦——但 replaceChild 会让原有文本节点消失,getSelection() 或 scrollIntoView() 找不到目标。
立即学习“前端免费学习笔记(深入)”;
- 高亮前用
container.querySelectorAll('mark.error')记录所有错题mark元素 - 高亮完成后,调用
marks[0]?.scrollIntoView({ block: 'center' }),再marks[0]?.focus() - 如果试卷含富文本编辑区(如
contenteditable),需额外调用container.normalize()合并相邻文本节点,否则mark可能被拆成两段
CSS 必须强覆盖默认样式
mark 默认背景是浅黄,但在试卷里不够醒目;若父容器有 color 或 background 继承,容易被盖住。
.error-highlight mark.error {
background: #ffebee;
color: #c62828;
padding: 2px 4px;
border-radius: 3px;
font-weight: bold;
text-decoration: none;
}
- 加
!important不推荐,优先用更高权重选择器(如#exam-container .error-highlight mark) - 避免用
z-index,试卷通常无层叠上下文,加了反而引发遮挡 - 移动端需补
-webkit-text-stroke: 0.5px防字体模糊
真正难的是动态题——JS 渲染的题目、Vue/React 模板、带条件分支的判断题。这类场景下,高亮必须在框架更新 DOM 后的 nextTick 或 useEffect 里触发,否则 TreeWalker 扫不到新节点。别信“一次写完就跑通”,先确认渲染时机,再动高亮逻辑。



















