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

无需line-clamp实现两行文本完整显示,避免单词截断

问题描述

从JSON文件生成多个单词,希望页面仅显示两行内容。当前使用line-clamp属性时,第二行的最后一个单词会被截断,希望若单词将被截断则自动移至下一行,目标是实现类似typetest.io的文本显示效果。

现有代码示例

React组件代码

<div className="box">
    <p className="word">
        {wordList.map((word, wordIndex) => {
            const currWord = typedHistory[wordIndex] || "";
            const isCurrentWord = wordIndex === typedHistory.length;
            const hasWrongChars = currWord.split("").some((char, index) => char !== word[index]);
            const isExcessLength = currWord.length > word.length;
            const isIncomplete = currWord.length >= 0 && currWord.length < word.length && typedHistory.includes(currWord);
            const shouldHighlightWordRed = hasWrongChars || isExcessLength || isIncomplete;
            return (
                <span key={wordIndex} ref={isCurrentWord ? currentWordRef : null}>
                    {word.split("").map((char, charIndex) => {
                        let charColor = "#fff";

                        if (wordIndex < typedHistory.length) {
                            charColor = shouldHighlightWordRed ? "red" : "var(--main-color)";
                        } else if (isCurrentWord) {
                            const typedChar = typedChars[charIndex] || "";
                            charColor = typedChar === char ? "var(--main-color)" : typedChar !== "" ? "red" : "#fff";
                        }
                        return (
                            <span key={charIndex} style={{ color: charColor }}>{char}</span>
                        );
                    })}
                    {' '}
                </span>
            );
        })}
    </p>
</div>

CSS样式

.box {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  background-color: var(--sub-alt-color);
  border-radius: 10px;
  max-height: 8.5em;  
  height: 8.5em;
  user-select: none;
}
.box .word {
  display: block;
  font-size: 1.5rem;
  overflow-y: hidden;
  line-height: 1.8em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
          line-clamp: 2; 
  -webkit-box-orient: vertical;
}

文本生成逻辑

useEffect(() => {
    import("../../public/english.json").then((words) =>
        dispatch(setWordList(words.default))
    );
}, [dispatch]);
解决方案

原生line-clamp是基于行数强制截断,不会识别单词边界,无法直接实现“截断时自动移走单词”的效果。要达成需求,需通过JavaScript结合DOM测量控制渲染的单词数量,确保所有显示的单词完整放在两行内:

  1. 测量容器与单词宽度
    在组件挂载或窗口resize时,计算.word容器的宽度,以及每个单词(含后续空格)的实际渲染宽度。

  2. 筛选可显示的单词
    累加单词宽度,直到加上下一个单词后总宽度超过两行总宽度(容器宽度×2),停止累加,只渲染这些单词,剩余单词暂时隐藏。

  3. 动态更新显示内容
    当用户输入导致单词样式变化、窗口大小改变时,重新计算并更新显示的单词列表。

具体实现示例:

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

// ... 其他业务代码

const wordContainerRef = useRef(null);
const [visibleWords, setVisibleWords] = useState([]);

useEffect(() => {
    const calculateVisibleWords = () => {
        if (!wordContainerRef.current || wordList.length === 0) return;
        
        const container = wordContainerRef.current;
        const containerWidth = container.offsetWidth;
        const lineCount = 2;
        const maxTotalWidth = containerWidth * lineCount;
        
        let currentTotalWidth = 0;
        const tempVisible = [];
        
        // 创建临时元素测量单词宽度
        const tempSpan = document.createElement('span');
        tempSpan.style.fontSize = '1.5rem';
        tempSpan.style.visibility = 'hidden';
        tempSpan.style.position = 'absolute';
        document.body.appendChild(tempSpan);
        
        for (let i = 0; i < wordList.length; i++) {
            const word = wordList[i];
            tempSpan.textContent = word + ' '; // 包含单词后的空格
            const wordWidth = tempSpan.offsetWidth;
            
            if (currentTotalWidth + wordWidth <= maxTotalWidth) {
                tempVisible.push(word);
                currentTotalWidth += wordWidth;
            } else {
                break;
            }
        }
        
        document.body.removeChild(tempSpan);
        setVisibleWords(tempVisible);
    };

    // 初始计算
    calculateVisibleWords();
    // 窗口resize时重新计算
    window.addEventListener('resize', calculateVisibleWords);
    
    return () => {
        window.removeEventListener('resize', calculateVisibleWords);
    };
}, [wordList]);

// 渲染时使用visibleWords替代原wordList
return (
    <div className="box">
        <p className="word" ref={wordContainerRef}>
            {visibleWords.map((word, wordIndex) => {
                // 保留原有的单词渲染逻辑
                const currWord = typedHistory[wordIndex] || "";
                const isCurrentWord = wordIndex === typedHistory.length;
                const hasWrongChars = currWord.split("").some((char, index) => char !== word[index]);
                const isExcessLength = currWord.length > word.length;
                const isIncomplete = currWord.length >= 0 && currWord.length < word.length && typedHistory.includes(currWord);
                const shouldHighlightWordRed = hasWrongChars || isExcessLength || isIncomplete;
                
                return (
                    <span key={wordIndex} ref={isCurrentWord ? currentWordRef : null}>
                        {word.split("").map((char, charIndex) => {
                            let charColor = "#fff";

                            if (wordIndex < typedHistory.length) {
                                charColor = shouldHighlightWordRed ? "red" : "var(--main-color)";
                            } else if (isCurrentWord) {
                                const typedChar = typedChars[charIndex] || "";
                                charColor = typedChar === char ? "var(--main-color)" : typedChar !== "" ? "red" : "#fff";
                            }
                            return (
                                <span key={charIndex} style={{ color: charColor }}>{char}</span>
                            );
                        })}
                        {' '}
                    </span>
                );
            })}
        </p>
    </div>
);

同时修改CSS,移除line-clamp相关属性:

.box .word {
  display: block;
  font-size: 1.5rem;
  overflow-y: hidden;
  line-height: 1.8em;
  /* 移除以下line-clamp相关属性 */
  /* display: -webkit-box; */
  /* -webkit-line-clamp: 2; */
  /* line-clamp: 2;  */
  /* -webkit-box-orient: vertical; */
}

这种方式通过预计算宽度控制可见内容,确保所有显示的单词完整落在两行内,与目标效果的核心逻辑一致。

内容的提问来源于stack exchange,提问作者Rohith Nambiar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 16:04:58