考虑下面的HTML示例,我需要突出显示一个搜索文本192.168.1.1。这只是一个例子,文本搜索和HTML结构完全是任意的,因为它将运行在最终用户的网站上和他们的输入。程序还将对插入的span执行额外的操作,不只是突出显示(例如,在悬停时显示工具提示,但只要插入了span,这就不是问题)。
编辑:我刚刚发现一个新的行将导致空间之间的textContent将不再正确。不过,为了更好的视觉效果,我会把它们保存在那里。您可以检查下面的片段以获得正确的HTML代码。
<div>
<span class="foo-1">The computer address</span>
<em>is</em>
<span class="ip-1">192</span>.
<span class="ip-2">168</span>.
<span class="ip-3">1</span>.
<span class="ip-4">1</span>
</div>使用textContent或innerText,很容易找到与我需要的子文本最接近的元素(示例中的div有完整的192.168.1.1文本)。但是,下一步,我想在不破坏原始结构或其他HTML/CSS class或属性的情况下包装这些部件。理想情况下,我希望最终的HTML是:
<div>
<span class="foo-1">The computer address</span>
<em>is</em>
<span class="my-highlight-span"><span class="ip-1">192</span>.
<span class="ip-2">168</span>.
<span class="ip-3">1</span>.
<span class="ip-4">1</span></span>
</div>使用深度优先搜索,我可以很容易地到达div,但我不知道如何从那里开始。还有一个棘手的案例:
<div>
<span class="foo-1">The computer address</span>
<em>is
<span class="ip-1">192</span>.</em>
<span class="ip-2">168</span>.
<span class="ip-3">1</span>.
<span class="ip-4">1</span>
</div>我认为,如果有解决办法,这应该是实现这一目标的最简单方法:
<div>
<span class="foo-1">The computer address</span>
<em>is
<span class="my-highlight-span"><span class="ip-1">192</span>.</span></em><span class="my-highlight-span">
<span class="ip-2">168</span>.
<span class="ip-3">1</span>.
<span class="ip-4">1</span></span>
</div>如果相关,代码将在Chrome扩展环境中执行,但我认为这应该是一个纯粹的浏览器Javascript问题。
这里有一个片段,您可以尝试一下:
function transform(el, text) {
// Transform this Element to highlight text
}
const text = "192.168.1.1";
transform(document.querySelector("#case-1"), text);
transform(document.querySelector("#case-2"), text);
transform(document.querySelector("#case-simple"), text);.my-highlight-span {
background-color: cornflowerblue;
}<p>Case 1:</p>
<div id="case-1">
<span class="foo-1">The computer address</span>
<em>is</em>
<span class="ip-1">192</span>.<span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span>
</div>
<p>Case 2:</p>
<div id="case-2">
<span class="foo-1">The computer address</span>
<em>is
<span class="ip-1">192</span>.</em><span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span>
</div>
<p>Simple case:</p>
<div id="case-simple">
The computer address is 192.168.1.1
</div>
<hr />
<p>Desired Result:</p>
<div id="case-1-result">
<span class="foo-1">The computer address</span>
<em>is</em>
<span class="my-highlight-span"><span class="ip-1">192</span>.<span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span></span>
</div>
<div id="case-2-result">
<span class="foo-1">The computer address</span>
<em>is
<span class="my-highlight-span"><span class="ip-1">192</span>.</span></em><span class="my-highlight-span">
<span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span></span>
</div>
<div id="case-simple-result">
The computer address is <span class="my-highlight-span">192.168.1.1</span>
</div>
发布于 2022-01-21 18:38:08
我终于得到了使用API接口的答案,这归功于令人敬畏的解释这里和Range.surroundContents()的微妙解决方案。
scanForText方法,我递归地扫描textContent of TEXT_NODEs,以确定搜索项的开始和结束。Range API来选择和提取内容,并在span包围它之后将其插入。显然有一些限制,但这在大多数情况下是可行的。例如,在第2种情况下,我的代码将在<em>中生成一个额外的空<em>。
function scanForText(el, expectingStart, expectingEnd, result) {
if (el.nodeType === Node.TEXT_NODE) {
const nodeContent = el.textContent;
result[4] += nodeContent;
const currText = result[4];
if (expectingStart < currText.length && !result[0]) {
result[0] = el;
result[1] = expectingStart - (currText.length - nodeContent.length);
}
if (expectingEnd <= currText.length && !result[2]) {
result[2] = el;
result[3] = expectingEnd - (currText.length - nodeContent.length);
return;
}
} else {
for (let childEl of el.childNodes) {
scanForText(childEl, expectingStart, expectingEnd, result);
if (result[2]) { // When already found ending, return
return;
}
}
}
}
function transform(el, text) {
const fullText = el.textContent;
const startIndex = fullText.indexOf(text);
const endIndex = startIndex + text.length;
const scanState = [null, -1, null, -1, ""];
scanForText(el, startIndex, endIndex, scanState);
const [startNode, startRangeIndex, endNode, endRangeIndex] = scanState;
if (!startNode || !endNode) {
console.warn("This should not be happening");
return;
}
const range = new Range();
range.setStart(startNode, startRangeIndex);
range.setEnd(endNode, endRangeIndex);
const surrounding = document.createElement("span");
surrounding.className = "my-highlight-span";
// This can't be used when cutting one text boundary.
// Alternative is offered at https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents
// range.surroundContents(surrounding)
const extracted = range.extractContents();
surrounding.appendChild(extracted);
range.insertNode(surrounding);
}
const text = "192.168.1.1";
transform(document.querySelector("#case-1"), text);
transform(document.querySelector("#case-2"), text);
transform(document.querySelector("#case-simple"), text);.my-highlight-span {
background-color: cornflowerblue;
}<p>Case 1:</p>
<div id="case-1">
<span class="foo-1">The computer address</span>
<em>is</em>
<span class="ip-1">192</span>.<span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span>
</div>
<p>Case 2:</p>
<div id="case-2">
<span class="foo-1">The computer address</span>
<em>is
<span class="ip-1">192</span>.</em><span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span>
</div>
<p>Simple case:</p>
<div id="case-simple">
The computer address is 192.168.1.1
</div>
<hr />
<p>Desired Result:</p>
<div id="case-1-result">
<span class="foo-1">The computer address</span>
<em>is</em>
<span class="my-highlight-span"><span class="ip-1">192</span>.<span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span></span>
</div>
<div id="case-2-result">
<span class="foo-1">The computer address</span>
<em>is
<span class="my-highlight-span"><span class="ip-1">192</span>.</span></em><span class="my-highlight-span">
<span class="ip-2">168</span>.<span class="ip-3">1</span>.<span class="ip-4">1</span></span>
</div>
<div id="case-simple-result">
The computer address is <span class="my-highlight-span">192.168.1.1</span>
</div>
https://stackoverflow.com/questions/70802881
复制相似问题