挑逗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的引用
要素分析
变量(_variales.scss):
- $grid-breakpoints:
$grid-breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px ) !default; @include _assert-ascending($grid-breakpoints, "$grid-breakpoints"); @include _assert-starts-at-zero($grid-breakpoints);
分为xs
,sm
,md
,lg
,xl
五个等级,分别表示极小
、小
、中等
、大
、超大
。这个断点设置主要用于媒体查询,即当浏览器视窗横向尺寸发生变化时,一旦到了指定条件,比如width=768px
,就将触发设置在md断点下的样式。这些等级的数据可以按需改动,全局凡是引用这个map的都将受到影响。
在文档注释中提到,这里设置的数值表示变化的最小值,即触发md
的条件是≥768px。
在变量$grid-breakpoints
后跟着两个@mixin,这两个@mixin定义在根目录下_function.scss文件中,都起一个判断作用,其中_assert-ascending()
是确保整个$grid-breakpoints
的内容是按升序排列,即从小到大;_asseert-starts-at-zero()
是确保$grid-breakpoints
的第一个元素值必须为0,即xs必须为0。
- $container-max-widths:
$container-max-widths: ( sm: 540px, md: 720px, lg: 960px, xl: 1140px ) !default; @include _assert-ascending($container-max-widths, "$container-max-widths");
相比较于网格断点变量,这里的容器变量删去了xs
等级,只余下了4个等级。这并不违背逻辑,因为在注释中,这里写的数值表示容器宽度的最大值。举个例子,在md
条件下,视窗最小宽度为768px,而容器最大宽度为720px,留有一定的余地。所以这样的话,数值为0的xs
在容器宽度这里是没有意义的,标记为sm
的容器宽度值就是在0-540px之间变动,正对应着视窗宽度xs
到sm
的区间。变量后紧跟着的函数之前已经提过,这里不再赘述。
- grid-columns:
$grid-columns: 12 !default; $grid-gutter-width: 30px !default;
这里的grid-columns指的是包括$grid-columns
、$grid-gutter-width
在内的网格列定义。
这两者($grid-columns
、$grid-gutter-width
)都是初始定义,分别表示总列数和列间隔,在之前的alpha6版本中,还有一个不同视宽下的gutter组,在beta版本中已被删除。
至此,网格涉及的变量已经介绍完,后续的所有mixin和函数都是基于这些数值的,所以这里的数值很重要,牵一发而动全身。
@mixin(mixins文件夹)
- _breakpoints.scss:
- breakpoint-next:
@function breakpoint-next( $name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) { $n: index($breakpoint-names, $name); @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); }
该函数看似有三个,实际只有两个参数,一个是$name
,即断点名,需手动输入,第二个是在变量中定义的断点的名称列表。该函数的作用就是返回输入的断点名的下一个断点,如果没有下一个了,那就返回空值。即breakpoint-next(“sm”)
将返回md
,breakpoint-next(“xl”)
将返回null。
- breakpoint-min、breakpoint-max:
@function breakpoint-min($name, $breakpoints: $grid-breakpoints) { $min: map-get($breakpoints, $name); @return if($min != 0, $min, null); }
@function breakpoint-max($name, $breakpoints: $grid-breakpoints) { $next: breakpoint-next($name, $breakpoints); @return if($next, breakpoint-min($next, $breakpoints) - 1px, null); }
这两个函数光看函数名很容易引起误会,它们的作用绝非获取断点列表中的最大值和最小值,因为我们在事先定义断点列表时就已经确认过断点列表是按照升序排列的了。这里的min
,max
指的是当前选中等级的区间左右值。所以这两个函数都包含$name参数,当对“md
”分别应用这两个函数时,得到的值将是768px(min)和991px(max)。
这里有一个问题,当使用breakpoint-max()
函数获取区间右值时,为了不与下一个断点数值上重合,所以进行了一个减一操作(官方的膜法)。
另外就是通过这个函数,能够知道新的Bootstrap4的一个小坑,在Bootstrap3中,是有等级xs
的,代表极小,而在新版本中,由于xs
设定值的特殊性(断点值为0),所以从宽度定义上,xs
这个等级就被取消了,这里的min
函数,在$name=“xs”
的情况下将返回空值,这样就从数值上去掉了xs
。在后面提到的breakpoint-infix
函数中,将从类名定义层次上取消”xs
”等级。
- breakpoint-infix:
@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}"); }
核心函数,直接关系到类名的自动化生成。它的功能是利用前面breakpoint-min()
函数,将断点名以“-name
”的形式返回,即breakpoint-infix(“md”)
将返回“-md”
,换句话说,这个函数的作用就是给断点名前头加个短横线,等到时候需要循环创建列的时候,就可以利用这个函数动态生成诸如”.col-md-4
”之类的类名了。
在这个函数中有一个判断,即如果breakpoint-min()
函数返回的值是null
,那么整个函数将返回一个空字符串,而在min
函数中,只有等级为“xs
”时,才会返回null
,所以,在创建列的类名时,你将再也看不见“.col-xs-4
”,取而代之的是”.col-4
”。在从b3迁移至b4的时候,这一点要尤其注意。
- media-breakpoint-up
- media-breakpoint-down:
@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { $min: breakpoint-min($name, $breakpoints); @if $min { @media (min-width: $min) { @content; } } @else { @content; } }
@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { $max: breakpoint-max($name, $breakpoints); @if $max { @media (max-width: $max) { @content; } } @else { @content; } }
关于media的都用@mixin
定义,而不是前面的@function
了。这里的两个mixin,功能也简单,就是利用前面提到的breakpoint-min
和breakpoint-max
函数,定义变化节点的media query,这样在创建网格时,就能根据预先设定的几个等级来进行响应式变化了。这里的up
和down
,可以理解成“大于(up)”、”小于(down)”,亲测,在后续的应用中,基本都是用的media-breakpoint-up
。如果你打算重写Bootstrap,那么用用down好像也不错。
- media-breakpoint-between
- media-breakpoint-only:
@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { $min: breakpoint-min($lower, $breakpoints); $max: breakpoint-max($upper, $breakpoints); @media (min-width: $min) and (max-width: $max) { @content; } }
@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { $min: breakpoint-min($name, $breakpoints); $max: breakpoint-max($name, $breakpoints); @if $min != null and $max != null { @media (min-width: $min) and (max-width: $max) { @content; } } @else if $max == null { @include media-breakpoint-up($name) } @else if $min == null { @include media-breakpoint-down($name) } }
候补@mixin
,分别代表两种宽度之间和仅在一种宽度下的情形。between
好说,利用前面的up
和down
两个@mixin
表示在某个区间范围内的情形,可以用来跨等级,比如说给“sm
”一种排版,然后给“md
”到“xl
”一种排版(真的有人会这么干吗?)。而only
这个@mixin
有些奇怪,在Alpha6中它将between
包在了其中,在Beta中它也做了大致相同的事,只是多进行了一些判断。而且正如其函数名所示,它表示“仅”,直接把表达式写出来可能更直观,(前缀省略)
only(“md”)=between(“md”,”md”)
就是这样一种奇怪的函数,不排除在后续对Bootstrap进行拆解时会再见到它,不过目前,它对我们没什么用处。
综上,除了一些辅助函数,我们在后续的网格搭建中会用到的函数或者@mixin只有俩,一个是breakpoint-infix($name)
,一个是media-breakpoint-up($name)
。
- _grid.scss
这里指的_grid.scss是指的mixins文件夹下的_grid.scss,而非根目录下的_grid.scss
关于这个@mixin
集合,一言以蔽之,即,想建网格就靠它。
这是一个网格搭建的基础集合,但单靠它,我们还是创建不出Bootstrap引以为傲的12列网格系统的,想提前知道原因的话可以打开_grid-frameworks.scss
文件先看看。
顺带一提,和Alpha6版本不同的是,_grid.scss
删除了@mixin make-gutter()
,大概是官方觉得写这么一个@mixin
有点多此一举吧。
- make-container:
@mixin make-container() { margin-right: auto; margin-left: auto; padding-right: ($grid-gutter-width / 2); padding-left: ($grid-gutter-width / 2); width: 100%; }
创建一个相对定位的容器,也就是大家熟悉的.container的本体……的一部分。嗯,是的,一部分。如果你新建了一个
<div class=”main”></div>
在scss中写一个
.main{@include make-container();}
你创建的实际上是一个在Bootstrap中以“container-fluid
”为类名的流体容器,仔细观察这个@mixin
,你就会发现,它指定了容器宽度为100%,在实际的浏览器中的表现为横向全屏,任凭你调整浏览器的窗口大小,这一点都不会变(当然,它自带一个左右padding)。
- make-container-max-width:
@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) { @each $breakpoint, $container-max-width in $max-widths { @include media-breakpoint-up($breakpoint, $breakpoints) { max-width: $container-max-width; } } }
一般不单独使用,搭配上一个make-container
,就能创建出大家熟悉的.container
了(其实这一点在根目录下的_grid.scss就可以找到)。在这个@mixin
里,它确定了width=$container-max-width
(在变量中有定义),这就代表着根据这个@mixin
创建出的容器,不会再像流体容器那样宽度随心所欲,而是呈阶梯性变化,某种程度上,这更符合我们的预期和使用习惯。
- make-row:
@mixin make-row() { display: flex; flex-wrap: wrap; margin-right: ($grid-gutter-width / -2); margin-left: ($grid-gutter-width / -2); }
这里是一个很重要的变化,大家可以注意到,row这里的display
变成了flex
,这也是b4主要的改进之一,row这个基础构建的变化意味着整个b4框架在很大程度上都会建立在flexbox的基础上(IE8滚蛋吧)。
顺带吐个槽,大家可以注意到,make-row
里的循环给每个row
加上了一个负margin
,大小也正是gutter/2
,(即15px,如果你没改的话),目测是为了抵消创建容器(container)时padding
的影响,所以说……嗯……当初为啥加个padding
呢?
- make-column-ready:
@mixin make-col-ready() { position: relative; width: 100%; min-height: 1px; padding-right: ($grid-gutter-width / 2); padding-left: ($grid-gutter-width / 2); }
我很奇怪Bootstrap创建了这个@mixin
却没有使用它,在后面的_grid-frameworks.scss
中找到了原因,这个@mixin
被替换成了一个占位符。这应该是目前内测版的一个小疏漏,后续版本要么删除这个@mixin
,要么把占位符那一块进行更新。总之,这个@mixin
我们先略过不谈。
- make-col:
@mixin make-col($size, $columns: $grid-columns) { flex: 0 0 percentage($size / $columns); max-width: percentage($size / $columns); }
嗯,列终于也变成flex布局了,以后等高列,元素垂直居中就很简单了。这里简单解释下这个@mixin
,参数$size
为整数,从1到12,它的列宽计算也正是基于此,通过$size/$columns
得到占比,以这个百分数结果为列宽,这使得其具备一定的响应性。
顺带说明一下这个flex
,flex:0 0 <number>
这是个简写属性,它的具体意思为不放大也不缩小(对应情况为有剩余空间或剩余空间不足)的宽度(或主轴空间,一般为横向)为<number>
的flex元素。
这里也有一个小坑,我在刚开始测试时,以为make-col()
就是12列布局的奥秘所在,于是创建了两个div
,分别给它们加上make-col(4)
和make-col(8)
<div class="container"> <div class="side">...</div> <div class="main">...</div> </div>
.side{ @include make-col(4); } .main{ @include make-col(8); }
刚开始,它们也的确如我所想的呈1:2宽度分布,但是当我缩小窗口到一定程度的时候,惊恐地发现它们并没有折行,而是发生了重叠。
究其原因,就是出在这个make-col()
上,首先,我们惯常是习惯在列上包一层row
,row
中我们定义了flex-wrap
为wrap
,理论上row
中的元素在宽度不够时会折行,但是靠make-rol()
定义的“.col-<number>
”的类(为什么没有前缀之前在infix中提过了)的宽度却是百分比数,换句话说,它的宽度永远是够的(row内的col宽度和永远小于等于100%),这样,当你的内容缩小到一定程度的时候,势必会出现溢出现象(容器宽度小于内容宽度)。当你手动调整<number>
,使其和大于12时,比如make-col(4)
和make-col(9)
,但这就相当于手动折行了,效果不算特别理想。
所以经过分析,实际使用中依然需要搭配前缀,比如大窗口下用md
,而这个不带前缀的类则可以视作xs
的替代品,在极小窗口下使用它,但不要单独使用,不然在小窗口情况下,将会出现溢出情况,如果在页面元素多的情况下,影响会很大。
活在天堂的offset、push、pull,Alph6中存在而在Beta版中被删除,这里就不多做介绍了,愿它们一路走好