文本过长时中间部分用省略号表示。

本文介绍了一种使用JavaScript实现的字符串截取方法,该方法能够从字符串的前后两端分别截取部分内容,并在中间插入省略号,以达到在不丢失关键信息的前提下展示长字符串的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:
https://blog.youkuaiyun.com/baidu_38027860/article/details/81035157

//截取字符串中间用省略号显示
function getSubStr (str){
var subStr1 = str.substr(0,10);
var subStr2 = str.substr(str.length-5,5);
var subStr = subStr1 + "..." + subStr2 ;
return subStr;
}

var str = "12312312asdasdxzczxaweqwdsadasdczxcasd";

getSubStr(str);    

//直接调用即可
<think>我们将在JavaScript中实现一个函数,用于将文本截断为最多两行,并在第二行末尾添加省略号(...)。由于在纯JavaScript中(没有DOM环境)无法直接获取文本的渲染行数,我们需要一个测量文本宽度的函数。这里我们使用Canvas的measureText方法来实现。 步骤: 1. 创建一个离屏Canvas用于测量文本宽度。 2. 设置与目标样式相同的字体(字体大小、字体系列等)。 3. 定义一个函数,该函数接收文本、最大宽度(每行的宽度)和最大行数(这里为2)作为参数。 4. 将文本按单词(或字符)分割,然后逐词(或逐字符)添加到当前行,并测量当前行的宽度。 5. 如果当前行宽度超过最大宽度,则将该词放入下一行。 6. 当达到最大行数,对最后一行进行截断,确保加上省略号后不超过最大宽度。 注意:我们这里按单词分割,但中文可能需要按字符分割(因为中文没有空格)。因此,我们可以根据文本内容选择分割方式,或者统一按字符分割。 由于用户需求是两行,所以最大行数设为2。 实现代码: 我们将创建一个函数`truncateText`,它接受以下参数: - `text`: 原始文本 - `maxWidth`: 每行的最大宽度(像素) - `font`: 字体样式(例如:"16px Arial") 返回截断后的文本(两行,行之间用换行符`\n`连接,第二行末尾有省略号)。 注意:如果文本不足两行,则返回原文本(不需要换行和省略号)。 具体步骤: 1. 创建离屏Canvas(如果尚未创建)并获取2D上下文。 2. 设置上下文字体。 3. 将文本分割成数组(英文按空格,中文按字符?)。为了通用性,我们可以按字符分割(包括空格),因为英文单词内部不能断开,但为了简单起见,我们这里按空格和字符混合:先尝试按空格分割成单词,然后对每个单词再按需要分割(如果单词本身超过行宽)。但为了简化,我们直接按字符分割(包括空格),这样处理更简单,但可能会在单词中间断开。或者,我们可以先按单词,如果单词太长(超过行宽),则再按字符分割。 这里我们采用按字符分割,因为这样处理简单且适用于中英文。 步骤: - 初始化变量:当前行内容为空,行数初始为1,结果行数组为空。 - 遍历每个字符: currentLine + 当前字符,然后测量宽度。 - 如果宽度小于maxWidth,则继续。 - 否则,说明当前行已满,那么: 将当前行(不包含当前字符)加入结果数组,行数+1。 如果行数已经达到最大行数(2),则开始处理最后一行(加上当前字符,然后截断并加省略号)。 否则,新的一行从当前字符开始。 - 如果遍历结束,行数不足2行,则返回所有行(不需要省略号)。 但是,这样处理可能不够高效,而且最后一行需要单独处理(因为要加省略号)。 另一种方案(更高效): - 先处理第一行:不断添加字符直到超过宽度,然后回退到最后一个完整的单词(如果有)或者字符,作为第一行。 - 然后处理第二行,同样添加字符直到超过宽度,然后回退并加上省略号。 但是,由于我们按字符处理,不考虑单词,所以可能会在单词中间截断。这符合中文习惯,英文可能不太友好。 为了兼顾英文,我们可以这样: - 按单词分割,然后逐个单词添加,如果添加一个单词后超过宽度,则回退到上一个单词,并换行。但是,如果一个单词很长(超过一行),则需要按字符分割这个单词。 考虑到复杂度,我们这里先实现按字符分割的版本,因为它更通用,但可能会在单词中间截断。 我们也可以提供一个选项来选择是否按单词分割,但用户需求中没有明确,我们先按字符分割实现。 实现(按字符分割): ```javascript function truncateText(text, maxWidth, font) { // 创建离屏canvas用于测量 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.font = font; // 设置字体 const ellipsis = '...'; const ellipsisWidth = ctx.measureText(ellipsis).width; let lines = []; // 存储每一行的文本 let currentLine = ''; for (let i = 0; i < text.length; i++) { const char = text[i]; const testLine = currentLine + char; const testWidth = ctx.measureText(testLine).width; // 如果当前行宽度未超过最大宽度,则继续添加字符 if (testWidth <= maxWidth) { currentLine = testLine; } else { // 当前行已满,将当前行推入lines(不包含当前字符) if (lines.length === 0) { // 第一行满了 lines.push(currentLine); // 重置当前行,从当前字符开始新的一行(第二行) currentLine = char; } else { // 已经有一行,现在在第二行,且第二行也放不下这个字符了,需要截断 // 注意:此我们只需要两行,所以第二行要加上省略号 // 现在currentLine是第二行的当前内容,我们尝试将当前字符加入第二行会超出,所以这个字符不能加入 // 但我们需要在第二行末尾加上省略号,所以需要预留省略号的位置 // 处理第二行:将当前行(第二行)加上省略号,但必须确保加上省略号后不超出 // 如果当前第二行内容加上省略号已经超出,则需要从第二行末尾移除一些字符,直到加上省略号不超出 while (ctx.measureText(currentLine + ellipsis).width > maxWidth && currentLine.length > 0) { currentLine = currentLine.slice(0, -1); } lines.push(currentLine + ellipsis); // 返回两行 return lines.join('\n'); } } } // 循环结束,文本已经全部处理完,但可能不足两行 if (currentLine !== '') { lines.push(currentLine); } // 如果只有一行,则直接返回;如果有两行,则返回两行(第二行没有省略号) return lines.join('\n'); } ``` 但是,上面的代码有一个问题:当第一行满了之后,第二行在添加字符,如果整个文本在第二行没有填满(即没有触发第二行满的条件),那么第二行就不会被截断,也不会加省略号。但是,我们的需求是:如果文本超过两行,才在第二行末尾加省略号。上面的代码中,当第二行在添加字符,如果遇到一个字符导致第二行超过宽度,就会触发截断并结束。但是,如果文本恰好两行,则不会加省略号,这符合需求。 但是,上面的代码在第二行触发截断,直接返回了,所以不会继续处理后面的字符。这正确。 然而,还有一种情况:第一行未满,但第二行在添加过程中,最后整个文本不足两行(即第二行未满),那么就不会进入else分支,最后返回两行(实际可能只有一行,或者两行但都不满)且没有省略号。这符合需求。 但是,上面的代码在第二行未满的情况下,第二行是完整的,没有省略号。只有当第二行在添加某个字符导致超过宽度,才会触发截断并加上省略号。 但是,我们考虑另一种情况:文本刚好两行,且第二行刚好满(没有超过),那么不会触发截断,也就没有省略号。但实际上,文本刚好两行,我们不需要省略号。只有超过两行(即有两行,且还有剩余字符),才需要省略号。所以,上面的代码逻辑是:当在第二行添加某个字符导致超过宽度,我们才认为文本超过了两行,然后对第二行进行截断并加上省略号。 因此,这个函数满足需求。 但是,我们也可以考虑另一种情况:文本超过两行,但是第二行的最后一个字符添加后,第二行刚好满(没有超过),那么不会触发截断,也就没有省略号。但实际上,文本还有剩余字符(第三行),此应该认为超过两行,但我们的函数没有处理第三行。因为我们的函数在第二行未超过不会触发截断,而第三行的字符根本没有处理(因为当第二行满的候,我们并没有继续处理,而是直接返回了)。所以,我们需要在第二行满的候(即第一行已满,第二行也满的候)触发截断,但此第二行并没有超过,所以不会进入else分支。 因此,我们需要修改逻辑:不能只处理两行,而是应该处理到两行满,然后如果还有剩余字符,就在第二行末尾加省略号。 重新设计: 我们使用一个行数计数器,最多两行。遍历每个字符,当行数达到两行且第二行已满(即不能再加字符),如果还有字符剩余,则对第二行进行截断(加上省略号)。 修改后的代码: 步骤: - 初始化lines数组,currentLine为空,lineCount=0(表示当前行数,但还没有完成一行) - 遍历每个字符: currentLine += char 测量currentLine的宽度 如果宽度超过maxWidth: 则说明当前行已经放不下这个字符了,那么: 将当前行回退到上一个字符(即去掉当前字符)作为一行,lineCount++。 如果lineCount已经达到2(即已经是第二行),那么: 我们需要在第二行末尾加上省略号(注意:第二行可能已经有一些字符,但加上省略号后不能超过maxWidth) 然后结束遍历,返回结果。 否则(即第一行): 将回退后的行加入lines,然后currentLine设置为当前字符(作为下一行的开始) 否则,继续下一个字符。 - 遍历结束后,如果currentLine不为空,则将其加入lines(作为最后一行)。 - 然后,如果lines的行数达到了2行,并且原始文本还没有处理完(即还有字符未处理,但我们提前结束了遍历,所以不会出现这种情况?)或者我们如何知道还有剩余字符? 实际上,我们在第二行满的候(即第二行加入一个字符导致超过宽度)就进行了截断并结束,此剩余字符不再处理。所以,如果我们在第二行满的候,已经知道还有剩余字符(因为遍历没有结束,但我们主动结束),所以我们在第二行末尾加省略号。 但是,在第二行触发截断,我们并不知道后面是否还有字符。不过,我们是在遍历过程中,当第二行满了(即加入一个字符导致超过),我们进行了截断并返回,此后面的字符都没有处理,所以肯定有剩余字符(因为当前字符已经导致第二行超过,但后面可能还有字符)。因此,我们需要在第二行满加上省略号。 修改后的代码: ```javascript function truncateText(text, maxWidth, font) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.font = font; const ellipsis = '...'; const ellipsisWidth = ctx.measureText(ellipsis).width; let lines = []; let currentLine = ''; let lineCount = 0; for (let i = 0; i < text.length; i++) { const char = text[i]; const testLine = currentLine + char; const testWidth = ctx.measureText(testLine).width; if (testWidth <= maxWidth) { currentLine = testLine; } else { // 当前行加上这个字符就超了,所以当前行应该是之前的内容(currentLine)作为一行 lineCount++; if (lineCount === 2) { // 已经是第二行满了,那么我们需要在第二行加上省略号 // 注意:此currentLine是还没有加char的,所以我们要在currentLine上加省略号,但可能加省略号后也会超出,所以需要调整 // 我们需要确保currentLine加上省略号后不超过maxWidth,如果超过,则从currentLine中移除一些字符 let finalLine = currentLine; while (ctx.measureText(finalLine + ellipsis).width > maxWidth && finalLine.length > 0) { finalLine = finalLine.slice(0, -1); } lines.push(finalLine + ellipsis); return lines.join('\n'); // 直接返回两行 } else { // 第一行满了,将当前行(不加char)加入第一行 lines.push(currentLine); currentLine = char; // 下一行从当前字符开始 } } } // 循环结束,将最后一行加入 if (currentLine !== '') { lines.push(currentLine); } return lines.join('\n'); } ``` 测试: - 文本不足一行:返回原文本 - 文本一行:返回一行 - 文本超过一行不足两行:返回两行(第二行没有省略号) - 文本超过两行:返回两行,第二行末尾有省略号 但是,这个代码有一个问题:当第一行满,我们加入的是currentLine(即不加当前字符),然后第二行从当前字符开始。但是,这个字符可能导致第二行一开始就超了吗?不会,因为一个字符的宽度通常不会超过maxWidth(除非maxWidth很小,或者字符很宽,比如一个很长的单词)。如果第二行第一个字符就超了,那么第二行会立即进入else分支,然后lineCount=2,然后进行截断。此,currentLine是单个字符,然后我们尝试在这个字符后面加省略号,但可能这个字符本身加上省略号就超了,所以我们会不断截断这个字符,直到为空,然后加上省略号,结果就是省略号。 但是,如果maxWidth连一个省略号都放不下,那么会一直截断到空字符串,然后加上省略号,结果就是省略号省略号本身也可能被截断?但省略号三个点通常不会分开)。所以,我们假设maxWidth至少能显示一个省略号。 改进:在开始前,先检查maxWidth是否小于省略号宽度,如果是,则直接返回空字符串(或者省略号,但省略号也显示不下,所以返回空)。 但是,这种情况很少,我们暂不考虑。 另一个问题:换行符的处理。如果文本中有换行符,我们可能希望按换行符分段。但是,我们这里没有处理。我们可以先将文本按换行符分割成多个段落,然后对每个段落进行两行截断?但用户需求是两行,所以如果有换行符,可能第一行就有一个换行符,那么第一行被分成两段?这不符合需求。因此,我们可以在处理前将换行符替换为空格。 我们可以这样做: text = text.replace(/\n/g, ' '); 根据需求决定。 最后,我们提供一个使用示例: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Text Truncation</title> <style> .container { width: 300px; font-size: 16px; font-family: Arial, sans-serif; } </style> </head> <body> <div class="container"> <pre id="output"></pre> </div> <script> // 将上面的truncateText函数放在这里 const text = "这是一段非常长的文本,用于测试两行截断功能。如果文本超过两行,则第二行末尾会显示省略号。如果没有超过,则正常显示。"; const maxWidth = 300; // 容器的宽度,单位像素 const font = "16px Arial"; // 字体样式,需要与容器样式一致 const truncated = truncateText(text, maxWidth, font); document.getElementById('output').innerText = truncated; </script> </body> </html> ``` 注意:由于Canvas的measureText可能在不同浏览器和系统上有细微差异,所以最好在目标环境中测试。 另外,如果文本中有英文单词,我们可能希望在单词断开处加连字符(-),但这里没有实现。 相关问题: 1. 如何实现按单词截断而不是按字符截断? 2. 如何支持多行省略(比如三行)? 3. 如何提高截断函数的性能(特别是长文本)? 4. 在React/Vue中如何封装这个功能?</think>### JavaScript 实现文本截断(两行省略) 在 JavaScript 中实现文本多行截断需要手动计算文本宽度,因为 Canvas 原生不支持 CSS 的 `text-overflow: ellipsis` 属性。以下是完整解决方案: ```javascript /** * 文本截断函数(支持两行省略号) * @param {string} text - 原始文本 * @param {number} maxWidth - 容器最大宽度(px) * @param {number} fontSize - 字体大小(px) * @returns {string} - 处理后的文本(带换行符和省略号) */ function truncateText(text, maxWidth, fontSize) { // 创建测量画布 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置测量字体(需与实际渲染字体一致) ctx.font = `${fontSize}px -apple-system, sans-serif`; const ellipsis = '...'; const ellipsisWidth = ctx.measureText(ellipsis).width; let lines = []; let currentLine = ''; const words = text.split(''); const lineHeight = fontSize * 1.5; // 行高(1.5倍字体大小) // 第一行处理 for (const char of words) { const testLine = currentLine + char; const metrics = ctx.measureText(testLine); if (metrics.width <= maxWidth) { currentLine = testLine; } else { lines.push(currentLine); currentLine = char; // 开始新行 if (lines.length >= 2) break; // 已达最大行数 } } // 处理剩余文本 if (currentLine && lines.length < 2) { lines.push(currentLine); } // 第二行添加省略号 if (lines.length >= 2) { let lastLine = lines[1]; while (ctx.measureText(lastLine + ellipsis).width > maxWidth && lastLine.length > 0) { lastLine = lastLine.slice(0, -1); // 逐个移除字符 } lines[1] = lastLine + ellipsis; } return lines.join('\n'); // 用换行符连接 } ``` #### 使用示例 ```html <div style="width: 300px; font-size: 16px;" id="textContainer"></div> <script> const longText = "这是一段非常长的文本内容,需要被截断显示在两行内,超出部分省略号表示..."; const truncated = truncateText(longText, 300, 16); const container = document.getElementById('textContainer'); container.innerText = truncated; // 自动保留换行符 container.style.whiteSpace = 'pre-line'; // 关键样式:保留换行 </script> ``` #### 关键实现原理 1. **文本测量** 使用 `canvas.measureText()` 动态计算文本宽度[^1] ```javascript const metrics = ctx.measureText(testLine); const textWidth = metrics.width; ``` 2. **分行逻辑** - 按字符遍历文本(支持中文) - 当累计宽度 > 容器宽度创建新行 - 最多保留两行内容 3. **省略号处理** 第二行尾部空间不足: ```javascript while (ctx.measureText(lastLine + ellipsis).width > maxWidth) { lastLine = lastLine.slice(0, -1); // 移除末尾字符 } ``` 4. **样式要求** 渲染容器必须设置: ```css white-space: pre-line; /* 保留换行符 */ line-height: 1.5; /* 与JS中的lineHeight对应 */ ``` #### 性能优化建议 ```javascript // 复用测量画布(避免重复创建) const measureCanvas = document.createElement('canvas'); const ctx = measureCanvas.getContext('2d'); function truncateText(text, maxWidth, fontSize) { ctx.font = `${fontSize}px -apple-system, sans-serif`; // ...后续逻辑不变 } ``` #### 响应式适配 ```javascript // 根据屏幕宽度动态计算maxWidth const container = document.getElementById('textContainer'); const maxWidth = container.clientWidth * 0.95; // 留5%边距 // 根据设备像素比调整 const dpr = window.devicePixelRatio; const realMaxWidth = maxWidth * dpr; ``` ### 相关问题 1. **如何实现按单词截断而不是按字符截断?** 修改分词逻辑即可: ```javascript // 将 split('') 改为 split(' ') const words = text.split(' '); ``` 但需注意长单词仍需字符级截断[^1] 2. **如何支持三行或更多行省略?** 修改行数限制: ```javascript const maxLines = 3; // 改为目标行数 if (lines.length >= maxLines) break; ``` 3. **在Canvas渲染中如何应用此方案?** 生成截断文本后直接用 `fillText` 绘制: ```javascript ctx.fillText(truncatedText, x, y); ``` 4. **如何兼容自定义字体?** 确保字体加载完成后再测量: ```javascript document.fonts.ready.then(() => { const truncated = truncateText(...); }); ``` 5. **在React/Vue中如何封装组件?** 创建可复用组件,监听容器尺寸变化(使用ResizeObserver)并自动触发截断计算[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值