效果

指定列数为3,自适应最小列数为2,最小列宽为300px;更多效果可查看博客mdx组件文章

1
2
3
需求
  • 实现一个组件,通过指定列数,用于分栏展示
  • 在窄屏下,自动减少列数
  • 如果有子项横跨多列,则提供minCol用于指定布局最小列数
初步实现n栏、自适应

简单的分栏,可直接根据grid布局实现,代码如下:

/* n栏布局 */
.grid {
    display: grid;
    grid-template-columns: repeat(n, 1fr);
    grid-gap: 10px;
}

样例:

1
2
3

通过指定n,即可实现n栏布局。这样基本效果就实现了,但是对于博客内容而言,在窄屏下,如果栏数过多,就会导致内容过于拥挤。 于是我们可以传入一个minWidth值,让每一列的最小宽度为minWidth,即可通过自适应屏幕宽度来分配列数,达到窄屏下自动减少列数的目的,代码也很简单:

/* 设定最小列宽为200px,如此当n列 列宽之和超过父容器长度时就会自动分配 */
.grid {
    --minWidth: 200px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--minWidth), 1fr));
    grid-gap: 10px;
}

显而易见,上面的代码虽然实现了自适应,但是并没有提到列数n,即在屏幕很宽的时候会自适应出来很多列(现在假设我们需要的是3列) 样例:

1
2
3
4
5
6
结合自适应与指定列数

现在,需求就成为了在保持自适应的同时,限制列数为n,即在屏幕很宽的时候,自适应出来的列数也不会超过n

在这里,我们可以思考上述代码中的列数是怎么计算出来的:

  • 已知条件中,我们传入了一个最小宽度minWidth
  • 而repeat函数以及minmax根据传入的最小宽度,会先按照最小宽度计算出可容纳的最多列数,即(父容器宽度100% / minWidth)取整;然后再去根据列数排列
  • 那么按照上面的计算过程,我们能够控制的也就是这个最小宽度minWidth,即我们可以通过更改传入的minWidth来控制列数
  • 当然,这里更改的是传入的minWidth,而不是用户传给组件的minWidth;所以之后,将传入repeat的minWidth都记为newMinWidth

因为要满足两个要求

  1. 屏幕宽度足够时,列数为n
  2. 屏幕宽度不足时,列数根据最小宽度自适应

那么根据第一个情况,我们可以通过计算得出每一列的宽度,即(父容器宽度100% - (n-1)*gap) / n,记为minWidth1,然后将这个宽度作为newMinWidth传入repeat函数,即可实现列数为n的效果 按照第二个情况,直接传入minWidth即可实现满足最小宽度的自适应

在第二种情况下,按照第一种情况计算出来的minWidth1是肯定会比minWidth小的(因为计算出来之后不满足最小宽度才进入第二种情况)。所以通过取两者的最大值即可实现两种情况的兼容

.grid {
    --n: 3;
    --gap: 10px;
    --minWidth: 300px;
    display: grid;
    grid-template-columns: repeat(
        auto-fit,
        minmax(
            max(
                calc(100% / var(--n) - var(--gap) * (var(--n) - 1) / var(--n)),
                var(--minWidth)
            ),
            1fr
        )
    );
    grid-gap: var(--gap);
}

样例

1
2
3
4
5
6
满足元素横跨多列

在上述代码中,我们已经实现了n栏布局以及最小宽度的自适应。但是,如果子元素有一个横跨多列,那么就会出现一种情况:在窄屏下,通过自适应,列数到了1,但子元素需要跨m列,就会出现溢出的情况(会导致多余的gap出现,并且溢出的列宽度都为0) 此时,就需要手动指定自适应后的最小列数minCol,即在窄屏下,至少要有m列,这样就能保证子元素不会溢出(实际开发肯定不会出现溢出问题,因为代码都是手写的可以随意更改(如窄屏可通过媒体查询指定其他样式等等),但这里是由组件形式提供使用,所以需要考虑溢出的情况)

样例(试着将屏幕拉窄):

1
2
3

同样按照上面实现最大列数为n,来实现最小列数为m的效果,上面的情形可分为:

  • 屏幕宽度足够时,列数为n(同上计算)1
  • 屏幕宽度不足时,列数根据最小宽度自适应
    • 自适应后的列数小于m,此时需要手动指定列数为m(新增情况)2
    • 自适应后的列数大于等于m,此时不需要手动指定列数(同上计算)3

对于这个新增情况,我们可以通过计算得出每一列的宽度,即(父容器宽度100% - (m-1)*gap) / m,记为minWidth2然后将这个宽度作为newMinWidth传入repeat函数,即可实现列数为m的效果 可以想到,所有情况中minWidth2>minWidth1,在2情况下,minWidth2小于minWidth;在3情况下,minWidth2大于等于minWidth1

  • 情况1:取min(minWidth2, max(minWidth, minWidth1))得到minWidth1;(此时关系为minWidth2 > minWidth1 > minWidth)
  • 情况2:按照之前的操作,可取min(minWidth2, max(minWidth, minWidth1))得到minWidth2;(此时关系为minWidth2 < minWidth, minWidth1 <= minWidth)
  • 情况3:取min(minWidth2, max(minWidth, minWidth1))得到minWidth;(此时关系为minWidth2 >= minWidth > minWidth1)

所以三种情况都可以通过取min(minWidth2, max(minWidth, minWidth1))得到最终的newMinWidth

.grid {
    --n: 3;
    --gap: 10px;
    --minWidth: 300px;
    --minCol: 2;
    display: grid;
    grid-template-columns: repeat(
        auto-fit,
        minmax(
            min(
                calc((100% - (var(--minCol) - 1) * var(--gap)) / var(--minCol)),
                max(
                    calc(100% / var(--n) - var(--gap) * (var(--n) - 1) / var(--n)),
                    var(--minWidth)
                )
            ),
            1fr
        )
    );
    grid-gap: var(--gap);
}

样例(试着将屏幕拉窄):

1
2
3
最后的题外话

上面代码中用的是auto-fit,实际也可以用到auto-fill,两者在宽屏上的效果略微不同:参考 auto-fill与auto-fit

DORAKIKA
我是一个喜欢折腾的前端工程师,对一切新鲜事物充满好奇,希望我的文章能给你带来思考和帮助
👋我是DORAKIKA
分享作者『DORAKIKA』发表的文章『grid布局实现自适应分栏组件』https://blog.dorakika.cn/post/20230407/
© 请您在需要时著名本文内容来源信息,若在文末注明“参考、扩展”等字样涉及转载第三方内容,请您一同复制
元素focus与键盘交互
元素focus与键盘交互
在html中,某些元素如input、textarea、button、a等,在获取焦点时,会进入focus状态,这些元素可被称之为焦点元素。而通过键盘的tab键,可以快速切换焦点元素,再配合enter、方向键等键盘操作,可以实现不使用鼠标也可以很好的完成页面交互。
Astro:集多功能与一体的web框架
Astro:集多功能与一体的web框架
Astro是一个旨在构建以内容为优先的web框架。相比之下next.js\nuxt.js\React\Vue等框架更趋向于构建“应用程序式”的交互式站点,而Astro更关注站点构建的速度与站点的内容