
本文介绍一种纯 JavaScript 实现的安全字符串截断方法:在限定字符数(如 50 字符)的前提下,忽略 HTML 标签长度计数,优先保留完整语义(截断至最后一个句号),同时确保 HTML 标签成对闭合、不破坏 DOM 结构。
本文介绍一种纯 JavaScript 实现的安全字符串截断方法:在限定字符数(如 50 字符)的前提下,忽略 HTML 标签长度计数,优先保留完整语义(截断至最后一个句号),同时确保 HTML 标签成对闭合、不破坏 DOM 结构。
在前端开发中,常需对富文本内容做摘要展示(如新闻列表、评论预览),但直接按字符截断 innerHTML 会导致 HTML 标签被意外截断,引发 DOM 结构错乱、样式丢失甚至 XSS 风险。更理想的做法是:仅统计可见文本长度,跳过所有 HTML 标签,并在满足字数限制的前提下,尽可能停在语义终点(如句号 .),最后补全未闭合的标签以保障 HTML 合法性。
以下提供一个健壮、无依赖的 JavaScript 函数 truncateHtmlText,完全满足上述需求:
/**
* 安全截断含 HTML 的字符串
* @param {string} html - 原始 HTML 字符串
* @param {number} maxLength - 最大可见字符数(不含标签)
* @returns {string} 截断后的合法 HTML 字符串
*/
function truncateHtmlText(html, maxLength) {
if (!html || typeof html !== 'string') return '';
let result = '';
let tempBuffer = ''; // 缓存当前待提交的文本段(不含标签)
let tagOpen = false; // 是否处于 HTML 标签内
let visibleCount = 0; // 已累计的可见字符数
// 第一遍:逐字符解析,提取可见文本并识别句号断点
for (let i = 0; i < html.length; i++) {
const ch = html[i];
if (ch === '<') {
tagOpen = true;
tempBuffer += ch;
} else if (ch === '>') {
tagOpen = false;
tempBuffer += ch;
} else if (tagOpen) {
tempBuffer += ch;
} else if (ch === '.' && visibleCount < maxLength) {
// 遇到句号且未超限 → 提交整段(含句号)到结果
result += tempBuffer + '.';
tempBuffer = '';
visibleCount++; // 句号计入可见字符
} else if (visibleCount < maxLength) {
tempBuffer += ch;
visibleCount++;
}
}
// 第二遍:处理剩余未提交的 tempBuffer(仅保留其中的 HTML 标签,丢弃多余文本)
// 目的是补全可能缺失的闭合标签,避免 DOM 不平衡
let tagStack = [];
let finalTagBuffer = '';
for (let i = 0; i < tempBuffer.length; i++) {
const ch = tempBuffer[i];
if (ch === '<') {
// 检测是否为自闭合标签(如 <br/>)或结束标签(</p>)
const nextTagEnd = tempBuffer.indexOf('>', i);
if (nextTagEnd === -1) continue;
const tagContent = tempBuffer.substring(i + 1, nextTagEnd).trim();
if (tagContent.startsWith('/')) {
// 结束标签:尝试匹配栈顶开始标签
const tagName = tagContent.slice(1).split(/\s+/)[0].toLowerCase();
if (tagStack.length && tagStack[tagStack.length - 1] === tagName) {
tagStack.pop();
}
} else if (tagContent.endsWith('/')) {
// 自闭合标签:不入栈
} else {
// 开始标签:提取标签名并入栈
const tagName = tagContent.split(/\s+/)[0].toLowerCase();
tagStack.push(tagName);
}
finalTagBuffer += tempBuffer.substring(i, nextTagEnd + 1);
i = nextTagEnd;
}
}
// 补充缺失的结束标签(逆序闭合)
while (tagStack.length > 0) {
const tagName = tagStack.pop();
finalTagBuffer += `</${tagName}>`;
}
return result + finalTagBuffer;
}✅ 使用示例:
立即学习“前端免费学习笔记(深入)”;
const html = `<p>This is a test message</p>This is a test message.<p>This is a test <a href="#">message.</a> </p>`; console.log(truncateHtmlText(html, 50)); // 输出:"<p>This is a test message</p>This is a test message."
⚠️ 注意事项与最佳实践:
- 不依赖 jQuery:本方案为原生 JavaScript 实现,轻量、兼容性好(支持 ES6+ 环境),无需引入额外库;
- 标签完整性保障:通过模拟标签栈(tagStack)自动补全未闭合的 HTML 元素,避免渲染异常;
- 句号优先逻辑:仅当句号出现在 maxLength 范围内时才作为截断点;若无句号,则退化为普通字符截断;
- 规避潜在风险:函数不执行 HTML 解析或 eval,不使用 innerHTML 动态插入,天然免疫 XSS(前提是输入本身已安全);
- 性能提示:适用于中短文本(
- 扩展建议:可轻松增强支持其他标点(如 !、?)、自定义“语义断点”正则,或添加省略号 … 标识截断。
该方案兼顾准确性、安全性与实用性,是服务端摘要不可用时,前端动态生成内容预览的可靠选择。











