背景
在平时的业务场景中,为了提升用户体验,经常会使用固定宽度,超出显示...
的css样式。但是每次都写一次,比较麻烦,为了更好的摸鱼,所以把Ellipsis组件抽离出来。
先捋一下要实现的功能:
- 固定宽度,超出显示...
- 鼠标
hover
上去,能看到全部 - 支持多行显示...
- 考虑
resize
场景
思路
计算要渲染文字的宽度,如果宽度超出,就显示...
这里的问题是,如何计算文字的宽度呢?
- 可以先把文字在看不见的地方先渲染出来,获取到实际宽度,然后再去比较
- 直接给容器设置
overflow:hidden
,然后比较scrollWidth
跟clientWidth
的值
这里我用的是第二种。
import React, { FC, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { Tooltip } from 'antd';
import debounce from 'lodash/debounce';
import useDimensions from 'react-cool-dimensions';
import { TooltipProps } from 'antd/lib';
interface EllipsisProps {
line?: number
style?: React.CSSProperties
children?: React.ReactElement
}
const Ellipsis: FC<EllipsisProps & TooltipProps> = (props) => {
const {
children,
line = 1,
style: currentStyle = {},
...other
} = props;
const wrapper = useRef<HTMLElement>();
const [showPopover, setShowPopover] = useState(false);
// 先设置overflow: 'hidden', 这样超出了才能进行比较
const defaultStyle: React.CSSProperties = line === 1 ? {
display: 'block',
maxWidth: '100%',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
...currentStyle
} : {
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: line,
...currentStyle
}
const validShowPopover = () => {
// 当line为1的时候,比较width
// 否则,比较height
const {
scrollWidth = 0,
clientWidth = 0,
scrollHeight = 0,
clientHeight = 0
} = wrapper.current as HTMLElement || {};
setShowPopover(scrollWidth > clientWidth || scrollHeight > clientHeight)
}
const debounceMesaure = useCallback(debounce(() => {
validShowPopover();
}, 200), [validShowPopover]);
// 支持resize
const { observe } = useDimensions({
onResize: debounceMesaure,
});
useEffect(() => {
validShowPopover();
}, []);
const renderChildren = () => {
return React.cloneElement(children, {
style: defaultStyle,
ref: (element: HTMLElement | undefined) => {
observe(element);
wrapper.current = element;
},
})
}
// 基于ant的Tooltip组件
if (showPopover) {
return <Tooltip {...other}>{renderChildren()}</Tooltip>
}
return renderChildren();
}
export default Ellipsis
使用
Ellipsis组件的children必须是一个html元素标签
如果要渲染的元素没有给定宽度的话,需要手动向Ellipsis组件传一个width参数
import Ellipsis from './Ellipsis';
const EllipsisPage = () => {
return <div>
<Ellipsis style={{ width: 200 }} title={text} line={2}>
<span>{text}</span>
</Ellipsis>
</div>
}
export default EllipsisPage;
如果要渲染的元素已经有指定的宽度,则不需要
import Ellipsis from './Ellipsis';
const EllipsisPage = () => {
return <div>
<div style={{ width: 200 }}>
<Ellipsis title={text} line={1}>
<span>{text}</span>
</Ellipsis>
</div>
</div>
}
export default EllipsisPage;
更多前端面试资料: 前端资料