匹配高亮时函数无法正确显示HTML标签格式的问题求助
字符串匹配高亮导致HTML格式破坏的解决方法
问题描述
我尝试实现字符串匹配高亮功能,但开启高亮代码element.innerHTML = highlightedText;后,div#results区域内的UL/LI原有格式无法正常显示;注释掉该代码后,文本显示正常但没有高亮效果。求解决思路。
原代码参考
JavaScript代码
function wrapMatchesInSpan(element, searchTerm) { var text = element.textContent; var regex = new RegExp(searchTerm, 'gi'); var matches = text.match(regex); if (matches && matches.length > 0) { var highlightedText = text.replace(regex, function(match) { return '<span class="highlight">' + match + '</span>'; }); element.innerHTML = highlightedText; //if I comment this line, then the text works but, without format } } function search() { var searchTerm = document.getElementById("searchInput").value.toLowerCase(); var listItems = document.getElementById("searchResults").getElementsByTagName("li"); var teamItems = document.getElementById("searchTeams").getElementsByTagName("li"); var resultsDiv = document.getElementById("results"); for (var i = 0; i < listItems.length; i++) { var listItem = listItems[i]; var text = listItem.textContent.toLowerCase(); if (text.includes(searchTerm)) { listItem.innerHTML = listItem.innerHTML.replace(new RegExp(searchTerm, 'gi'), function(match) { return '<span class="highlight">' + match + '</span>'; }); listItem.style.display = "list-item"; } else { listItem.innerHTML = ''; // Clear the content listItem.style.display = "none"; } } var hideAllTeams = true; for (var j = 0; j < teamItems.length; j++) { var teamItem = teamItems[j]; var teamText = teamItem.textContent.toLowerCase(); if (teamText.includes(searchTerm)) { teamItem.style.display = "list-item"; hideAllTeams = false; } else { teamItem.style.display = "none"; } } if (searchTerm === "") { resultsDiv.style.display = "none"; } else { resultsDiv.style.display = "block"; } if (hideAllTeams) { teamsDiv.style.display = "none"; } else { teamsDiv.style.display = "block"; } }
CSS代码
* { box-sizing: border-box; } #results { display: none; margin-top: 10px; } #searchResults { background-color: #f6f6f6; margin-top: -2px; width: 430px; height: 200px; border: 1px solid #ddd; } #searchInput { background-image: url('/css/searchicon.png'); background-position: 10px 12px; background-repeat: no-repeat; width: 425px; font-size: 14px; padding: 12px 20px 12px 40px; border: 1px solid #ddd; margin-bottom: 12px; } #searchTeams { list-style-type: none; width: 425px; font-size: 16px; border: 1px solid #ddd; } #searchTeams li a { color: 1px solid #ddd; margin-top: -2px; /* Prevent double borders */ background-color: #f6f6f6; background-position: 10px 12px; font-weight: bold; padding: 12px; text-decoration: none; font-size: 19px; color: black; display: block } #teams { color: 1px solid #ddd; margin-top: -2px; /* Prevent double borders */ padding: 12px; text-decoration: none; } #searchTeams li a:hover:not(.header) { background-color: #aeadca; width: 100%; padding-left: 35px; color: blue; } ul.scroll { background-color: #f6f6f6; width: 600px; height: 500px; overflow-x: hidden; overflow-y: auto; text-align: left; padding: 20px; } .highlight { background-color: yellow; }
HTML代码
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>NACC-Teams-Locator</title> </head> <body> <div id="teams"> <h2>ACC Teams:</h2> <input type="text" id="searchInput" placeholder="Search for teams / keywords / services..." oninput="search()"> <ul class="scroll" id="searchTeams"> <li style="display: list-item;"><a href="#">WMAA</a></li> <li style="display: list-item;"><a href="#">MobileInf</a></li> <li style="display: list-item;"><a href="#">MSM</a></li> <li style="display: list-item;"><a href="#">SECAPPS</a></li> <li style="display: list-item;"><a href="#">SECAPPS</a></li> </ul> <div id="results"> <h3>Services supported by team:</h3> <ul id="searchResults" class="scroll"> <li style="display: none;"> <h3>MobileInf:</h3><br> -APIs<br> -Layer 7<br> -Gloo<br> -Gateways<br> -API portal</li> <li style="display: none;"> <h3>Midrange Servers Management (MSM):</h3> -Servers<br> -Filesystem usage<br> -Patching<br> -Root<br> -API portal</li> <li style="display: none;"> <h3>SECAPPS:</h3> -Okta<br> -Passwordsafe<br> -OIG<br> -Forgerock<br> -Evidian <br> -Cyberark<br> -go/password</li> <li style="display: none;"> <h3>Hadoop:</h3> -Hive<br> -Cloudera<br> -Hue/Ambari<br> -Yarn/HDFS<br> -Hadoop Data Movement</b> </li> </ul> </div> </div> <p> //<img src="" /> </p> </body> </html>
问题根源
- 直接替换
innerHTML会破坏原有HTML结构(比如LI内的<h3>标签会被当成文本处理,或重复替换导致嵌套<span>) - 每次搜索未重置之前的高亮状态,导致多次搜索后HTML结构混乱
- 代码中
teamsDiv未定义,会引发控制台错误
修复方案
1. 保存原始HTML内容
给需要高亮的列表项添加自定义属性data-original,存储初始HTML内容,每次搜索前先恢复原始状态。
2. 针对文本节点进行高亮替换
遍历元素的文本节点,只在纯文本中匹配搜索词并包裹高亮标签,避免修改现有HTML标签结构。
3. 修复未定义变量
提前获取teamsDiv DOM元素,避免报错。
修改后的完整代码
JavaScript代码
// 仅在文本节点中包裹匹配的搜索词,不破坏HTML结构 function highlightTextNodes(element, searchTerm) { if (!searchTerm.trim()) return; const regex = new RegExp(searchTerm, 'gi'); const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false); let node; while (node = walker.nextNode()) { if (node.parentElement.classList.contains('highlight')) continue; // 跳过已高亮的节点 const text = node.textContent; const matches = text.match(regex); if (matches) { const fragments = text.split(regex); const parent = node.parentElement; parent.removeChild(node); fragments.forEach((fragment, index) => { if (fragment) parent.appendChild(document.createTextNode(fragment)); if (index < matches.length) { const span = document.createElement('span'); span.className = 'highlight'; span.textContent = matches[index]; parent.appendChild(span); } }); } } } function search() { const searchTerm = document.getElementById("searchInput").value.toLowerCase().trim(); const listItems = document.getElementById("searchResults").getElementsByTagName("li"); const teamItems = document.getElementById("searchTeams").getElementsByTagName("li"); const resultsDiv = document.getElementById("results"); const teamsDiv = document.getElementById("teams"); // 修复未定义变量 // 重置服务列表项的状态 for (let i = 0; i < listItems.length; i++) { const listItem = listItems[i]; // 恢复原始HTML内容 listItem.innerHTML = listItem.dataset.original || listItem.innerHTML; const text = listItem.textContent.toLowerCase(); if (searchTerm && text.includes(searchTerm)) { highlightTextNodes(listItem, searchTerm); listItem.style.display = "list-item"; } else { listItem.style.display = "none"; } } // 处理团队列表项 let hideAllTeams = true; for (let j = 0; j < teamItems.length; j++) { const teamItem = teamItems[j]; const link = teamItem.querySelector('a'); // 恢复原始文本 link.textContent = link.dataset.original || link.textContent; const teamText = teamItem.textContent.toLowerCase(); if (searchTerm && teamText.includes(searchTerm)) { highlightTextNodes(link, searchTerm); teamItem.style.display = "list-item"; hideAllTeams = false; } else { teamItem.style.display = "none"; } } // 控制结果区域显示 resultsDiv.style.display = searchTerm ? "block" : "none"; teamsDiv.style.display = hideAllTeams && searchTerm ? "none" : "block"; } // 页面加载时保存原始内容 document.addEventListener('DOMContentLoaded', function() { // 保存服务列表的原始HTML const serviceItems = document.getElementById("searchResults").getElementsByTagName("li"); for (let item of serviceItems) { item.dataset.original = item.innerHTML; } // 保存团队链接的原始文本 const teamLinks = document.querySelectorAll("#searchTeams li a"); for (let link of teamLinks) { link.dataset.original = link.textContent; } });
CSS代码(无修改)
* { box-sizing: border-box; } #results { display: none; margin-top: 10px; } #searchResults { background-color: #f6f6f6; margin-top: -2px; width: 430px; height: 200px; border: 1px solid #ddd; } #searchInput { background-image: url('/css/searchicon.png'); background-position: 10px 12px; background-repeat: no-repeat; width: 425px; font-size: 14px; padding: 12px 20px 12px 40px; border: 1px solid #ddd; margin-bottom: 12px; } #searchTeams { list-style-type: none; width: 425px; font-size: 16px; border: 1px solid #ddd; } #searchTeams li a { color: 1px solid #ddd; margin-top: -2px; /* Prevent double borders */ background-color: #f6f6f6; background-position: 10px 12px; font-weight: bold; padding: 12px; text-decoration: none; font-size: 19px; color: black; display: block } #teams { color: 1px solid #ddd; margin-top: -2px; /* Prevent double borders */ padding: 12px; text-decoration: none; } #searchTeams li a:hover:not(.header) { background-color: #aeadca; width: 100%; padding-left: 35px; color: blue; } ul.scroll { background-color: #f6f6f6; width: 600px; height: 500px; overflow-x: hidden; overflow-y: auto; text-align: left; padding: 20px; } .highlight { background-color: yellow; }
HTML代码(无修改)
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>NACC-Teams-Locator</title> </head> <body> <div id="teams"> <h2>ACC Teams:</h2> <input type="text" id="searchInput" placeholder="Search for teams / keywords / services..." oninput="search()"> <ul class="scroll" id="searchTeams"> <li style="display: list-item;"><a href="#">WMAA</a></li> <li style="display: list-item;"><a href="#">MobileInf</a></li> <li style="display: list-item;"><a href="#">MSM</a></li> <li style="display: list-item;"><a href="#">SECAPPS</a></li> <li style="display: list-item;"><a href="#">SECAPPS</a></li> </ul> <div id="results"> <h3>Services supported by team:</h3> <ul id="searchResults" class="scroll"> <li style="display: none;"> <h3>MobileInf:</h3><br> -APIs<br> -Layer 7<br> -Gloo<br> -Gateways<br> -API portal</li> <li style="display: none;"> <h3>Midrange Servers Management (MSM):</h3> -Servers<br> -Filesystem usage<br> -Patching<br> -Root<br> -API portal</li> <li style="display: none;"> <h3>SECAPPS:</h3> -Okta<br> -Passwordsafe<br> -OIG<br> -Forgerock<br> -Evidian <br> -Cyberark<br> -go/password</li> <li style="display: none;"> <h3>Hadoop:</h3> -Hive<br> -Cloudera<br> -Hue/Ambari<br> -Yarn/HDFS<br> -Hadoop Data Movement</b> </li> </ul> </div> </div> <p> //<img src="" /> </p> </body> </html>
内容的提问来源于stack exchange,提问作者Oswaldo Barajas
相关产品推荐
相关产品推荐

