断点:
$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px
);
不同断点对应的容器宽度:
$container-max-widths: (
sm: 540px, // min-width: 576px
md: 720px, // min-width: 768px
lg: 960px, // min-width: 992px
xl: 1140px // min-width: 1200px
);
实际上断点到底取在哪里,可以随意自定义。这里只是引用的 bootstrap 的规范而已。
2.1 固定容器实现
固定容器实现很简单。无非就是将容器的宽度设置为 100%
, 水平居中而已。
/**
* 基本容器的样式
* 宽度 100%
* 有半个槽宽的内边距
* 水平居中
* @param $gutter 槽宽, 如果只是想要一个普通的容器。可以将参数槽宽设置为 0
* 默认值是 $gird-gutter-width
*/
@mixin make-container($gutter: $grid-gutter-width) {
width: 100%;
padding-right: $gutter / 2;
padding-left: $gutter / 2;
margin-right: auto;
margin-left: auto;
}
槽宽这个概念如果用过 bootstrap 应该能够理解。如果不理解可以跳到本文底部,有详细介绍。
当屏幕宽度小于 576px
的时候,所有的容器宽度都是 100%
。即 xs
的情况下:
.container,
.container-sm,
.container-md,
.container-lg,
.container-xl,
.container-fluid {
@include make-container();
}
2.2 响应式容器实现
要实现响应式容器,就是要根据不同的断点分别给不同的容器设置媒体查询,以 max-width
约束容器的宽度。
根据规范所示:
- 当断点为
sm
时, .container
, .container-sm
的 max-width
为 540px
,其余容器为初始的 100%
- 当断点为
md
时, .container
, .container-sm, .container-md
的 max-width
为 720px
,其余容器为初始的 100%
- 当断点为
lg
时, .container
, .container-sm, .container-md, .container-lg
的 max-width
为 960px
,其余容器为初始的 100%
- 当断点为
xl
时, .container
, .container-sm, .container-md, .container-lg, .container-xl
的 max-width
为 1140px
,其余容器为初始的 100%
分析一下,就可以发现。每个断点处需要设置媒体查询的容器数刚好在 .container-#{$breakpoint}
处停止。
使用 sass 描述如下:
@each $breakpoint, $container-max-width in $container-max-widths {
/**
* .container
* .container-sm
* .container-md
* .container-lg
* .container-xl
* 按照断点设置媒体查询
* 其实就是通过 max-width 控制容器到底有多宽
*/
@include media-breakpoint-up($breakpoint, $grid-breakpoints) {
// 每个断点的屏幕最大 width
%responsitive-#{$breakpoint} {
max-width: $container-max-width;
}
// 用于确定哪些容器需要设置媒体查询的 flag
$extend-breakpoint: true;
@each $name, $width in $grid-breakpoints {
@if $extend-breakpoint {
.container#{breakpoint-infix($name)} {
@extend %responsitive-#{$breakpoint};
}
}
@if $name == $breakpoint {
$extend-breakpoint: false;
}
}
}
}
其中两个辅助函数 breakpoint-min
, breakpoint-infix
:
/**
* 根据断点名称取得对应的断点 width.
* 注意:如果是 xs 断点,返回的是 null
* @param $name: 传入的 map key
* @param $breakpoints-map: 断点 map
* @return: 断点对应的 mind-width
*/
@function breakpoint-min($name, $breakpoints-map: $grid-breakpoints) {
$min: map-get($map: $breakpoints-map, $key: $name);
@return if($min != 0, $min, null);
}
/**
* 根据断点名称作为 key 查询map
* 若是 map 中 key对应的 value 不为 0 则生成后缀名
* 否则返回空串
*
* @param $name: 传入的 map key
* @param $breakpoints-map: 断点 map
* @return: 断点对应的后缀名 格式 '-sm'
*/
@function breakpoint-infix($name, $breakpoints-map: $grid-breakpoints) {
@return if(breakpoint-min($name) != null, '-#{$name}', '');
}
辅助 mixin media-breakpoint-up
:
/**
* 根据 $name 作为 key 查询 $breakpoints-map 中对应的断点值
* 如果断点值存在,则对相应内容设置媒体查询
* 如果断点值不存在,则将混合的内容原样输出
*
* @param $name 断点名称
* @param $breakpoints-map 保存断点的 map
*/
@mixin media-breakpoint-up($name, $breakpoints-map: $grid-breakpoints) {
$min: breakpoint-min($name, $breakpoints-map);
@if $min {
@media (min-width: $min) {
@content;
}
}@else {
@content;
}
}
3. Row
栅格布局主要就是围绕 row
和 column
展开。行中放置列,列中放置应用内容,列中又可以嵌套行(子子孙孙无穷尽也(x))。
行其实就是一个固定的容器,所以样式也很简单。
/**
* 行基础样式
* 开启 flex 布局
* 允许多行容器
* 左右有半个槽宽的负外边距
*
* @param $gutter 槽宽
*/
@mixin make-row($gutter: $grid-gutter-width) {
display: flex;
flex-wrap: wrap;
margin-right: -$gutter / 2;
margin-left: -$gutter / 2;
}
// 行
.row {
@include make-row();
}
4. Column
column 是栅格布局中最重要的部分,同时也是最复杂的一部分。
有多种列可供使用:
- 等宽列
.col
- 特点是
.row
中放置 n 个 .col
, 那么一个 .col
的宽度就是 .row
的 n 分之一
- 比例列
.col-${i}
-
$i 取值为 1- 12
. bootstrap 默认情况下一行可以分作12列。 .col-{$i}
所占的宽度就是 row
总宽度的 $i / 12
。
- 这里默认分成的列数对应变量是
$grid-columns: 12 !default;
- 可变宽度的弹性列
.col-auto
如果是在小屏幕下,我们通常不会让一行有很多列,通常一行都只有一列。所以根据不同的屏幕断点,bootstrap 还提供了响应式列。
- 等宽列
.col-#{$breakpoint}
- 比例列
.col-#{$breakpoint}-${i}
- 可变宽度的弹性列
.col-#{$breakpoint}-auto
语义是,当屏幕大于等于断点对应宽度时,呈现列的语义形式。当小于断点宽度时,所有的列都退化成 width: 100%;
的形式。同样的,这也通过媒体查询实现.
4.1 列基础样式
所有列的最基础的样式:
/**
* 列基础样式
* 开启相对定位,作为列中内容绝对定位的参考点
* width 为 100%
* 左右有半个槽宽的外边距
*/
%grid-column {
position: relative;
width: 100%;
padding-left: $gutter / 2;
padding-right: $gutter / 2;
}
这个样式用于当屏幕小于对应断点的时候,列的样式进行退化
4.2 设置 .col, .col-${i}, .col-auto 的基础样式
$infix: breakpoint-infix($breakpoint, $breakpoints);
// .col-*-i 系列设置基础样式
@if $columns > 0 {
@for $i from 1 through $columns {
.col#{$infix}-#{$i} {
@extend %grid-column;
}
}
}
// .col-*, -col-*-auto 系列设置列基础样式
.col#{$infix},
.col#{$infix}-auto {
@extend %grid-column;
}
我们这里以 $breakpoint: sm
为例,则 $infix: '-sm'
。变量 $colums: 12
是默认的一行可以分为多少列.
执行完之后, .col-sm, .col-sm-#{$i}(i 取值 1-12), .col-sm-auto
它们的默认样式都设置成了 %grid-column
。退化的基本样式就设置好了。
之后就开始设置媒体查询,以确定不同的列的样式。
因为此时的例子 $breakpoint: sm
, 所以接下来的内容都会被编译进 @media(min-width: 576px)
中:
4.3 列样式设置
等宽列样式设置
即 flex:1 1 0; max-width: 100%
。列可以等比例放大等比例缩小。初始行可用空间计算值是整个 main size
。(如果不理解,可以去搜索 flex-basis: 0 代表什么含义)。这样的话,无论 .row
下放置多少个 .col-sm
,每个 .col-sm
的宽度都是相等的。(前提是列能容纳得了内容)
.col-sm {
flex-basis: 0;
flex-grow: 1;
max-width: 100%;
}
.row-cols#{$infix}-#{$i}
这个是 bootstrap 的特色类,这个类应用在 row
上。约束其下最多可以拥有多少个等宽列.这个类对于其他的列是不影响的,仅仅影响等宽列。(是通过选择器优先级实现的)
// 设置 .row-cols-*-i 系列 的样式
@if $grid-row-columns > 0 {
@for $i from 1 through $grid-row-columns {
.row-cols-sm-#{$i} {
@include row-cols($i);
}
}
}
辅助 mixin row-cols
/**
* 设置 .row-col-*-i 系列下的列样式
* flex: 0 0 100% / $count,即是一个不会放大不会缩小,永远按 i 的值等比例平分行的列
*
* @param $count 要平分的列数
*/
@mixin row-cols($count) {
& > * {
flex: 0 0 100% / $count;
max-width: 100% / $count;
}
}
即 .row-cols-sm-1 > *
的选择器特殊性和 .col-sm
相同。但是前者声明在后者后面,所以造成了样式覆盖。即 .row-cols#{$infix}-#{$i}
只对等宽列起效。
严格的说,当使用对应断点的 .row-cols-#{$breakpoint}-#{$i}
之后,会对其下列中 .col-#{$breakpoint}
及断点之前的等宽列生效。即 .row-cols-md-4
会对 .col, .col-sm, .col-md
都生效。原因是因为整个循环顺序是从 xs -> xl
。如果不明白,看一下编译输出的 CSS 就知道为什么了。
可变宽度弹性列样式设置 .col-sm-auto
// 设置 .col-*-auto 的样式
.col-sm-auto {
@include make-col-auto();
}
辅助 mixin make-col-auto
/**
* 设置 .col-*-auto 的样式
* 默认情况下是一个不会放大不会缩小,宽度由 flex item 宽度决定的盒子
*/
@mixin make-col-auto() {
flex: 0 0 auto;
width: auto;
max-width: 100%;
}
设置比例列样式 .col-sm-#{$i}
// 设置 .col-*-i 系列的样式
@if $columns > 0 {
@for $i from 1 through $columns {
.col-sm-#{$i} {
@include make-col($i, $columns);
}
}
}
辅助 mixin make-col
/**
* .col-*-i 的样式
*
* @param $size 占据的列数
* @param $columns: 总可用列数
*/
@mixin make-col($size, $columns: $grid-columns) {
flex: 0 0 percentage($size / $columns);
max-width: percentage($size / $columns);
}
这里就是按比例分配,如果 $i: 5
, 则 .col-sm-5
就占据整行宽度的 5/12
。
flex 布局中使用 order
属性来视觉排序 flex item.所以 bootstrap 也提供了列排序的类
// 列排序相关
.order-sm-first {
order: -1;
}
.order-sm-last {
order: $columns + 1;
}
// 一行最多12列,也就是说从 -1 开始编号就可以安排完整行所有的列排列顺序
@for $i from 0 through $columns {
.order-sm-#{$i} {
order: $i;
}
}
只要明白浏览器视觉排序是按照 order
数值从小到大排序就能了解这个是干嘛的了。
列偏移
实际需求中,总是会有列偏移的需求,这里是通过 margin-left
实现。
// 设置列偏移
@if $columns > 0 {
@for $i from 0 through ($columns - 1) {
@if not ($infix == '' and $i == 0) {
.offset#{$infix}-#{$i} {
@include make-col-offset($i, $columns);
}
}
}
}
辅助 mixin make-col-offset
:
@mixin make-col-offset($size, $columns: $grid-columns) {
$num: $size / $columns;
margin-left: if($num == 0, 0, percentage($num));
}
整个创建响应式列的流程就是这样,这里只是举了断点为 sm
的情况,其他断点也是这个流程。就不一一赘述了,实际上实现也是通过循环。如果不需要响应式,只是需要等宽列,可变宽度弹性列,比例列的话就更简单了,我相信如果能理解上面的内容的话以读者的聪明才智可以很容易自己写出来。
5. 槽
槽(gutter) 是两列之间的间距。槽的数量是列数量 - 1.
槽公式:
设栅格行宽度为 w, 列宽度为 c(这里的列宽指内容区域), 槽宽度为 g, 列数为 n
则 w = n * c + (n - 1) * g
bootstarp 的设计是这样的:
-
container
container 上左右分别有 1/2 gutter
的 padding
-
row
row 上设置了 -1/2 gutter
的左右外边距将容器的 padding
影响抹平
-
column
colum 上设置了 1/2 gutter
的左右外边距使得内容距离容器的边界有了 1/2 gutter
的距离。使得内容不贴边。
同时允许列嵌套行,因为行的负外边距会将列的左右 padding 影响清除,可以达到无限套娃的目的。
总之这个 gutter 的 大小因不同的设计而异, bootstrap 的默认 gutter 宽度为 30px
,读者可以根据自己的设计目的调整。
6. 完整源码
完整源码移步 git 仓库(不是 bootstrap 完整版本,是个人重写加了注释的仅具有 grid 功能的版本。内容不多)
bootstrap-grid地址:https://github.com/IliyaRin/bootstrap-grid
更多编程相关知识,请访问:编程入门!!