挑逗Bootstrap4源代码 - Grid篇(下)

本文所引用的版本为Bootstrap 4 Beta版,阅读者请先下载好相关源文件。

时光荏苒,若后续版本代码发生变化,将看心情进行更新补充。如果你觉得本文不错,欢迎评论支持,如需转载请标明作者及出处,谢谢。

在日常使用Bootstrap的时候,我们最常见的做法是给HTML内的元素添加上预设的类名,这种方法直观且易于调试。但是对于一个前端洁癖患者来说,在HTML标签内添加一大堆类名简直和内联style一样让人深恶痛绝。那么在这种时候,学会使用Bootstrap的Scss,利用其内置的函数和@mixin来对你自己命名的类进行样式添加就成了一件很棒很酷的事。

涉及文件

  • 变量:_variables.scss(起始行:171,结束行:205)
  • 函数:_function.scss //其中函数主要用于变量文件中,在此不述
  • 公共类:_flex.scss //在utilities文件夹下,用于flex布局的各种类,只是给属性加了包装,同样不述
  • @mixin:

    • _breakpoints.scss //断点函数区,包括断点区间查找、自动扩展媒体查询等功能
    • _grid.scss //辅助mixin,提供容器、行、列创建
    • _grid-framworks.scss //核心mixin,依据断点,循环创建以flex为基础的12网格
  • 引用:_grid.scss //自动创建包括12列网格在内的布局,本质上是对_grid-frameworks和_grid的引用
  1. _grid-frameworks.scss
@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {
  // Common properties for all breakpoints
  %grid-column {
    position: relative;
    width: 100%;
    min-height: 1px; // Prevent columns from collapsing when empty
    padding-right: ($gutter / 2);
    padding-left:  ($gutter / 2);
  }

  @each $breakpoint in map-keys($breakpoints) {
    $infix: breakpoint-infix($breakpoint, $breakpoints);
    @for $i from 1 through $columns {
      .col#{$infix}-#{$i} {
        @extend %grid-column;
      }
    }
    .col#{$infix},
    .col#{$infix}-auto {
      @extend %grid-column;
    }

    @include media-breakpoint-up($breakpoint, $breakpoints) {
      
      .col#{$infix} {
        flex-basis: 0;
        flex-grow: 1;
        max-width: 100%;
      }
      .col#{$infix}-auto {
        flex: 0 0 auto;
        width: auto;
        max-width: none; // Reset earlier grid tiers
      }

      @for $i from 1 through $columns {
        .col#{$infix}-#{$i} {
          @include make-col($i, $columns);
        }
      }

      @for $i from 1 through $columns {
        .order#{$infix}-#{$i} {
          order: $i;
        }
      }
    }
  }
}

整个文件只有一个@mixin make-grid-columns(),这个@mixin才是真正的搭建12列Grid网格系统的核心,让我们细细来拆解。

首先是参数,引入了列数($columns)、列间距($gutter)、断点列表($breakpoints)。这三者都已经预设好了,不需要操心。

%grid-column {
    position: relative;
    width: 100%;
    min-height: 1px; 
    padding-right: ($gutter / 2);
    padding-left:  ($gutter / 2);
}

下面是占位符%grid-column,这个在之前讲make-col-ready()时已经讲过了,出于减小css体积的考虑,占位符在这里显然优于@mixin。那么这里定义的就是一个列的基本属性,而min-height的设置也是考虑到当列为空时不至于坍缩。

@each $breakpoint in map-keys($breakpoints) {}

接下来是一个大循环,这个循环的根本目的在于为不同的断点加上相匹配的类,它的循环依据就是断点名。

$infix: breakpoint-infix($breakpoint, $breakpoints);

让我们以“md”为例进入这个循环,首先是$infix变量,结合先前的知识,我们了解到,这个$infix的值将是”-md”,让我们记住它,接下来,又是一个小循环。

@for $i from 1 through $columns {
   .col#{$infix}-#{$i} {
      @extend %grid-column;
   }
}

这个@for循环的目的,就是为了创建12列的基本属性,因为所有的列都具备%grid-column定义的基本属性,所以我们将这份共性总结出来,单独设置一个循环进行类名构建,当这个循环结束,你就可以看到css里出现了用逗号相连的col-md-1一直到col-md-12

