import React, { useEffect, useRef, useState, memo } from 'react'; import { Wrapper } from './style'; import { getTextWidth } from 'utils'; import { ILine } from './types'; export const LineEllipsis = function({ children = '', line = 2, foldable = false, width = 120, actionText = ['更多', '收起'], ...rest }: ILine) { const ref: any = useRef(); const [rendered, set] = useState(children); const [isOpen, setIsOpen] = useState(false); const [showAction, setShowAction] = useState(false); useEffect(() => { const containerWidth = width - 4; // 隐藏状态或者低于单字符宽度,直接退出计算 if (containerWidth < 17) { return; } // 不换行文本原始长度 const textOriginWidth = getTextWidth(children, ref.current); // 平均每个字符的长度(概数,可能有字母汉字数字混合情况或者非等宽字体) const avgCharWidth = textOriginWidth / (children.length || 1); // 大概的换行索引 let likelyReturnIdx = Math.floor(containerWidth / (avgCharWidth || 1)); // 每行大概的个数 const rowWidth = likelyReturnIdx; // 准确的换行索引 const returnIdx: number[] = []; // 需要加省略号 const needEllipsis = containerWidth * line < textOriginWidth; needEllipsis && setShowAction(needEllipsis); if (needEllipsis) { let characters = children.split(''); Array(line) .fill(0) .forEach((_, idx) => { while ( containerWidth > getTextWidth( characters .slice(returnIdx[idx - 1] || 0, likelyReturnIdx) //.concat([idx === line - 1 && foldable ? '...' + actionText[0] : '']) .join(''), ref.current ) ) { likelyReturnIdx += 1; if (likelyReturnIdx > characters.length) { break; } } returnIdx.push(likelyReturnIdx - 1); likelyReturnIdx += rowWidth; }); let lastIndex = returnIdx.pop() as number; let lastStartIndex = (returnIdx.pop() as number) - 1; while ( containerWidth < getTextWidth( characters .slice(lastStartIndex || 0, lastIndex) .concat(['...', foldable ? actionText[0] : '']) .join(''), ref.current ) ) { lastIndex -= 1; if (lastIndex === 0) { break; } } set( characters .slice(0, lastIndex) .concat(['...']) .join('') ); } else { set(children); } }, [children, line]); return ( {((foldable && !isOpen) || !foldable) && rendered} {foldable && !isOpen && showAction && ( setIsOpen(true)}> {isOpen ? actionText[1] : actionText[0]} )} {foldable && isOpen && ( <> {children} setIsOpen(false)}> {isOpen ? actionText[1] : actionText[0]} )} ); }; LineEllipsis.defaultProps = { width: 120, line: 2, foldable: false, }; export default memo(LineEllipsis);