
本文详解如何在保留 html 结构的前提下,精准按词数截断文本,并修复因空字符串导致的过早截断问题,确保“read more”展开功能稳定可靠。
本文详解如何在保留 html 结构的前提下,精准按词数截断文本,并修复因空字符串导致的过早截断问题,确保“read more”展开功能稳定可靠。
在实现基于词数的 HTML 内容截断时,一个隐蔽但关键的问题常被忽略:textContent.split(/\s+/) 会产生大量空字符串(如换行、缩进、前后空白导致的 "")。这些空字符串虽无实际语义,却被错误计入 wordCount,导致截断提前发生——例如本应显示 10 个单词,却在第 3 个有效词后就插入 ...,尤其在包含 <h1></h1>、<div> 等块级元素且内部存在缩进或换行的 HTML 中尤为明显。<h3>✅ 核心修复:跳过空字符串</h3>
<p>原逻辑中:</p>
<pre class="brush:php;toolbar:false;">const words = node.nodeValue.split(/\s+/);
for (let i = 0; i <p>✅ 正确写法需增加非空校验:</p><pre class="brush:php;toolbar:false;">for (let i = 0; i <blockquote><p>? 提示:<code>split(/\s+/)</code> 在遇到连续空白(如 <code>\n\t</code>)时会生成 <code>["", "", "hello"]</code>,而 <code>words[i]</code> 为 <code>""</code> 时为 falsy,<code>words[i] && ...</code> 可安全跳过。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill9868" title="Java注释规范Skills"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="Java注释规范Skills" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill9868" title="Java注释规范Skills" class="overflowclass">Java注释规范Skills</a>
<p class="overflowclass">当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。</p>
</div>
<a rel="nofollow" href="/xiazai/skill9868" title="Java注释规范Skills" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div></blockquote><h3>? 完整健壮实现(含结构保护)</h3><p>以下为优化后的生产就绪版本,已集成空字符串过滤、HTML 标签完整性保障及事件委托优化:</p><pre class="brush:php;toolbar:false;">document.addEventListener("DOMContentLoaded", function() {
function shortenText(element, limit) {
const originalContent = element.innerHTML;
const nodes = Array.from(element.childNodes);
let wordCount = 0;
let truncatedContent = '';
function traverseNodes(node) {
if (wordCount >= limit) return;
if (node.nodeType === Node.TEXT_NODE) {
const text = node.nodeValue.trim();
if (!text) return; // 忽略纯空白文本节点
const words = text.split(/\s+/).filter(w => w.length > 0); // 双重过滤:trim + filter
for (let i = 0; i {
// 对属性值做基础转义(防 XSS 基础防护)
const escapedValue = attr.value.replace(/"/g, '"');
truncatedContent += ` ${attr.name}="${escapedValue}"`;
});
truncatedContent += '>';
// 递归处理子节点
node.childNodes.forEach(traverseNodes);
truncatedContent += `${tagName}>`;
}
}
// 逐节点遍历,确保顺序与 DOM 一致
nodes.forEach(child => {
if (wordCount Read more';
element.dataset.originalContent = originalContent;
}
function expandText(element) {
element.innerHTML = element.dataset.originalContent;
}
// 使用事件委托优化性能(避免每个 .more 绑定独立 listener)
document.addEventListener('click', function(e) {
const moreEl = e.target.closest('.shorten-text .more');
if (moreEl) {
const container = moreEl.closest('.shorten-text');
if (container) expandText(container);
}
});
// 初始化所有截断容器
document.querySelectorAll('.shorten-text').forEach(el => {
const limit = parseInt(el.dataset.limit, 10) || 100;
shortenText(el, limit);
});
});</pre><h3>⚠️ 注意事项与最佳实践</h3></pre></pre>
<ul>
<li>
<strong>不要依赖 <code>innerHTML 做原始内容存储:若内容含用户输入,需服务端净化;前端可改用 dataset 存储序列化文本(如 JSON.stringify({html: ...})),但本例为简化保留 innerHTML。
.more 使用 cursor: pointer 和 text-decoration: underline 已满足基本可访问性,建议补充 role="button" 和键盘支持(如 Enter 触发)以符合 WCAG。shortenText 时,可缓存 originalContent 并使用 DocumentFragment 批量操作 DOM,减少重排。traverseNodes 抽离为可配置策略函数。通过严格过滤空字符串、保留标签嵌套结构、并采用事件委托优化交互,该方案可稳定支撑多层级 HTML 内容的词数截断需求,彻底解决“截断过早”与“破坏 DOM 结构”的典型问题。










