多列不等高布局之横向瀑布流和纵向瀑布流

背景:项目中本来使用的是multi-columns多列布局,但是后来随着业务的增加,布局中的元素的高度不是固定的了,而是增加了一个Antd的Collapse的展开折叠操作。这种情况下,使用纵向的multi-columns布局就使得元素高度变化比较大时,就会随着高度自动重排,有时会换列,这样的话操作复杂性就很高,不利于用户使用,故修改为横向瀑布流的方式。以下对两种方式进行一个简单的介绍和记录。

一、 multi-columns多栏布局---纵向瀑布流

1. 优点:

纯css写法,省时省力

2. 缺点:

排列顺序只能从上到下,再从左到右。只能用于数据固定, 无法动态的加载追加,对于滚动到底部加载新数据则无法实现。

3. 两个重要属性:

column-count : 定义列数
column-gap :列与列之间的间隔

4. 注意点:

有时候页面会出现在前几列的最后一个元素的内容被自动断开,一部分在当前列尾,一部分在下一列的列头。这时候子元素可以用 break-inside设置为不被截断 avoid来控制。

 break-inside: avoid; // 不被截断 默认值是auto,会被截断

5. 实现代码:
.card{
      column-count: 3; // 定义三列
      column-gap: 20px; // 列与列的距离为20px

      .card-item{
        text-align: center;
        width: 216px;
        border-radius: 16px;
        grid-row-start: auto;
        margin-bottom: 20px;
        break-inside: avoid; // 不被截断
      }
  }

 二、 横向瀑布流---泳道设计

1. 优点:

横向布局,更符合使用习惯

2. 缺点:

实现复杂度较高,元素较多时,计算量较大,需要考虑重复渲染的问题

3. 实现思路:

用flex弹性布局+计算元素高度实现布局。第一列元素排列之后,每次元素的追加都选择当前高度最小的那一列。

4. 算法思路:通过上面的分析则能了解瀑布流的思路了
  • 设计要分成的列数
  • 设置每列的 宽度一致
  • 每次插入的位置选择所有列高度最小 的位置,依次循环
5. 代码实现思路:
  • 由于是使用Collapse折叠结构,则计算时直接利用其子元素的个数来进行高度比较
  • 多少列则定义多少新的空数组,然后根据瀑布流的思路依次插入到空的数组即可
6. 代码实现

结构:

<div className={styles.flexLeftList}>
     <div className={styles.columnItem}>
         {generateDOM(columnListLeft.current)}
     </div>
     <div className={styles.columnItem}>
          {generateDOM(columnListCenter.current)}
     </div>
     <div className={styles.columnItem}>
          {generateDOM(columnListRight.current)}
     </div>
</div>

const generateDOM = (list) => (
      list.map((item) => {
        return (
          <div className={styles.module} key={item.id} id={item.id}>
            {/* 二级菜单名称 */}
            <div className={`${styles.title}>
              {item.name}
            </div>
            {/* 三级菜单 */}
            {item?.children?.map((childItem) => {
              return (
                <div style={{display: 'block'}}>
                    <Item
                      type={ItemType}
                      item={childItem}
                      key={childItem.id}
                    />
                </div>
              );
            })}
          </div>
        );
      })
    )

js:

    // 计算flex布局每列的数据项
    const caLFlex = (mList) => {
      if(!mList.length) return;
      let arrLeft: IMenuItem[] = []; // 第一列的数据
      let arrCenter: IMenuItem[] = []; // 第二列的数据
      let arrRight: IMenuItem[] = []; // 第二列的数据
      let countArray_left: number[] = []; // 根据数量判断高度
      let countArray_center: number[] = [];
      let countArray_right: number[] = [];

      mList.forEach((item,index) =>{
        if(index === 0){ // 第一行中的元素无需判断,直接加到新的数组中
          countArray_left.push(item?.children?.length ?? 0);
          arrLeft.push(item);
          return;
        }
        if(index === 1){
          countArray_center.push(item?.children?.length ?? 0);
          arrCenter.push(item);
          return;
        }
        if(index === 2){
          countArray_right.push(item?.children?.length ?? 0);
          arrRight.push(item);
          return;
        }

        // 累计各行元素数目
        const countTotal_left = countArray_left.length ? Array.from(countArray_left).reduce(( acc, cur ) => acc + cur) : 0; // 第一列的总个数
        const countTotal_center = countArray_center.length ? Array.from(countArray_center).reduce(( acc, cur ) => acc + cur) : 0; // 第二列的总个数
        const countTotal_right = countArray_right.length ? Array.from(countArray_right).reduce(( acc, cur ) => acc + cur) : 0; // 第三列的总个数
  
        // 找到最小值
        let minNumber = Math.min(countTotal_left,countTotal_center,countTotal_right);
        switch (minNumber) {
          case countTotal_left:
            countArray_left.push(item?.children?.length ?? 0);
            arrLeft.push(item);
            break;
          case countTotal_center:
            countArray_center.push(item?.children?.length ?? 0);
            arrCenter.push(item);
            break;
          case countTotal_right:
            countArray_right.push(item?.children?.length ?? 0);
            arrRight.push(item);
            break;
         }
      });
    
      // 重新将数据赋值给各列数组
      columnListLeft.current = arrLeft;
      columnListCenter.current = arrCenter;
      columnListRight.current = arrRight;
    };

css:

    // flex横向布局
    .flex-left-list {
      width: 100%;
      min-height: 100%;
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      justify-content: space-around;
      .column-item{
        width: calc(100% / 3);
        .module{
          display: flex;
          flex-direction: column;
          height: fit-content;
          padding: 0 16px 30px 16px;
          .title {
            font-size: 16px;
            color: rgba(0,0,0,0.85);
            margin-bottom: 10px;
            padding-left: 14px;
          }
        }
      }
    }


参考链接:https://blog.csdn.net/Smile_666666/article/details/124728121 (大哥讲解更详细,有需要可以去看,同时多谢大哥提供思路~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值