关于多tag搜索换行场景的展开折叠实现

近期做了一个多行tag搜索的需求,要求是最多展示两行,超过两行则隐藏,并展示一个 expand all 的按钮,具体效果如下

注意,这里并不是文字的展开收缩,所以css自带的展开收缩样式是不太适合的

核心要点

定位第二行最后一个元素的下标

首先,布局肯定是用flex布局,并且支持换行,核心部分就是需要找到第二行最后一个元素的下标

将第二行最后一个元素设置为 visibility: hidden;

目的是避免用expand all替换最后一个元素之后造成布局的改变,避免第三行的元素被挤到第二行expand all 后面

expand all 按钮绝对定位到第二行最后一个子元素旁边

在不影响原本tags列表布局情况的同时将expand all加上去

子元素宽度相加计算【不建议】

经过一番查找,在社区里学到的一种解决方案是通过计算子元素的宽度以及视图宽度来确定子元素是否换行,并且记录当前行数,当前行数进行到第三行时,就可以知道第二行最后一个元素的下标

这种方式理论上是可以的,但是实践过程中,常常出现expand all后面多出一个tag的情况,这种情况的原因我猜测是因为宽度计算的准确性没法保证

通过子元素的定位计算【推荐】

首先,子元素是横向排布,理论上每一行的子元素的y坐标是一样的【事实证明也会有一点点偏差,比如border之类样式的影响,所以最好有个5px的容错比较】

只需要循环子元素,通过 offsetTop 比较,就可以计算出第二行的最后一个元素

expand all 元素后面增加 <div style={{ width: "100%" }}></div> 可以避免后面的元素被挤上来,强制flex换行

核心代码

js 复制代码
  function getLastElementOfSecondRow() {
    const container = document.querySelector<HTMLElement>(".tags");
    if (!container) return;
    const containerWidth = container.offsetWidth;

    let lastIndex = 100;
    let currnetY = 0;
    let row = 0;

  
    if (containerWidth !== null) {
      const items = container.querySelectorAll<HTMLElement>(".tag-item");

      for (let i = 0; i < items.length; i++) {
        const item = items[i];
        if (Math.abs(currnetY - item.offsetTop) > 20) {
          currnetY = item.offsetTop;
          row++;
        }

        if (row > 2) {
          lastIndex = i - 1;
          break;
        }
      }
    }
    if (row <= 2) setExpand(false);

    setSectionRowIndex(lastIndex - 1);
  }

组件渲染

js 复制代码
<div className="tags flex flex-row flex-wrap">
 {list.map((item, index) => {
          if (index === secondRowIndex)
            return (
              <>
                <div
                  className="tag-item"
                  style={{
                    position: "relative",
                    minWidth: "112px",
                    height: "40px",
                  }}
                >
                  <button
                    key="expand"
                    style={{ position: "absolute", left: 0 }}
                    onClick={() => setExpand(!expand)}
                  >
                      + Expand all
                  </button>
                  <button
                    key={index}
                    style={{ visibility: "hidden" }}
                  >
                      {item.nickname}
                  </button>
                </div>
                <div style={{ width: "100%" }}></div>
              </>
            );
          return (
            <button
              className="tag-item"
              key={index}
            >
                {item.nickname}
            </button>
          );
        })}
</div>
相关推荐
北极糊的狐2 小时前
Vue3 中父子组件传参是组件通信的核心场景,需遵循「父传子靠 Props,子传父靠自定义事件」的原则,以下是资料总结
前端·javascript·vue.js
看到我请叫我铁锤3 小时前
vue3中THINGJS初始化步骤
前端·javascript·vue.js·3d
q***25213 小时前
SpringMVC 请求参数接收
前端·javascript·算法
q***33373 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
烛阴3 小时前
从`new()`到`.DoSomething()`:一篇讲透C#方法与构造函数的终极指南
前端·c#
还债大湿兄3 小时前
阿里通义千问调用图像大模型生成轮动漫风格 python调用
开发语言·前端·python
谢尔登3 小时前
defineProperty如何弥补数组响应式不足的缺陷
前端·javascript·vue.js
蓝瑟忧伤4 小时前
前端技术新十年:从工程体系到智能化开发的全景演进
前端
Baklib梅梅4 小时前
员工手册:保障运营一致性与提升组织效率的核心载体
前端·ruby on rails·前端框架·ruby
IT_陈寒4 小时前
Redis性能翻倍的5个冷门技巧,90%开发者都不知道第3个!
前端·人工智能·后端