将多余的字用省略号表示,只显示一行(n行)

本文详细介绍了如何使用CSS的-webkit-line-clamp属性来限制文本的显示行数,这对于优化网页布局和提高用户体验至关重要。通过实例展示了如何在不同的浏览器中实现一致的效果。

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

只显示一行(n行)
在这里插入图片描述

.hideWord{
            display: -webkit-box;
            -webkit-box-orient:vertical;
            overflow: hidden;
            -webkit-line-clamp: 1; /*n行*/
}
<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、付费专栏及课程。

余额充值