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

React TS中实现Div内长文本短版展示及展开收起功能

React长文本截断+展开收起组件解决方案

方法1:利用DOM元素的getClientRects()计算实际行数(最精准)

这个方法直接基于文本渲染后的实际行数据,能准确处理容器自动换行的情况:

  1. 先将完整文本渲染到目标容器,通过getClientRects()获取每一行的矩形信息,集合的长度就是实际行数。
  2. 如果行数超过指定阈值,用二分法逐步截取文本,直到行数符合要求,同时拼接省略号;展开后切换为完整文本。

示例代码:

import { useState, useRef, useEffect } from 'react';

const TextTruncator = ({ text, maxLines = 3 }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const [truncatedText, setTruncatedText] = useState(text);
  const textRef = useRef(null);

  useEffect(() => {
    if (isExpanded || !textRef.current) return;

    const element = textRef.current;
    // 先渲染完整文本,获取真实行数
    element.textContent = text;
    const lines = element.getClientRects();
    
    if (lines.length <= maxLines) {
      setTruncatedText(text);
      return;
    }

    // 二分法查找最大可显示的文本长度
    let low = 0;
    let high = text.length;
    let bestLength = text.length;

    while (low <= high) {
      const mid = Math.floor((low + high) / 2);
      element.textContent = text.slice(0, mid) + '...';
      const currentLines = element.getClientRects();
      
      if (currentLines.length <= maxLines) {
        bestLength = mid;
        low = mid + 1;
      } else {
        high = mid - 1;
      }
    }

    setTruncatedText(text.slice(0, bestLength) + '...');
  }, [text, maxLines, isExpanded]);

  return (
    <div ref={textRef} style={{ width: '300px' }}>
      {isExpanded ? text : truncatedText}
      {textRef.current && textRef.current.getClientRects().length > maxLines && (
        <button onClick={() => setIsExpanded(!isExpanded)}>
          {isExpanded ? 'show less' : 'show more'}
        </button>
      )}
    </div>
  );
};

export default TextTruncator;

注意:要确保容器的样式(宽度、字体、行高)和最终显示完全一致,否则行数计算会出现偏差。

方法2:CSS line-clamp + JS控制(最简单)

如果不需要复杂的自定义截断逻辑,用CSS原生属性可以快速实现,JS只负责切换展开/收起状态:

import { useState } from 'react';

const TextTruncator = ({ text, maxLines = 3 }) => {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div style={{ width: '300px' }}>
      <div 
        style={{
          display: '-webkit-box',
          WebkitLineClamp: isExpanded ? 'none' : maxLines,
          WebkitBoxOrient: 'vertical',
          overflow: 'hidden',
          textOverflow: 'ellipsis',
        }}
      >
        {text}
      </div>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? 'show less' : 'show more'}
      </button>
    </div>
  );
};

export default TextTruncator;

优点:代码极简,无需手动处理文本截断;缺点:-webkit-line-clamp是webkit内核属性,虽然后续Firefox等主流浏览器已支持,但旧版浏览器可能需要降级方案。

方法3:Canvas计算文本宽度(适合自定义截断逻辑)

如果需要精细化控制(比如避免截断到单词中间),可以用Canvas计算文本宽度,逐词判断是否超出容器:

import { useState, useRef, useEffect } from 'react';

const TextTruncator = ({ text, maxLines = 3, containerWidth = 300, fontSize = '16px', lineHeight = '1.5' }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const [truncatedText, setTruncatedText] = useState(text);
  const canvasRef = useRef(document.createElement('canvas'));

  useEffect(() => {
    if (isExpanded) return;

    const ctx = canvasRef.current.getContext('2d');
    ctx.font = `${fontSize} sans-serif`;
    const lineHeightPx = parseFloat(fontSize) * parseFloat(lineHeight);
    const maxHeight = lineHeightPx * maxLines;
    let currentHeight = 0;
    let currentLine = '';
    let result = '';

    // 按空格拆分单词,避免截断单词
    const words = text.split(' ');
    for (const word of words) {
      const testLine = currentLine + (currentLine ? ' ' : '') + word;
      const lineWidth = ctx.measureText(testLine).width;
      
      if (lineWidth > containerWidth) {
        currentHeight += lineHeightPx;
        if (currentHeight + lineHeightPx > maxHeight) {
          // 处理最后一行,添加省略号
          const finalLine = currentLine + '...';
          if (ctx.measureText(finalLine).width <= containerWidth) {
            result += finalLine;
          } else {
            // 连当前行都放不下,逐字符截断
            let truncated = '';
            for (const char of currentLine) {
              truncated += char;
              if (ctx.measureText(truncated + '...').width > containerWidth) {
                truncated = truncated.slice(0, -1);
                break;
              }
            }
            result += truncated + '...';
          }
          break;
        }
        result += currentLine + ' ';
        currentLine = word;
      } else {
        currentLine = testLine;
      }
    }

    // 处理剩余未超出的行
    if (currentHeight + lineHeightPx <= maxHeight) {
      result += currentLine;
    } else {
      result += currentLine.slice(0, -3) + '...';
    }

    setTruncatedText(result);
  }, [text, maxLines, containerWidth, fontSize, lineHeight, isExpanded]);

  return (
    <div style={{ width: `${containerWidth}px`, fontSize, lineHeight }}>
      {isExpanded ? text : truncatedText}
      {truncatedText.length < text.length && (
        <button onClick={() => setIsExpanded(!isExpanded)}>
          {isExpanded ? 'show less' : 'show more'}
        </button>
      )}
    </div>
  );
};

export default TextTruncator;

这个方法适合需要精确控制截断位置的场景,但要保证Canvas的字体样式和实际显示一致,否则宽度计算会有误差。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:33:29