无需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测量控制渲染的单词数量,确保所有显示的单词完整放在两行内:
测量容器与单词宽度
在组件挂载或窗口resize时,计算.word容器的宽度,以及每个单词(含后续空格)的实际渲染宽度。筛选可显示的单词
累加单词宽度,直到加上下一个单词后总宽度超过两行总宽度(容器宽度×2),停止累加,只渲染这些单词,剩余单词暂时隐藏。动态更新显示内容
当用户输入导致单词样式变化、窗口大小改变时,重新计算并更新显示的单词列表。
具体实现示例:
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
相关产品推荐
相关产品推荐

