React 文字超出显示省略号并且触发Tooltip 的组件封装
背景
项目中的遇到了很多的文案显示,比如form表单的label、checkbox等等的文案显示。要求的是没有超出固定的宽度只显示本身的文字,如果超出的固定宽度是显示省略号并且鼠标移上去显示tooltip。查看的一下组件库也没有该组件,于是思考了一下,不得不自己封装一个组件。
方法1
通过创建一个标签来跟原标签的宽度对比来判断是否显示省略号,对比再删除创建的标签
封装步骤
第一步 checkIsOverflowed 方法
先封装一个能够自动获取到传入文案的宽度的方法。
创建一个标签,根据传入的字号来获取到宽度与传入的宽度比较一下。
export const checkIsOverflowed = (
ref: any,
width: number = 0,
fontSize: number = 12
) => {
const element = ref
const tempDiv = document.createElement('div');
tempDiv.style.position = 'absolute';
tempDiv.style.top = '9999px';
tempDiv.style.fontSize = `${fontSize}px`
tempDiv.textContent = element.textContent;
document.body.appendChild(tempDiv);
const isOverflowed = tempDiv.clientWidth > width;
document.body.removeChild(tempDiv);
return isOverflowed
}
第二步 OverflowTooltip 组件
根据 checkIsOverflowed 方法来获取到是否显示省略号,来判断是显示tooltip组件还是本身的文本。
为了方便外部控制组件的样式,所以暴露了 className, tooltipOverlayClassName,style三个参数。
export const OverflowTooltip: FC<OverflowProps> = ({
text,
width,
ellipsisWidth,
fontSize = 14,
className = '',
tooltipOverlayClassName = '',
style = {},
}) => {
const overflowRef = useRef<HTMLDivElement>(null)
// 是否出现省略号
const [isOverflow, setIsOverflow] = useState<boolean>(false)
useEffect(() => {
const isOverflowed = checkIsOverflowed(overflowRef?.current, width, fontSize)
setIsOverflow(isOverflowed)
}, [overflowRef.current, text])
const renderItem = () => {
if (!isOverflow) {
return <div>{text}</div>
}
return (
<Tooltip
title={text}
overlayClassName={tooltipOverlayClassName}
>
<div
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: ellipsisWidth
}}
>{text}</div>
</Tooltip>
)
}
return (
<div
ref={overflowRef}
className={className}
style={style}
>
{renderItem()}
</div>
)
}
完整代码
import { FC, useRef, useEffect, useState } from 'react'
import { Tooltip } from 'antd'
export const checkIsOverflowed = (
ref: any,
width: number = 0,
fontSize: number = 12
) => {
const element = ref
const tempDiv = document.createElement('div');
tempDiv.style.position = 'absolute';
tempDiv.style.top = '9999px';
tempDiv.style.fontSize = `${fontSize}px`
tempDiv.textContent = element.textContent;
document.body.appendChild(tempDiv);
const isOverflowed = tempDiv.clientWidth > width;
document.body.removeChild(tempDiv);
return isOverflowed
}
interface OverflowProps {
text: React.ReactNode
width: number
ellipsisWidth: number
fontSize?: number
className?: any
tooltipOverlayClassName?: any
style?: any
}
export const OverflowTooltip: FC<OverflowProps> = ({
text,
width,
ellipsisWidth,
fontSize = 14,
className = '',
tooltipOverlayClassName = '',
style = {},
}) => {
const overflowRef = useRef<HTMLDivElement>(null)
// 是否出现省略号
const [isOverflow, setIsOverflow] = useState<boolean>(false)
useEffect(() => {
const isOverflowed = checkIsOverflowed(overflowRef?.current, width, fontSize)
setIsOverflow(isOverflowed)
}, [overflowRef.current, text])
const renderItem = () => {
if (!isOverflow) {
return <div>{text}</div>
}
return (
<Tooltip
title={text}
overlayClassName={tooltipOverlayClassName}
>
<div
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: ellipsisWidth
}}
>{text}</div>
</Tooltip>
)
}
return (
<div
ref={overflowRef}
className={className}
style={style}
>
{renderItem()}
</div>
)
}
方法2
原理
-
创建两个标签,一个标签可能是多行显示,另个一个标签是永远是单行显示。
-
对比两个标签的高度来判断是否显示省略号。
注意: 创建的两个标签是不会在文档流中显示
参考 ant-design-mobile ellipsis组件
完整代码
/**
* * aria-hidden: https://developer.mozilla.org/zh-CN/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden
* * \u00A0: HTML 中的作用主要是表示不换行空格, 保持文本连续性:在一些需要保持特定文本内容在同一行显示,避免因浏览器窗口大小调整或其他因素导致换行的场景中,\u00A0非常有用。
*/
import { FC, useRef, useEffect, useState, useLayoutEffect, Fragment } from 'react'
import { Tooltip } from 'antd'
enum MEASURE_STATUS {
PREPARE = 1,
ELLIPSIS = 99,
NO_ELLIPSIS = 100
}
const measureStyle: React.CSSProperties = {
visibility: 'hidden',
whiteSpace: 'inherit',
lineHeight: 'inherit',
fontSize: 'inherit',
}
interface OverflowProps {
text: React.ReactNode
width: number
fontSize?: number
className?: any
tooltipOverlayClassName?: any
style?: any
}
export const OverflowTooltip: FC<OverflowProps> = ({
text,
width,
className = '',
tooltipOverlayClassName = '',
style = {},
}) => {
const [status, setStatus] = useState<MEASURE_STATUS>(
MEASURE_STATUS.PREPARE
)
const singleRowMeasureRef = useRef<HTMLDivElement>(null)
const fullMeasureRef = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
if (status === MEASURE_STATUS.PREPARE) {
const fullMeasureHeight = fullMeasureRef.current?.offsetHeight || 0
const singleRowMeasureHeight = singleRowMeasureRef.current?.offsetHeight || 0
if (fullMeasureHeight <= singleRowMeasureHeight) {
setStatus(MEASURE_STATUS.NO_ELLIPSIS)
}
else {
setStatus(MEASURE_STATUS.ELLIPSIS)
}
}
}, [status])
const renderContent = () => {
if (status === MEASURE_STATUS.NO_ELLIPSIS) {
return (
<div>{text}</div>
)
}
if (status === MEASURE_STATUS.ELLIPSIS) {
return (
<Tooltip
title={text}
overlayClassName={tooltipOverlayClassName}
>
<div
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: width
}}
>{text}</div>
</Tooltip>
)
}
return null
}
const renderItem = () => {
return (
<>
{/* 文本完全显示,折行显示 */}
{
status === MEASURE_STATUS.PREPARE && (
<div
key='full'
aria-hidden
ref={fullMeasureRef}
style={{
...measureStyle,
width: width
}}
>
{text}
</div>
)
}
{/* 控制单行显示 */}
{
status === MEASURE_STATUS.PREPARE && (
<div
key='stable'
aria-hidden
ref={singleRowMeasureRef}
style={{
...measureStyle,
width: width
}}
>
{'\u00A0'}
</div>
)
}
{/* 真正的显示文本 */}
{renderContent()}
</>
)
}
return (
<div
className={className}
style={{
...style,
overflow: 'hidden',
wordBreak: 'break-word'
}}
>
{renderItem()}
</div>
)
}
使用后的效果
总结
根据在项目中的需求来封装一些组件,方便复用,以后就是省时省力。
方法1的思路:通过对传入文本的宽度和要求的宽度进行对比,来显示原本的文本还是tooltip组件。但是因为会进行dom元素删除,大量的话会造成性能问题
方法2的思路:通过创建两个标签并且不在文档流中显示出来, 对比高度来判断是否显示省略号。 判断完就把这两个标签就不显示了,只不过就是在jsx中操作的。最后来决定显示原本的文本还是tooltip组件。