You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript文本截断功能异常,寻求问题排查与修复方案

问题原因分析及解决方案

问题原因

  1. 标题文本占用词数额度:原代码会遍历所有子节点并计数所有文本内容,包括<h1>-<h6>标题内的文字,导致标题的词数直接占用data-limit的限制额度,正文还未显示足够内容就触发截断。
  2. 空白字符被错误计数:使用split(/\s+/)分割文本节点时,连续空格、换行等空白会生成空字符串,这些空字符串被当成有效词计数,导致实际有效文本还没达到限制就提前截断。
  3. 内联标签截断逻辑不完善:当在<strong>、<a>等内联标签内的文本达到词数限制时,省略号的插入位置可能破坏标签语义,且无法保证标签完整保留。

解决方案代码

JavaScript

document.addEventListener("DOMContentLoaded", function() {
  // 定义不需要截断/不计入词数的标签
  const skipCountTags = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
  // 定义需要完整保留的内联标签(不允许在标签内部截断)
  const preserveTags = new Set(['strong', 'b', 'a', 'em', 'i']);

  function shortenText(element, limit) {
    const originalContent = element.innerHTML;
    const nodes = Array.from(element.childNodes);
    let wordCount = 0;
    let truncatedContent = '';
    let isTruncated = false;

    function traverseNodes(node) {
      if (isTruncated) return;

      if (node.nodeType === Node.TEXT_NODE) {
        if (wordCount >= limit) {
          isTruncated = true;
          return;
        }

        const text = node.nodeValue.trim();
        if (!text) return; // 跳过纯空白文本节点

        const words = text.split(/\s+/).filter(word => word.length > 0);
        const remainingWords = limit - wordCount;
        const takeWords = words.slice(0, remainingWords);
        
        truncatedContent += takeWords.join(' ') + ' ';
        wordCount += takeWords.length;

        // 达到限制则添加省略号并终止遍历
        if (wordCount >= limit) {
          truncatedContent += '...';
          isTruncated = true;
        }
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        const tagName = node.nodeName.toLowerCase();
        
        // 跳过已存在的"Read more"节点
        if (tagName === 'span' && node.classList.contains('more')) return;

        // 标题标签完整保留,不计入词数
        if (skipCountTags.has(tagName)) {
          truncatedContent += node.outerHTML;
          return;
        }

        // 开始构建标签结构
        truncatedContent += `<${tagName}`;
        Array.from(node.attributes).forEach(attr => {
          truncatedContent += ` ${attr.name}="${attr.value}"`;
        });
        truncatedContent += '>';

        // 处理需要完整保留的内联标签
        if (preserveTags.has(tagName)) {
          const tagText = node.textContent.trim();
          const tagWordCount = tagText.split(/\s+/).filter(w => w).length;
          
          if (wordCount + tagWordCount <= limit) {
            // 能完整容纳,直接添加标签内容
            truncatedContent += node.innerHTML;
            wordCount += tagWordCount;
          } else {
            // 无法完整容纳,直接截断并标记状态
            truncatedContent += '...';
            isTruncated = true;
          }
        } else {
          // 普通标签正常遍历子节点
          node.childNodes.forEach(childNode => traverseNodes(childNode));
        }

        // 未截断时才闭合标签,避免结构异常
        if (!isTruncated) {
          truncatedContent += `</${tagName}>`;
        }
      }
    }

    for (let childNode of nodes) {
      if (!isTruncated) {
        traverseNodes(childNode);
      }
    }

    // 仅在内容被截断时添加展开链接
    if (isTruncated) {
      element.innerHTML = truncatedContent.trim() + `<span class="more">Read more</span>`;
      element.dataset.originalContent = originalContent;
    }
  }

  function expandText(element) {
    element.innerHTML = element.dataset.originalContent;
    delete element.dataset.originalContent; // 清理缓存的原始内容
  }

  document.querySelectorAll('.shorten-text').forEach(el => {
    const limit = parseInt(el.dataset.limit) || 100;
    shortenText(el, limit);

    el.addEventListener('click', function(event) {
      if (event.target.classList.contains('more')) {
        expandText(el);
      }
    });
  });
});

CSS

* {
  font-family: Calibri, Arial;
  margin: 0px;
  padding: 25px;
  text-align: center;
}

.shorten-text {
  margin: 20px 0;
  padding: 10px;
  border: 1px solid #ccc;
  background-color: #f9f9f9;
}

.more {
  color: blue;
  cursor: pointer;
  text-decoration: underline;
}

HTML

<h1>Text Shortening Test</h1>
<div class="shorten-text" data-limit="10">
  <h1>A Headline!</h1>
  <div>A wonderful serenity has taken possession of my entire soul, <strong>like these sweet mornings</strong> of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.</div>
  <p>I am so happy, my dear friend, so absorbed in the <a href="#">exquisite</a> sense of mere tranquil existence, that I neglect my talents.</p>
</div>

<div class="shorten-text" data-limit="5">
  <h2>Another Header</h2>
  <div>This is a test paragraph to check the shortening function. It should only show a few words.</div>
  <p>This paragraph should be mostly hidden.</p>
</div>

<div class="shorten-text" data-limit="15">
  <h3>Yet Another Header</h3>
  <div>Testing the function with a longer limit to see how it handles more text. The goal is to ensure that the text is truncated properly without breaking the structure of the HTML content.</div>
</div>

关键修改说明

  1. 标题不计入词数:标题标签完整保留且不占用词数额度,确保正文能按设定限制显示足够内容。
  2. 过滤无效空白:只计数有效单词,避免空白字符错误消耗词数配额。
  3. 完整保留内联标签:<strong>、<a>等内联标签要么完整显示,要么直接截断,不会出现标签被拆分的情况。
  4. 精准截断控制:用isTruncated标记管理遍历状态,避免不必要的节点处理,同时保证HTML结构完整。
  5. 条件显示展开链接:只有内容确实被截断时才显示“Read more”,避免冗余元素。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:44:56