阅读 819

CSS&JavaScript:你究竟会几种多列布局?

α 产品经理有个需求-多列布局的实现

  • 产品-彦祖 :
    • 家辉啊,我需要一个这样的场景展示数据,可以 自定义列数 ,后端数据返回的就是数组,你看你前端咋弄啊
  • 切图仔-渣渣辉 :
    • 好的彦祖, 自定义多列 嘛简单。思考 ing: 我有一个数组 list,输入对应的列数 col,就可以展示对应的列数,大概的 demo 我都写好了在下面
const cols:number = 3;
const list:Array<any> = [1,2,3,4,5,6,7]
<MultiBox columns={cols} list={list}/>

// show list
1 | 2 | 3
4 | 5 | 6
7 |   |
复制代码

β 思考🤔 column-conut 实现

  • 多列展示,这还不简单嘛,flex 就是天然的多列啊,看了下文档发现不能自定义列数;想起常用的 column-count css 属性,先用字符串试试, 在实际使用数组的时候发现不好使;
  • 我们来看看 column-count 的定义
column-count CSS属性,描述元素的列数。
column-count: 3;
column-count: auto;

Block containers except table wrapper boxes
复制代码

定义: 是个严格的正数 ,用来描述元素内容被划分的理想列数. 假如 column-width (en-US)也被设置为非零值, 此参数仅表示所允许的 "最大列数"

• 注意上面的 最大列数 这里就是 了,你指定的 number 并不一定是现在的列数,而是最大列数

import React from "react";
type IMultiBoxProps = {
  cols?: number;
  list: string | Array<any>;
};

const MultiBox = (props: IMultiBoxProps) => {
  const { cols = 1, list } = props;
  return (
    <div
      style={{
        columnCount: cols
      }}
    >
      {Array.isArray(list) ? list.join("") : list}
    </div>
  );
};

export default function App() {
 const defaultMultiBox1Props = {
    cols: 3,
    list:
      "当我年轻的时候,我梦想改变这个世界;当我成熟以后,我发现我不能够改变这个世界,我将目光缩短了些,决定只改变我"
  };
  const defaultMultiBox2Props = {
    cols: 3,
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9]
  };

  return (
    <div className="App">
      <h1>Test MultiBox</h1>
      <MultiBox {...defaultMultiBox1Props} />
      <MultiBox {...defaultMultiBox2Props} />
    </div>
  );
}
复制代码

改进

  • 既然 div 不行,ul>li 的文字排列可以吧, 把 list 的内容放进 li 中,想到了就做;初看没问题,搞定;
const MultiBox = (props: IMultiBoxProps) => {
  const { cols = 1, list } = props;

  return (
    <div>
        <ul style={{
            columnCount: cols
        }}>
            {list.map(val => <li>{val}</li>)}
        </ul>
    </div>
  );
};
复制代码
column-count 简易算法
  • 彦祖:渣渣辉啊,你这个 5 列的时候有 bug 啊!是不是我操作有问题?
list = [1, 2, 3, 4, 5, 6, 7, 8, 9];

<MultiBox cols={3} list={list} />

// 3 列没问题
| 1 | 4 | 7 |
| 2 | 5 | 8 |
| 3 | 6 | 9 |

<MultiBox cols={6} list={list} />

// 只有 5 列 ?
| 1 | 3 | 5 | 7 | 9
| 2 | 4 | 6 | 8 |
复制代码

这里就要提到上面的 此参数仅表示所允许的 "最大列数" 这个坑了

  • 我们来看 column-count 的计算方法, 首先计算每列可以承载最大的 items 数, 我们来试着模拟计算一下
const cols = 6;
const list = [1,2,3,4,5,6,7,8,9];
const result = [];
const countSize = Math.ceil(list.length / cols); // 2

for (let i = 0, len = list.length; i < len; i += countSize) {
    result.push(list.slice(i, i + countSize));
}
// [1,2]
// [3,4]
// [5,6]
// [7,8]
// [9]
复制代码
反思 chunk 二维数组实现
  • 可以看到上面只有 5 列,问题就出现了,当我们的承载数足够的时候,这种按位优先补偿的算法就有明显的问题,但是也符合上面提到的最大列数原则,但是这里 彦祖 可是要求符合 cols 的数量。
  • column-count 不能满足,既然我们已经透析了它的算法为什么不改进一下啦?col 和 row 的网格布局,本质可以看做一个二维数组,我们可以先搞一个 table[cols] 的数组,然后按照每列插值保证列数优先,就安全了,能保证定义多少列就展示多少列。
const cols = 6;
const list = [1,2,3,4,5,6,7,8,9];

function chunker(cols, lists){
    const table = new Array(cols); 
    for (let i = 0; i < table.length; i++) {
        table[i] = new Array(); 
    }

    while (lists.length > 0) {
        // insert one rows
        for (let col = 0; col < table.length; col++) {
            table[col].push(lists.shift());
        }
    }
    return table.filter(Boolean);
}
chunker(cols, list)

// [1,7]
// [2,8]
// [3,9]
// [4]
// [5]
// [6]
复制代码
  • css 和 tsx 模块实现
// css
.flex-direction-column{
    flex-direction: column;
}

.flex {
    display: flex;
}

// tsx
const MultiBox = (props: IMultiBoxProps) => {
  const { cols = 1, list } = props;

  const chunks = chunker(cols, list);

  return (
    <div className="flex">
        {chunks.map(col => 
            <div className="flex-direction-column">
                {col.map(item => item)}
            </div>
        )}
    </div>
  );
};
复制代码

γ gird 布局 实现

  • 哪还有什么 css 属性能直接展示自定义多列啦?除了 flex,当然还有 gird 网格布局了,这下马上去看了 gird 的属性文档;
    • 遇事不决 阮一峰教程
    • 发现了这样一个属性,这不完美解决了多列布局的问题嘛
.gird-layout {
  display: grid;
  grid-template-columns: repeat(3, 33.33%); 
  // 三列布局
}
复制代码
  • 完整代码(伪代码)
const MultiBox = (props: IMultiBoxProps) => {
  const { cols = 1, list } = props;
  
  return (
    <div
      style={{
        display: 'gird',
        gridTemplateColumns: repeat(cols, 1/cols);
      }}
    >
      {list.map( item => <div>{item}</div>)}
    </div>
  );
};
复制代码
  • 留个问题:这里的 gridTemplateColumns: repeat(cols, 1/cols); repeat 不能直接使用, gird 方法是找到了,有什么优雅的方法解决这个问题啦?
文章分类
前端
文章标签