Bootstrap入门

基本用法与布局的源码实现

Posted by QY on May 25, 2019

前言

虽然随着MVVM框架的流行, 原生Bootstrap样式由于基于JQuery实现, 逐渐被各种与MVVM框架的兼容性版本替代, 但是它在实现自己的核心思想上是没有变化的, 学习它的源码同样有助于我们了解编写样式的一些小技巧, 简单起见, 本文以Bootstrap3的源码为基础进行展开. 它是由less编写的, 因此建议没有Less基础的可以先学习LESS入门

使用

作为前端的样式框架, 本质是一系列已经定义好的CSS样式, 因此使用是非常简单的. 在自己的项目中提前引入官网上指定的css与js文件便能使用, 想要实现对应效果只需要给对应的标签添加文档中提到的class即可

容器

理论上在boostrap中所有内容由容器包裹, 因此它作为基础组件首先被介绍

  • 流体布局

    class="container-fluid"

    本质是一个设置了margin: 0 auto<div>标签

  • 固定容器

    class="container"

    源码:

      //阈值的定义
      @screen-xs:                  480px;
      @screen-xs-min:              @screen-xs;
      @screen-phone:               @screen-xs-min;
    
      @screen-sm:                  768px;
      @screen-sm-min:              @screen-sm;
      @screen-tablet:              @screen-sm-min;
    
      @screen-md:                  992px;
      @screen-md-min:              @screen-md;
      @screen-desktop:             @screen-md-min;
    
      @screen-lg:                  1200px;
      @screen-lg-min:              @screen-lg;
      @screen-lg-desktop:          @screen-lg-min;
      @grid-gutter-width:          30px;
    

    固定容器特性:

    响应式阈值 width
    大于1200(lg) 1170(1140+槽宽30)
    大于等于992小于1200(md) 970(940+槽宽30)
    大于等于768(sm) 750(720+槽宽30)
    小于768(xs) auto

栅格布局

  • 基本规则

    1. 一般被容器包裹,将容器分成多个行, 一行默认被分成12列

    2. 每一列之间用30px的槽分隔

    3. 也可以将一个列视为一个容器, 分割成多个行, 一行被分割成12列, 可以持续嵌套

栅格grid源码分析

  1. 流体容器与固定容器公共样式

    容器的左右外边距为auto, 左右内边距为15px

     .container-fixed(@gutter: @grid-gutter-width) {
       margin-right: auto;
       margin-left: auto;
       padding-left:  floor((@gutter / 2));  //15px
       padding-right: ceil((@gutter / 2));   //15px
       &:extend(.clearfix all);
     }
    
  2. 固定容器特定样式

    根据媒体查询结果设置容器宽度

     .container {
       .container-fixed();
    
       @media (min-width: @screen-sm-min) {
         width: @container-sm;
       }
       @media (min-width: @screen-md-min) {
         width: @container-md;
       }
       @media (min-width: @screen-lg-min) {
         width: @container-lg;
       }
     }
    
  3. 流体容器特定样式

     .container-fluid {
       .container-fixed();
     }
    
  4. 行特定样式

    将每一行的左右外边距设为-15px, 与容器的内边距重叠

     .row {
       .make-row();
     }
     .make-row(@gutter: @grid-gutter-width) {
       margin-left:  ceil((@gutter / -2));
       margin-right: floor((@gutter / -2));
       &:extend(.clearfix all);
     }
    
  5. 列特定样式

    初始化列公共样式

     .make-grid-columns();
     .make-grid-columns() {
     // 对lg, md, sm, xs四种情况, 都会将一行分成@grid-columns份, 此处为12
       .col(@index) { // 初始化列样式
         @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, . col-lg-@{index}";
         .col((@index + 1), @item);
       }
       .col(@index, @list) when (@index =< @grid-columns) { // 递归
         @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, . col-lg-@{index}";
         .col((@index + 1), ~"@{list}, @{item}");
       }
       .col(@index, @list) when (@index > @grid-columns) { // 递归出口
         @{list} {
           //设置相对定位以便每一列能够开启列排序
           position: relative;
           // 设置最小高度防止没有内容时元素坍塌
           min-height: 1px;
           // 通过槽宽来设置内边距
           padding-left:  ceil((@grid-gutter-width / 2));
           padding-right: floor((@grid-gutter-width / 2));
         }
       }
       .col(1); // 开启样式
     }
    

    根据媒体查询来决定开启哪种特定样式

     .make-grid(xs); //手机优先,当以下三种媒体查询没有效果时默认使用此样式, 否则按照 从小到大的顺序覆盖
     @media (min-width: @screen-sm-min) {
       .make-grid(sm);
     }
     @media (min-width: @screen-md-min) {
       .make-grid(md);
     }
     @media (min-width: @screen-lg-min) {
       .make-grid(lg);
     }
     //混合匹配, 用来分别设置width, pull, push, offset的样式
     .loop-grid-columns(@index, @class, @type) when (@index >= 0) {
       .calc-grid-column(@index, @class, @type);
       // 下一步迭代
       .loop-grid-columns((@index - 1), @class, @type);
     }
     //设置列样式
     .make-grid(@class) {
       .float-grid-columns(@class);
       .loop-grid-columns(@grid-columns, @class, width);
       .loop-grid-columns(@grid-columns, @class, pull);
       .loop-grid-columns(@grid-columns, @class, push);
       .loop-grid-columns(@grid-columns, @class, offset);
     }
    