.col-md-1, .col-md-2, .col-md-3, 
.col-md-4, .col-md-5, .col-md-6, 
.col-md-7, .col-md-8, .col-md-9, 
.col-md-10, .col-md-11, .col-md-12, 
 { position: relative; width: 100%; min-height: 1px; padding-right: 15px; padding-left: 15px; }

它们都有着同样的属性,这也是@extend的好处。但是请注意,我们并没有设置这些列的宽度,所以它们现在还是不可用的状态。

.col#{$infix},
.col#{$infix}-auto {
    @extend %grid-column;
}

接下来有一个单独的小家伙,通过它,你可以发现.col-md这个类也出现了,也是同样的属性,这个我们就先不去管它了。

@include media-breakpoint-up($breakpoint, $breakpoints) {}

接下拉就是大头了,我们引入了media-brakpoint-up()函数,拿到了media查询的外包装

@media (min-width:768px){}

花括号里就是我们要往这个外包装里塞的内容了。

.col#{$infix} {
    flex-basis: 0;
    flex-grow: 1;
    max-width: 100%;
 }

首先是一个小家伙,又是这货,它单独给col-md定义了一些flex属性,它的意思表明,当“.col-md”出现的时候,它将撑满整行剩余的空间。

.col#{$infix}-auto {
     flex: 0 0 auto;
     width: auto;
     max-width: none; 
 }

接下来定义了“.col-md-auto”类,这个类挺奇葩,宽度随着内容走,跟列宽毛关系都没有。

@for $i from 1 through $columns {
   .col#{$infix}-#{$i} {
      @include make-col($i, $columns);
   }
}

接下来的@for循环就开始定义我们的列宽了,利用make-col(),我们得到了从col-md-1col-md-12各种不同的列宽。

@for $i from 1 through $columns {
   .order#{$infix}-#{$i} {
      order: $i;
   }
}

最后的这个@for循环也是从1到12遍历一次,熟悉flex的同学会知道,添加的这个order属性就相当于名次,数值越小越靠前,而在flex布局下的12列Grid也正是依靠这一点来对不同的列进行排序的。

至此,“md”下的12列Grid框架构建完成。依次类推,其它的断点列也都是如此生成的。比较特殊的还是断点”xs”,由于先前提到的原因,最小的那一部分是没有前缀的,所以你在css里看到的.col-[1-12]就可以视作.col-xs-[1-12],这需要适应一下。

_grid.scss (根目录)

自动化构建其它诸如”.container”之类的元素,那个$enable-grid-classes布尔值,在变量里的第126行,默认为ture

$enable-grid-classes: true !default;

换句话说,如果你哪天心情不好,不想用bootstrap的网格系统了,直接把这里改成false就行。

@if $enable-grid-classes {
  .row {
    @include make-row();
  }

  .no-gutters {
    margin-right: 0;
    margin-left: 0;

    > .col,
    > [class*="col-"] {
      padding-right: 0;
      padding-left: 0;
    }
  }
}

在这里面有一个新定义的类“.no-gutter”,它这个嵌入式展开后是“.no-gutter>col,.no-gutter>[class*=”col-”]”,从结构可以看出来,它就是加在row元素上的,可以取消列的默认间距。

使用建议

说回我们的Grid,我们知道,如果不加以控制,那么Bootstrap在编译Scss的时候会自动生成所有断点下的列,如果你不打算给每个等级都用上一种布局,那么自动编译的Scss将会产生大量冗余的css代码。

.col-1, .col-2, .col-3,
.col-4, .col-5, .col-6, 
.col-7, .col-8, .col-9, 
.col-10, .col-11, .col-12, 
.col, .col-auto, 
.col-sm-1, .col-sm-2, .col-sm-3, 
.col-sm-4, .col-sm-5, .col-sm-6, 
.col-sm-7, .col-sm-8, .col-sm-9, 
.col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, 
.col-md-1, .col-md-2, .col-md-3, 
.col-md-4, .col-md-5, .col-md-6, 
.col-md-7, .col-md-8, .col-md-9, 
.col-md-10, .col-md-11, .col-md-12, 
.col-md, .col-md-auto, 
.col-lg-1, .col-lg-2, .col-lg-3, 
.col-lg-4, .col-lg-5, .col-lg-6, 
.col-lg-7, .col-lg-8, .col-lg-9, 
.col-lg-10, .col-lg-11, .col-lg-12, 
.col-lg, .col-lg-auto, 
.col-xl-1, .col-xl-2, .col-xl-3, 
.col-xl-4, .col-xl-5, .col-xl-6, 
.col-xl-7, .col-xl-8, .col-xl-9, 
.col-xl-10, .col-xl-11, .col-xl-12, 
.col-xl, .col-xl-auto

