近期做了一个多行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>