教你实现Grid网络布局的纯CSS框架

引言

我一直都用了Bootstrap和jQuery来开发WordPress,直到2021年了,总觉得jQuery这种东西应该被移除了吧,于是开始考虑自己实现了一些常用的jQuery命令,如add/removeClass、slideUp/Down、事件代理等等。后来一想,既然jQuery都移除了,那么Bootstrap是不是也可以移除了?毕竟只用它的栅格系统来布局而已。于是萌生了自己编写一个布局系统的想法。

网络上有很多人也在列举“你不需要基于 CSS Grid 的栅格布局系统”的理由,也有人发表“传统写法无法发挥 CSS Grid 的全部优势,至少用以往的组合类方式是无法做到的”观点。确实,Grid的Area命名这一块纯CSS很难做到,但如果我只是为了移除Bootstrap而选择一种更先进的布局系统的话,那也是完全可以有的。

本文描述了使用SASS预处理器实现Grid布局CSS框架的一种思路,并不是讲解Grid布局如何使用的教程,如果你还不知道Grid是布局是什么,请阅读《MDN:CSS Grid Layout》

解决的问题

之所以我想要在开发WordPress的时候移除Bootstrap,还是因为它对“列”的设置太死板了。Bootstrap每行可以排列12、6、4、3、2、1个元素,然而在实际应用中,4个会太少,6个会太多,偏偏想要每行排列5个怎么办?

这个问题在flex布局也是一样的存在,都需要重新计算宽度。然而在Grid中就简单多了,只要设置 grid-template-columns 就可以了。

想要的效果

在开发前,我通常会反向思考,如果有这么一个框架,它怎么用起来方便?然后再根据使用的方式去开发。

基础效果

根据上面提到想要解决的问题,因此我希望使用的方法是这样的:

  • 在包裹器上设置列数(cols-n)
  • 内部的元素上设置元素宽度(col-n)
<!-- 包裹器设置共6列 -->
<div class="cols-6">
    <!-- 占1列宽度 -->
    <div class="col-1">列元素</div>
    <!-- 占2列宽度 -->
    <div class="col-2">列元素</div>
    <!-- 占3列宽度 -->
    <div class="col-3">列元素</div>
</div>
<div class="cols-8">

    <!-- 从轨道2~轨道4均为该列 -->
    <div class="col-start-2 col-end-4">列元素</div>

    <!-- 从轨道6~轨道7均为该列 -->
    <div class="col-start-6 col-end-7">列元素</div>
</div>

手动定位

使用的时候除了定义列占多少宽度自动排以外,还要求可以设置列的起止网格轨道来排列

*关于网络轨道请阅读《MDN:Grid Tracks》

响应式支持

Bootstrap的响应式写法是 col-md-6 这样把响应点写在中间的方式,经常在看代码的时候会看得眼花缭乱,于是我在实现Grid布局的时候就考虑把它提到前面来做

Bootstrap方式我想要的方式
col-sm-7sm:col-7
col-md-6md:col-6

<!-- 包裹器默认6列,md及以上显示8列 -->
<div class="cols-6 md:cols-8">
    <div class="col-1">列元素</div> 
    <div class="col-2">列元素</div>

    <!-- 默认3个列宽度,md及以上占4个列宽度 -->
    <div class="col-3 md:col-2">列元素</div>

</div>

那么开始吧

由于我们有响应式,首先我们先创建一个Array来存放所有的样式,方便最后根据响应式分界点编译

$gridCSS:();

包裹器设置总列数

考虑使用“cols-n”的方式来标记包裹器的列,考虑最多允许12列此处使用grid-template-columns属性