.make-grid(xs)源码精读

  • 运行.float-grid-columns(@class)混合

    设置classcol-xs-1, col-xs-2, ..., col-xs-12的浮动样式

      .float-grid-columns(@class) {
        .col(@index) { // 初始化列样式
          @item: ~".col-@{class}-@{index}";
          .col((@index + 1), @item);
        }
        .col(@index, @list) when (@index =< @grid-columns) { // 递归
          @item: ~".col-@{class}-@{index}";
          .col((@index + 1), ~"@{list}, @{item}");
        }
        .col(@index, @list) when (@index > @grid-columns) { // 递归收尾, 给所有选择器添加浮动
          @{list} {
            float: left;
          }
        }
        .col(1); // 开启列样式
      }
    
  • 运行.loop-grid-columns(@grid-columns, @class, width)混合

设置classcol-xs-1, col-xs-2, ..., col-xs-12width

.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
  .col-@{class}-@{index} {
    width: percentage((@index / @grid-columns));
  }
}
  • 运行.loop-grid-columns(@grid-columns, @class, push)混合

    设置classcol-xs-1, col-xs-2, ..., col-xs-12的列排序

      .calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {
        .col-@{class}-push-@{index} {
          //列排序由left实现
          left: percentage((@index / @grid-columns));
        }
      }
      .calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {
        .col-@{class}-push-0 {
          //为了使递归中不将left设置为0px因此单独挑出@index=0时设置值为auto
          left: auto;
        }
      }
    
  • 运行.loop-grid-columns(@grid-columns, @class, pull)混合

    push的区别仅仅在于此处设置的是相对定位的right而非left

    因此对不同阈值不能跳跃设置列排序, 可能会发生left, right冲突或者未覆盖的情况

  • 运行.loop-grid-columns(@grid-columns, @class, offset)混合

    设置classcol-xs-1, col-xs-2, ..., col-xs-12的列偏移

      .calc-grid-column(@index, @class, @type) when (@type = offset) {
        .col-@{class}-offset-@{index} {
          //列偏移由外边距实现
          margin-left: percentage((@index / @grid-columns));
        }
      }
    

响应式工具

通过触发媒体查询来控制一些元素是否显示, 本质是根据class来控制display的值

.responsive-visibility() {
  display: block !important;
  table&  { display: table !important; }
  tr&     { display: table-row !important; }
  th&,
  td&     { display: table-cell !important; }
}

.responsive-invisibility() {
  display: none !important;
}
//初始化所有样式为隐藏
  .visible-xs,
  .visible-sm,
  .visible-md,
  .visible-lg {
    .responsive-invisibility();
  }
//将符合条件的元素显示出来
.visible-xs {
  @media (max-width: @screen-xs-max) {
    .responsive-visibility();
  }
}
.visible-xs-block {
  @media (max-width: @screen-xs-max) {
    display: block !important;
  }
}
.visible-xs-inline {
  @media (max-width: @screen-xs-max) {
    display: inline !important;
  }
}
.visible-xs-inline-block {
  @media (max-width: @screen-xs-max) {
    display: inline-block !important;
  }
}
//将符合条件的元素隐藏
.hidden-xs {
  @media (max-width: @screen-xs-max) {
    .responsive-invisibility();
  }
}
.hidden-sm {
  @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
    .responsive-invisibility();
  }
}
.hidden-md {
  @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
    .responsive-invisibility();
  }
}
.hidden-lg {
  @media (min-width: @screen-lg-min) {
    .responsive-invisibility();
  }
}

栅格盒模型的精妙之处

  • 回顾栅格的基本规则

    • 固定容器拥有左右内边距15px

    • 行两边有-15px的外边距

    • 列两边有15px的内边距

  1. 为了维护槽宽的规则列里面应该内边距隔开相邻列内的文本

  2. 当列里面放行行里又有子列时, 为了不让被行里面的子列内的文本与跟行外面那个列相邻列内的文本的边距扩大, 需要给行设置负的外边距使任意嵌套关系的两列内边距恒定为30px

  3. 当给容器内放行时, 必须给容器设置内边距才能抵消行的外边距的副作用

bootstrap的定制化

  • 修改源码

    bootstrap的入口文件为bootstrap.less, 里面的import为less的特性, 起着整合其他less的功能而不是css中发出新请求的功能, 可以经常使用

    可以通过修改源码后重新编译bootstrap.less再引入新css文件

  • 创建新的入口文件

    将bootstrap的入口文件当做混合引入到新的入口文件中

    接下来修改想要修改的变量或样式

    最后将新入口文件编译引入