动态检测文本溢出:实现一个优雅的 React Hook
需求场景与实现价值
在前端开发中,我们经常需要处理文本溢出场景。当容器空间不足时,通过 CSS 的 text-overflow: ellipsis 实现文本截断是常见做法。但在某些交互场景中,我们还需要:
- 动态感知截断状态(如显示「更多」按钮)
- 响应式布局中的智能提示
- 动态内容加载后的状态检测
本文将实现一个 React 自定义 Hook useEllipsis,帮助开发者优雅地解决这些问题。
核心检测原理
基础检测逻辑
通过比较元素的滚动尺寸与实际渲染尺寸判断溢出状态:
const isHorizontalOverflow = element.scrollWidth > element.clientWidth
const isVerticalOverflow = element.scrollHeight > element.clientHeight
智能监听机制
使用 ResizeObserver 实现精准监听:
const observer = new ResizeObserver(() => {
// 重新计算溢出状态
})
完整实现代码
function useEllipsis(domRef: React.RefObject<HTMLElement>): boolean {
const [isEllipsis, setIsEllipsis] = useState(false)
useEffect(() => {
const element = domRef.current
if (!element)
return
// 核心检测方法
const checkEllipsis = () => {
const hasHorizontal = element.scrollWidth > element.clientWidth
const hasVertical = element.scrollHeight > element.clientHeight
return hasHorizontal || hasVertical
}
// 更新状态(防抖处理)
const update = () => {
const newState = checkEllipsis()
if (newState !== isEllipsis) {
setIsEllipsis(newState)
}
}
// 双保险监听器
const resizeObserver = new ResizeObserver(update)
const mutationObserver = new MutationObserver(update)
// 启动监听
resizeObserver.observe(element)
mutationObserver.observe(element, {
characterData: true, // 监听文本变化
subtree: true, // 监听所有子节点
childList: true, // 监听子节点增删
})
// 手动触发一次初始检测
update()
// 清理函数
return () => {
resizeObserver.disconnect()
mutationObserver.disconnect()
}
}, [domRef, isEllipsis]) // 依赖 isEllipsis 用于状态比对
return isEllipsis
}
export default useEllipsis
核心特性解析
1. 复合条件检测
同时验证样式属性与尺寸关系:
// 验证是否应用了省略号相关样式
const isEllipsisStyle =
style.textOverflow === 'ellipsis' ||
style.webkitLineClamp !== 'none'
// 验证是否实际发生溢出
const isContentOverflow =
element.scrollWidth > element.clientWidth ||
element.scrollHeight > element.clientHeight
2. 智能方向检测
同时支持水平和垂直方向的检测:
- 水平方向:适用于
white-space: nowrap场景 - 垂直方向:适配
-webkit-line-clamp多行截断
3. 性能优化策略
- 自动清理观察器
- 防抖动检测机制(示例代码可扩展)
- 精确依赖项控制
应用示例
基础使用场景
const ArticleCard = ({ title }) => {
const titleRef = useRef(null)
const hasEllipsis = useEllipsis(titleRef)
return (
<div className="card">
<h3
ref={titleRef}
className="truncate-text"
>
{title}
</h3>
{hasEllipsis && (
<Tooltip content={title}>
<Icon name="expand" />
</Tooltip>
)}
</div>
)
}
总结与展望
通过这个 Hook 的实现,我们不仅解决了文本溢出检测的技术问题,更展示了现代浏览器 API 与 React 生态结合的最佳实践。