$gridCSS: join($gridCSS, (cols-0: (display:grid, grid-template-columns: none)) );
@each $item in (1,2,3,4,5,6,7,8,9,10,11,12) {
    $gridCSS: join($gridCSS, (cols-#{$item}: (display:grid, grid-template-columns:"repeat(#{$item}, 1fr)")) );
}

包裹器设置间距

在Grid中间距是用gap来实现的,为了方便,我考虑用“g-n”的方式来标记此处使用 gap 属性:

  • 四周间距标记:g-n
  • 水平间距标记:gx-n
  • 垂直间距标记:gy-n

* 间距默认有0~50的整十像素

@each $item in (0,10,20,30,40,50) {
    $gridCSS: join($gridCSS, (g-#{$item}: (gap:"#{$item}px")) );
    $gridCSS: join($gridCSS, (gx-#{$item}: (column-gap:"#{$item}px")) );
    $gridCSS: join($gridCSS, (gy-#{$item}: (row-gap:"#{$item}px")) );
}

子元素设置占用列宽

和包裹器设置总列数类似,子元素最多允许12列此处使用“col-n”来标记它

@each $item in (1,2,3,4,5,6,7,8,9,10,11,12) {
    $gridCSS: join($gridCSS, (col-#{$item}: (grid-column:"span #{$item} / span #{$item}")) );
}

子元素设置起止轨道

子元素除了使用上面的 col-n 来标记列宽外,还可以设置起止轨道来定义宽度并且还能定义位置。此处我们使用下面标记来定义它

  • 列起始轨道:col-start-n
  • 列终止轨道:col-end-n
  • 行起始轨道:row-start-n
  • 行终止轨道:row-end-n
@each $item in (1,2,3,4,5,6,7,8,9,10,11,12,13) {
    $gridCSS: join($gridCSS, (col-start-#{$item}: (grid-column-start:#{$item})) );
    $gridCSS: join($gridCSS, (col-end-#{$item}: (grid-column-end:#{$item})) );
    $gridCSS: join($gridCSS, (row-start-#{$item}: (grid-row-start:#{$item})) );
    $gridCSS: join($gridCSS, (row-end-#{$item}: (grid-row-end:#{$item})) );
}

至此,Grid基本的内容就设置好了。我们把刚才定义的 $gridCSS 渲染出来

@each $className, $classStyle in $gridCSS {
    .#{$className} {
        @each $styleKey, $styleVal in $classStyle {
            @each $val in $styleVal {
                #{$styleKey} : #{$val}
            }
        }
    }
}

响应式

设置定义响应分界点,并基于分界点生成媒体查询

$breakpoints: (576px,768px,992px,1200px);
$medias: (
    "xxs": "(max-width: #{nth($breakpoints,1) - 1px})",
    "xs": "(min-width: #{nth($breakpoints,1)})",
    "sm": "(min-width: #{nth($breakpoints,2)})",
    "md": "(min-width: #{nth($breakpoints,3)})",
    "lg": "(min-width: #{nth($breakpoints,4)})"
);

渲染出响应式媒体查询部分,顺手把container也加上吧,更方便使用

.container {
    width: 100%;
    margin-left: auto;
    margin-right: auto;
}

$mediaIndex: 1;
@each $mediaName, $mediaRange in $medias {
    @media #{$mediaRange} {
        @each $className, $classStyle in $gridCSS {
            .#{$mediaName}\:#{$className} {
                @each $styleKey, $styleVal in $classStyle {
                    @each $val in $styleVal {
                        #{$styleKey} : #{$val}
                    }
                }
            }
        }
        @if ($mediaIndex >= 2){
            .container {
                max-width: #{nth($breakpoints, $mediaIndex - 1)}
            }
        }
    }
    $mediaIndex: $mediaIndex + 1;
}

至此,一个简单的Grid布局就基本完成了。本文提供了一个基本的Grid布局实现思路,还可以在此基础上扩展成自己更顺手的工具。

如何使用

例如:要生成一个7列的布局,子元素之间间隔30像素;第一个子元素占用3列,第二个子元素占用剩下的全部。并且在手机屏幕上,它们都水平排列。就可以这样写:

<div class="container cols-7 g-30">
    <div class="col-7 sm:col-3">第一个子元素</div>
    <div class="col-7 sm:col-4">第二个子元素</div>
</div>

下载?

本文并没有提供任何下载,前面的代码示例已经将SASS代码的步骤全部展示出来了,你只需要根据自己的使用习惯手动编译一遍(或直接编译)就可以使用了,这里做了个Codepen示例: