You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

HTML字符串对比及差异高亮技术需求问询

刚好之前做过类似的文本对比高亮需求,给你分享一下完整的实现方案,完全适配带HTML标签的输入场景:

核心思路

我们需要同时处理HTML标签保留文本差异高亮两个核心点:

  • 原文展示框:把编辑后文本中消失的内容,用红色+删除线标记
  • 编辑后展示框:把原文中没有的新增内容,用黄色背景标记
    关键是不能破坏原有的HTML结构(比如strongolli这些标签要保留),所以不能直接对比原始HTML字符串,得先解析DOM节点,针对文本内容做差异对比,再重新构建带高亮样式的HTML。
实现步骤
    1. 解析HTML结构:把输入的HTML字符串转换成DOM节点,分离出文本片段和对应的标签结构,避免直接对比纯文本丢失原有格式
    1. 计算文本差异:用成熟的文本差异算法(比如Google的diff-match-patch)找出两个文本间的删除、新增、相同片段
    1. 添加高亮样式
    • 原文框:给删除的片段包裹红色高亮的<span>标签
    • 编辑后框:给新增的片段包裹黄色高亮的<span>标签
    1. 渲染结果:把处理后的HTML分别插入到两个展示框中
代码示例(JavaScript前端实现)

这里用Google开源的diff-match-patch库来处理文本差异,它对中英文的支持都很稳定,也可以自己实现简单的diff逻辑,但第三方库更省心:

// 初始化diff-match-patch(可以通过npm安装或CDN引入:https://cdn.jsdelivr.net/npm/diff-match-patch@1.0.5/dist/diff_match_patch.min.js)
const dmp = new diff_match_patch();

// 处理原文框:标红删除的内容
function getHighlightedOriginal(originalHtml, editedHtml) {
  // 解析HTML,提取文本和保留DOM结构(这里简化为遍历文本节点,实际项目可以优化为更精细的节点对比)
  const originalDoc = new DOMParser().parseFromString(originalHtml, 'text/html');
  const editedText = new DOMParser().parseFromString(editedHtml, 'text/html').body.textContent;
  
  // 遍历原文的所有文本节点,逐个对比
  const walker = document.createTreeWalker(originalDoc.body, NodeFilter.SHOW_TEXT, null, false);
  let currentNode;
  while (currentNode = walker.nextNode()) {
    const nodeText = currentNode.textContent;
    // 找出当前文本节点中在编辑后文本里消失的部分
    const diffs = dmp.diff_main(nodeText, editedText.slice(0, nodeText.length));
    dmp.diff_cleanupSemantic(diffs);
    
    // 构建带高亮的文本节点内容
    let newContent = '';
    for (const [type, text] of diffs) {
      if (type === -1) {
        // 删除内容:红+删除线
        newContent += `<span style="color: #dc3545; text-decoration: line-through;">${escapeHtml(text)}</span>`;
      } else {
        // 相同内容:直接保留
        newContent += escapeHtml(text);
      }
    }
    
    // 替换原文本节点为带HTML的span(注意要保留父标签结构)
    const span = document.createElement('span');
    span.innerHTML = newContent;
    currentNode.parentNode.replaceChild(span, currentNode);
  }
  
  return originalDoc.body.innerHTML;
}

// 处理编辑后框:标黄新增的内容
function getHighlightedEdited(originalHtml, editedHtml) {
  const originalText = new DOMParser().parseFromString(originalHtml, 'text/html').body.textContent;
  const editedDoc = new DOMParser().parseFromString(editedHtml, 'text/html');
  
  const walker = document.createTreeWalker(editedDoc.body, NodeFilter.SHOW_TEXT, null, false);
  let currentNode;
  while (currentNode = walker.nextNode()) {
    const nodeText = currentNode.textContent;
    const diffs = dmp.diff_main(originalText.slice(0, nodeText.length), nodeText);
    dmp.diff_cleanupSemantic(diffs);
    
    let newContent = '';
    for (const [type, text] of diffs) {
      if (type === 1) {
        // 新增内容:黄色背景
        newContent += `<span style="background-color: #fff3cd;">${escapeHtml(text)}</span>`;
      } else {
        // 相同内容:直接保留
        newContent += escapeHtml(text);
      }
    }
    
    const span = document.createElement('span');
    span.innerHTML = newContent;
    currentNode.parentNode.replaceChild(span, currentNode);
  }
  
  return editedDoc.body.innerHTML;
}

// 辅助函数:转义HTML特殊字符,避免XSS
function escapeHtml(text) {
  return text.replace(/[&<>"']/g, match => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  }[match]));
}

// 使用示例
const originalContent = '<p>Hello <strong>world</strong><ol><li>Item 1</li><li>Item 2</li></ol></p>';
const editedContent = '<p>Hello <strong>there</strong><ol><li>Item 1</li><li>Item 3</li></ol></p>';

// 渲染到页面的两个展示框
document.getElementById('original-display').innerHTML = getHighlightedOriginal(originalContent, editedContent);
document.getElementById('edited-display').innerHTML = getHighlightedEdited(originalContent, editedContent);
关键注意事项
  • DOM节点解析:上面的示例是遍历文本节点做对比,如果你需要更精细的标签级对比(比如标签本身的修改),可以扩展逻辑对比元素节点的属性和结构
  • 样式自定义:可以把高亮样式写成CSS类(比如.deleted { color: #dc3545; text-decoration: line-through; }),然后给span加类名,比内联样式更易维护
  • 性能优化:如果处理大段HTML,建议分批处理节点,避免页面卡顿
  • 兼容性DOMParserTreeWalker是现代浏览器都支持的API,IE浏览器需要做兼容处理(不过现在IE基本被淘汰了)

内容的提问来源于stack exchange,提问作者Pranav Kumar

火山引擎 最新活动