React 实现响应式tag列表

前言

最近ui小姐姐又给我出了一个难题,首先有一个tag列表,像下面这样

我们要做的呢就是让这些tag只显示第一行,其余的显示数字,就像这样

这可把我给难住了啊,我只是个组件的搬运工😭,为什么要承受这些,但是我又不能直接说我不会,那岂不是显得我很菜,这怎么行呢,于是我就满口答应了,这才有了这篇文章.

老规矩,先来看一下最终的实现效果,看起来还可以,勉强可以交差了,下面给同学们分享下我的思路,如果大家有更好的思路,欢迎与我分享.

实现过程

第一步

首先,我们需要知道第一行可以显示几个tag,问题在于怎么知道tag换行了呢,这可难不住我,从图中可以看出,第一行元素的offsetTop是为0的,我只需要找出第一个offsetTop大于0的元素,不就知道第一行能放下几个tag了嘛,于是写出来下面这个核心方法,用来计算从第几个tag开始换行的.

js 复制代码
const getLen = useCallback(() => {
    const nodes = Array.from(ref.current!.childNodes) as HTMLSpanElement[];
    const len = nodes.length;
    for (let i = 0; i < len; i++) {
      if (nodes[i].offsetTop > 10) {
        setMaxTagCount(i);
        setStyle({
          maxHeight: nodes[0].offsetHeight,
          overflow: 'hidden'
        });
        setLeft(nodes[i - 1].offsetLeft + nodes[i - 1].clientWidth); // 算出统计tag左边的距离
        break;
      }
    }
  }, []);

这里的setStyle是可以省略的,我是为了兼容所以动态设置的行高,setLeft是为了计算统计tag距离左边的距离,也就是第一行最后一个tag的offsetLeft加上自身的宽度.

响应式

第二步我们开始考虑优化,也就是响应式,这个思路也很简单,当包裹tag列表的元素宽度发生改变时,我们再调用一次'getLen'方法就可以了,至于监听元素改变用'resize-observer-polyfill'这个库就可以了,性能方面的我也没有去测试, 至少目前能用就行(😄),下面是完整的代码

js 复制代码
import React, { useRef, useState, CSSProperties, memo, useCallback, useLayoutEffect } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import { throttle } from 'lodash-es';
import styles from './index.module.less';

interface IProps {
  className?: string;
  data: string[];
}
export const TagList = memo(({ className, data = [] }: IProps) => {
  const ref = useRef<HTMLDivElement>(null);
  const [style, setStyle] = useState<CSSProperties>({});
  const [maxTagCount, setMaxTagCount] = useState<number>(0);
  const [left, setLeft] = useState(0);
  const getLen = useCallback(() => {
    const nodes = Array.from(ref.current!.childNodes) as HTMLSpanElement[];
    const len = nodes.length;
    for (let i = 0; i < len; i++) {
      if (nodes[i].offsetTop > 10) {
        setMaxTagCount(i);
        setStyle({
          maxHeight: nodes[0].offsetHeight,
          overflow: 'hidden'
        });
        setLeft(nodes[i - 1].offsetLeft + nodes[i - 1].clientWidth); // 算出统计tag左边的距离
        break;
      }
    }
  }, []);
  useLayoutEffect(() => {
    if (!ref.current) return;
    const observe = new ResizeObserver(throttle(getLen, 300));
    observe.observe(ref.current);
    return () => {
      observe.disconnect();
    };
  }, [getLen]);
  return (
    <div className={`${styles['tag-wrap']}`} ref={ref} style={style}>
      {data.map((item, index) => (
        <span key={item} title={item} className={`bixi-custom-tag ${className || ''}`}>
          {item}
        </span>
      ))}
      {!!maxTagCount && (
        <span className={`bixi-count-tag  ${className || ''}`} style={{ left: `${left + 8}px` }}>{`+${data.length - maxTagCount}`}</span>
      )}
      {!data.length && '--'}
    </div>
  );
});

我这里用的useLayoutEffect是为了在dom渲染到页面前就计算出tag位置,防止dom样式改变带来的闪烁影响,但是实测用useEffect效果也差不多,看不到闪烁效果.

less代码

less 复制代码
.tag-wrap {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding-right: 50px;

  :global {
    .bixi-custom-tag {
      max-width: 150px;
      padding: 2px 8px;
      overflow: hidden;
      color: #324558;
      font-weight: 400;
      line-height: 20px;
      white-space: nowrap;
      text-overflow: ellipsis;
      background: #f5f7fa;
      border-radius: 2px;
      cursor: pointer;
    }

    .bixi-count-tag {
      position: absolute;
      padding: 2px 8px;
      color: #324558;
      font-weight: 400;
      line-height: 20px;
      background: #f5f7fa;
      border-radius: 2px;
      cursor: pointer;
    }
  }
}

插曲

在写这篇文章的时候,我突发奇想,这个需求应该可以用antd Select的响应式设计来实现

我们只需要改改样式就可以了(初心不改,回归组件搬运工),这里就没有实现了,毕竟文章都水了一篇,感兴趣的同学可以自己去实现一下.

相关推荐
wangxiaoxuan2173 小时前
SLAM 的基本结构:前端、后端、回环与地图优化
前端·机器人·科沃斯八界
Agent世界6 小时前
用 CDP 做发布自动化:四道机器闸门与产物验收
前端·javascript
FungLeo6 小时前
成为全栈·Vue 管理后台篇·编辑器图片上传:从文件选择到文章附件关联
前端·vue.js·编辑器·markdown·文件上传·formdata·附件管理
某哈压力大6 小时前
SSH 突然 Connection reset?不是密钥坏了,是 OpenSSL 把 sshd 干趴了
前端·node.js·命令行
程序员大阳6 小时前
副队长ES6+教程(3)--解构赋值
前端·javascript·es6·赋值·解构
神膘护体小月半6 小时前
css - 元素背景图由 hover 触发,初次加载时闪动的解决办法
前端·javascript·css
程序员大阳7 小时前
副队长ES6+教程(2)--let 与 const
前端·javascript·es6·const·let·var
deli0077 小时前
写满 200GB 后 SSD 为什么掉到 95MB/s?SLC 缓存模型实测
前端
曦樂~8 小时前
【Web Development】03JavaScript 基础和事件
开发语言·前端·javascript
deli0078 小时前
HTTPS 的锁是怎么挂上的?TLS 1.3 握手 6 步实测
前端