比如我只想做桌面端和手机端两种适配,那么我可能只需要lgsm的列,css中其它的列代码对我是没用的。所以面对这种情况,我们就需要对Bootstrap进行修改。这里提供两种化用方式,如果你有其它的主意,也欢迎在评论区留言。

  1. 在变量文件中注释掉不需要的列,与之对应的容器等级也不要忘记
$grid-breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  //lg: 992px,
  //xl: 1200px
) !default;
$container-max-widths: (
  sm: 540px,
  md: 720px,
  //lg: 960px,
  //xl: 1140px
) !default;

这是一种很简单的方式,也很直观,我需要什么列就用什么列,不需要的我就给扔掉,但是注意,别扔掉默认值为0的xs。如果你不是前端洁癖患者,只是想缩减css体积,那么推荐用这种方式。

  1. 自己新建一个@mixin,替换掉默认的循环创建行为

第一种方式有一个问题,即虽然你注释掉了不需要的列,但仍需要在HTML中写入预设的类名。如果你不希望在HTML中写入一堆以”col-”开头的类名,那么就尝试自己定义一个@mixin,来创建自己的列吧。
创建之前注意,在bootstrap-grid.scss中将@import “grid”注释掉,咱们不需要自动创建。

其次,新建一个scss文件,引入bootstrap-grid

@import "bootstrap-grid"
%grid-column{
  position: relative;
  width: 100%;
  min-height: 1px;
  padding-left: ($grid-gutter-width/2);
  padding-right: ($grid-gutter-width/2);
}
@mixin make-my-col($breakpoint:null,$size:null,$breakpoints: $grid-breakpoints){
  @extend %grid-column;
  @include media-breakpoint-up($breakpoint,$breakpoints){
    @include make-col($size,$grid-columns);
  }
}

在这里我提供一个自定义的@mixin,名字也很简单make-my-col。包含两个参数,一个是$breakpoint(断点名),一个是$size(列宽)。这个@mixin其实是make-grid-columns()的简化版。

具体原理不用多说了,因为是自用,所以我就没去考虑参数验证的问题。如果你有这方面的需求,要应用到项目中,可以考虑加上参数验证。

调用也很简单,在你需要的类中直接调用即可,传入断点名和列宽,就能创建在对应视宽下的列了。

@import "bootstrap-grid";
.side{
  @include make-my-col(sm,2);
  @include make-my-col(md,6);
}
.content{
  @include make-my-col(sm,10);
  @include make-my-col(md,6);
}

P.S.写的时候注意顺序,要按照升序排列,小的放在上面,即sm在md上面,写反了md将失效。

这种方式同样有一个问题,在小型项目中,这样编译出的css能显著缩减css的体积。但在大型项目中,各种类名交错混杂,利用这种创建单个列的方式,最后生成的css代码不见得比bootstrap预定义的类名更好,所以请规范命名,一些容器元素最好保持固定宽度和固定变化。

Scss显然是利用Bootstrap更高效的方式,根据需求,以上两种方式可任选其一。当然,如果你有其它的利用方式,也可以随心所欲地蹂♂躏Bootstrap~

总结

“好的代码像一首诗”

以前对这句话只觉得莫名高大上,却没有多少感触。而在阅读了Bootstrap用Scss写的源码之后,却是真切地感受到了这一点。打开变量(_variables.scss)文件的时候,带给我的震惊是不可言表的。严谨而有序,体量庞大而层次井然。这些模块如果一个个看下来,相信会获益良多。所以如果你和我一样,是Scss的初学者,那么浏览一下Bootstrap的源码,绝对会爽翻。

Grid应该是Bootstrap的核心区块了,从这里入手虽然比较难,但是方便从根本上了解Bootstrap的运行方式。

总的来说,Beta版本的Bootstrap4相比于Alpha版本已经往前迈了一大步,告别了传统盒模型的布局方式,转身拥抱flexbox,同时删去了很多以前的残余代码,在初期,习惯使用b3的同学可能会觉得不大适应,具体表现是

“哎?我写了这个类咋没反应啊?”

眼下这个时候,官方说明文档都不见得会同步更新,看源代码才是最直接的阅读学习方式。

Grid篇到此结束,谢谢阅读,欢迎指出本文的错漏之处,前端新手上路,请多指教。

相关推荐