简记|React+Antd中实现 tooltip、ellipsis、copyable功能组件

前言

在项目中使用了antdProTable,其tooltipellipsiscopyable在某些情况下具有局限性,比如:1、渲染html内容;2、长文本使用tooltip的情况下,宽度没有自动适应导致tooltip纵向太长,如下图:

所以,我需要重新封装一个适用的组件:ColumnOverflow

组件介绍

ColumnOverflow 是一个用于处理文本溢出的 React 组件,它提供了以下功能:

  • 文本溢出时自动显示省略号
  • 鼠标悬停时显示完整内容的 Tooltip
  • 支持 HTML 内容渲染
  • 可选的复制功能
  • 支持水平和垂直方向的溢出检测
  • ...可扩展

组件API定义

ts 复制代码
type Props = {
  title: string;  // 显示的文本内容(支持HTML)
  copyText?: string;  // 复制按钮复制的文本,默认为title
  direction?: 'horizontal' | 'vertical';  // 溢出检测方向
  isShowCopyable?: boolean;  // 是否显示复制按钮
  styles?: object;  // Tooltip 的自定义样式
  placement?: TooltipPlacement; // 气泡框位置
};

布局实现

组件采用 styled-components 进行样式管理,使用 Flex 布局实现灵活的空间分配:

ts 复制代码
import styled from 'styled-components';

export const ColumnOverflowStyle = styled.div`
  .column-overflow {
    position: relative;
    width: 100%;
    display: flex;
    align-items: center;

    &__content {
      flex: 1;
      min-width: 0;
    }
    &__hidden {
      width: 100%;
      > div {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        width: 100%;
      }
    }
  }
`;

溢出检测

组件通过比较元素的 scrollWidth/scrollHeightclientWidth/clientHeight 来检测内容是否溢出:

ts 复制代码
const visibilityChange = useCallback(
      (event: any) => {
        const ev = event.target;
        let ev_size = ev.scrollWidth;
        let content_size = ev.clientWidth;
        if (direction === 'vertical') {
          ev_size = ev.scrollHeight;
          content_size = ev.clientHeight;
        }
        if (ev_size > content_size) {
          setShowTooltip(true);
        } else {
          setShowTooltip(false);
        }
      },
      [direction]
    );

HTML 内容渲染

组件直接支持在 title 属性中传入 HTML 内容,并在 Tooltip 和主内容区域进行渲染:

tsx 复制代码
<Tooltip
  overlayInnerStyle={{ whiteSpace: 'pre-wrap', ...styles }}
  title={<div dangerouslySetInnerHTML={{ __html: title }} />}
  open={showTooltip}
>
  <div className="column-overflow__hidden">
    <div dangerouslySetInnerHTML={{ __html: title }} />
  </div>
</Tooltip>

复制功能集成

通过 Ant DesignTypography.Text 组件实现复制功能,没传copytext的情况下默认为title。为什么要设置titlecopyText两个API是因为,title可能是需要渲染html的,而我们复制的内容并不需要html

tsx 复制代码
{isShowCopyable && (
  <Typography.Text
    className="ml10"
    copyable={{
      text: title
    }}
  />
)}

使用示例

html 复制代码
// 基础使用
<ColumnOverflow title="这是一段很长的文本..." />

// 带HTML内容
<ColumnOverflow title="<span style='color: red'>带HTML样式的文本</span>" />

// 启用复制功能,自定义复制内容
<ColumnOverflow 
  title="<span style='color: red'>带样式的文本</span>"
  copyText="纯文本内容"
  isShowCopyable={true}
/>

// 垂直方向溢出检测
<ColumnOverflow 
  title="多行文本..."
  direction="vertical"
/>

组件完整代码

  • ColumnOverflow.tsx
tsx 复制代码
import React, { useState, useCallback } from 'react';

import { Tooltip, Typography } from 'antd';

import { ColumnOverflowStyle } from './styles';

import type { TooltipPlacement } from 'antd/lib/tooltip';

type Props = {
  title: string;
  copyText?: string;
  direction?: 'horizontal' | 'vertical';
  isShowCopyable?: boolean;
  styles?: object;
  placement?: TooltipPlacement;
};

const ColumnOverflow: React.FC<Props> = React.memo(
  ({
    title,
    copyText = title,
    direction = 'horizontal',
    isShowCopyable = false,
    styles = {},
    placement = 'top'
  }) => {
    const [showTooltip, setShowTooltip] = useState(false);

    const visibilityChange = useCallback(
      (event: any) => {
        const ev = event.target;
        let ev_size = ev.scrollWidth;
        let content_size = ev.clientWidth;
        if (direction === 'vertical') {
          ev_size = ev.scrollHeight;
          content_size = ev.clientHeight;
        }
        if (ev_size > content_size) {
          setShowTooltip(true);
        } else {
          setShowTooltip(false);
        }
      },
      [direction]
    );

    let copyableElement = null;
    if (isShowCopyable) {
      copyableElement = (
        <Typography.Text
          className="ml10"
          copyable={{
            text: copyText
          }}
        />
      );
    }

    const htmlDom = <div dangerouslySetInnerHTML={{ __html: title }} />;

    return (
      <ColumnOverflowStyle>
        <div
          className="column-overflow"
          onMouseEnter={visibilityChange}
          onMouseLeave={() => setShowTooltip(false)}
        >
          <div className="column-overflow__content">
            <Tooltip
              overlayStyle={{ ...styles }}
              overlayInnerStyle={{
                whiteSpace: 'pre-wrap'
              }}
              title={htmlDom}
              open={showTooltip}
              placement={placement}
            >
              <div className="column-overflow__hidden">{htmlDom}</div>
            </Tooltip>
          </div>
          {copyableElement}
        </div>
      </ColumnOverflowStyle>
    );
  }
);

export default ColumnOverflow;
  • 以上代码是antd 4.x的用法,antd 5.x废弃了overlayStyleoverlayInnerStyle的写法,将以下组件替换即可:
tsx 复制代码
<Tooltip
   styles={{
      root: { ...styles },
      body: {
        whiteSpace: 'pre-wrap'
      }
   }}
  title={htmlDom}
  open={showTooltip}
  placement={placement}
>
  <div className="column-overflow__hidden">{htmlDom}</div>
</Tooltip>
  • styles.ts见上文的布局实现

使用效果:

  • 使用前:
  • 使用后:antd 4.x

  • 使用后:antd 5.x

  • 渲染html内容

相关推荐
前端付豪几秒前
3、Node.js异步编程彻底吃透
前端·后端·node.js
孤鸿玉5 分钟前
[Flutter小试牛刀] 低配版signals,添加局部刷新
前端·flutter
亦黑迷失6 分钟前
轻量级 Express 服务器:用 Pug 模板引擎实现动态参数传递
前端·javascript·后端
吃瓜群众i1 小时前
理解Javascript闭包
前端·javascript
安大桃子1 小时前
Mapbox GL + Deck.gl 三维实战:Mapbox 加载 Tileset3D 倾斜摄影模型
前端·webgl
yede1 小时前
多行文本省略号显示,更多按钮展开全部
前端
就是我1 小时前
React 应用性能优化实战
前端·react.js·性能优化
G扇子1 小时前
深入解析XSS攻击:从原理到防御的全方位指南
前端·安全
snakeshe10101 小时前
入解析React性能优化策略:eagerState的工作原理
前端
六边形6661 小时前
Vue中的 ref、toRef 和 toRefs 有什么区别
前端·vue.js·面试