CSS布局小结

前言

因为要在团队内作一次技术分享,想了几个题目,最后还是决定系统总结一下我在CSS布局这方面的知识。一是这个题目不算艰深,比较符合目前我的水平,同时也方便团队的设计和其他偶尔帮忙切切页面的小伙伴理解;二是从入行以后学得就比较杂乱,长此以往也不是好事,今年希望好好总结沉淀一下学到东西;三是刚刚过去的2016年正好是CSS正式诞生20周年(1996年12月17日,CSS第一版标准正式发布),我的首次技术分享取这个题目也有纪念意义。另外,准备分享的那天同时穿上第三届CSS CONF送的T恤,嘿嘿。


先来说说过去和现在的常见布局吧

  • 古老的table布局
    记得我在大学时候,有一门课程叫网页设计和网站制作,学的就是HTML和CSS来制作一些页面,用的还是Dreamweaver来写代码,那时老师课堂上教的就是table布局。当时的我怎么也不会想到几年之后会靠一行吃饭,人生真是充满了戏剧性。
    作为十多年前的主要布局方案,table布局的存在也是有一定道理的。table系列标签天生自带的栅格、伸缩和内容垂直居中等特性能很方便地解决布局排版的许多痛点,特别是在那个CSS还不够强大,或者说更多深入的CSS运用还没有被人挖掘出来的年代。不过这个布局方案的缺点也是显而易见的:完全背离HTML标签的语义;标签冗余;扩展性和自由度不高。

  • DIV和CSS时代
    从这时起,网页的布局算是正式进入现代了。div标签全称的单词是division,顾名思义,每一个div就是一个分隔出来独立的区域,它和CSS中floatpositiondisplay等属性的结合,在今天仍然是我们布局排版的主要手段。甚至在五六年前吧,市面上有很多这方面的技术书取名就用了DIV和CSS,搞得好像DIV是某一个专门的技术似的(想起半月前知乎那个SUV+CSS的梗了,哈哈哈哈),其实就是HTML里的一个块级标签,业界都拿它来用想必也是因为它的默认块级属性和其本身是唯一不带任何自身样式,最为“干净”的块级标签吧。

    目前主流的基于DIV和CSS的布局,主要利用的也是CSS(CSS2)中的三块核心内容:浮动、定位和盒子模型。关于这些内容网上的教程已经讲得很清楚了,下面主要是思路介绍和一些个人觉得需要注意的地方。

    比如我们利用左右浮动,可以让元素脱离原本的文档流,贴到父级元素的最左或者最右边,当然这中间可能会有需要清除浮动的情况,另外值得注意的是:其一,浮动后的元素会脱离文档流,平面看上去虽然还是在一层,然而垂直方向上已经高于相邻的其他元素了;其二,浮动属性最初设计是为了实现文字图片环绕的排版需求的,因此,个人觉得开始用它作为块级元素的排版本身也是对属性利用的深度挖掘吧。

    利用定位属性,我们可以随心所欲地将目标元素放在参照物内的任何地方,然而如果定位真有这么完美又怎么可能会出现那么多其他的布局方案呢?就拿绝对定位来说吧,我们定位排好一个元素之后,试着缩小浏览器的宽度到一定程度,定位元素就会和相邻元素发生重叠的情况。此外,关于绝对定位的参照物问题也需要说明,过去有很多资料(甚至包括w3cschool)指出绝对定位的参照元素是从absolute开始,向上一直追溯到定位属性不是默认的static的元素,如果都没有,那就以<html>,有的也说是<body>为参照。其实关于这一点在官方标准里说得很明确:
    <blockquote>
    The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element.

    If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative'or 'fixed', in the following way: In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element. In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined. Otherwise, the containing block is formed by the padding edge of the ancestor.

    If there is no such ancestor, the containing block is the initial containing block.

关于initial containing block :The containing block in which the root element lives is a rectangle called the initial containing block. For continuous media, it has the dimensions of the viewport and is anchored at the canvas origin...
</blockquote>
简而言之,绝对定位的参照元素是其最近的具有定位属性为absolute/relative/fixed三者之一的祖先元素的包含块来定位;如果没有,则追溯到根元素的包含块,即初始包含块(这里不是根元素html,我们可以给html设一个宽高进行实验,发现定位的元素还是相对于整个窗口而言的),这个初始包含块可以简单理解为视窗形成的矩形,其原点是左上角(canvas origin)。再啰嗦一点关于相对定位和绝对定位的区别,相对定位的元素依然会进入文档流,会保留它在文档流中的占位,而绝对定位的元素则会脱离文档流(这也是为什么我们通常选择给父元素设置relative来作为子元素绝对定位的参照,因为不给它任何偏移量的话它依然还是原来在文档流中的位置)。

至于盒子模型,则主要是利用内外边距marginpadding来进行局部调整布局了。需要注意的就是盒模型宽高的计算,还有IE和非IE浏览器下盒模型渲染的差异,以及相邻元素margin重合的情况,当然也记得使用负边距这个脑洞(虽然似乎官方并不推荐使用负值,border-radius负值渲染为内向圆角的提案就曾被驳回了=_=)。

  • 圣杯和双飞翼布局
    基于上面提到的CSS属性的利用,前辈们创造出了两栏,三栏,定宽+自适应等各种组合的页面布局方案,这里主要选择两个最具代表性的布局来作为示例。

    圣杯布局(Holy Grail)其实就是左右两列定宽,中间内容自适应三列布局的一种算是完美的实现。过去的三栏布局基于文档流的顺序,需要leftmainright三列依次渲染,而圣杯布局则解决了这一问题,它可以:
    <blockquote>
    两边带有固定宽度中间可以流动伸缩(fluid)
    允许中间一栏最先出现在文档中
    仅需一个额外的div标签
    仅需非常简单的 CSS,兼容性良好
    </blockquote>

    下面是主要代码:

      /* HTML */
      // 可以看到只多了一个额外的container,并且主要内容main如愿放在了文档流的前面
      <div class="header">头部信息</div>
      <div class="container">
          <div class="main col">主要内容</div>
          <div class="left col">左侧内容</div>
          <div class="right col">右侧内容</div>
      </div>
      <div class="footer">底部信息</div>
      /* CSS */
          .header, .footer {
          background: #ccc;
          color: white;
          text-align: center;
      }
      .container {
          padding: 0 100px 0 200px; /* 设置container的左右内边距为左右两栏的宽度值 */
          color: white;
          text-align: center;
      }
      .container .col {
          float: left;  /* 为三列都设置左浮动,此时左右两列还是被中列挤在下方 */
          position: relative; /* 都设置相对定位 */
      }
      .main {
          width: 100%; /* 中间宽度设置百分比 */
          background: green;
      } 
      .left {
          left: -200px;  /* 其实也可以是right: 200px; 移到左边占据padding的位置 */ 
          width: 200px;
          margin-left: -100%;  /* 左侧负margin100%,这个100%是container的宽度,此时到了上方 */ 
          background: red;
      }
      .right {
          width: 100px;
          margin-right: -100px; /* 移到右边占据右侧padding的位置 */ 
          background: blue;
      }
      .footer {
          clear: both; /* 清除footer周围的浮动,避免上移 */
      }
      /* 需要注意的是没有清除三列的浮动,container实际上是没有包住三列的
         当然清除三列的浮动也可以实现同样的效果 */
    

    双飞翼布局则是根据圣杯布局改变而来,在原本的基础上多增加了一层div嵌套,而省去了相对定位的设置,并且可以自由地利用margin属性给中列设置左右的间隙,下面是主要代码:

      /* HTML */
      // 原本main层的内容多用一个content层嵌套
      <div class="header">头部信息</div>
      <div class="container">
          <div class="main">
              <div class="content">主要内容</div>
          </div>
          <div class="left">左侧内容</div>
          <div class="right">右侧内容</div>
      </div>
      <div class="footer">底部信息</div>
      /* CSS */
      .header, .footer {
          background: #ccc;
          color: white;
          text-align: center;
      }
      .container {
          /* 不再需要原本container的内边距 */
          color: white;
          text-align: center;
      }
      .main {
          float: left;
          width: 100%;
      }
      .main .content {
          /* 利用content的外边距来预留左右空间,并且多出来的数值会成为间隙 */
          margin: 0 220px 0 320px; 
          background: red;
      }
      .left {
          float: left;
          width: 300px;
          margin-left: -100%;
          background: green; 
      }
      .right {
          float: left;
          width: 200px;
          margin-left: -200px;
          background: blue;
      }
      .footer {
          clear: both;
      }
    

    其实对于圣杯和双飞翼布局,我们还可以在这个基础上进行发挥修改,比如两列自适应单列定宽,比如左侧自适应或者右侧自适应等等,有兴趣的同学可以自己试试。

  • 瀑布流
    所谓瀑布流,就是一列列长条状的高度不一的盒子纵向排列,再配上JS做出下滑动态更新添加的效果,看上去像是瀑布一样。关于瀑布流布局我也没有在实际项目中运用到,大致了解通常是需要利用JS来计算列的数量和位置。不过CSS3提供的多列布局属性能让我们更加方便地实现这个效果,当然,方便背后的代价往往就是兼容性问题,不过除了IE,主流浏览器的支持情况已经很不错了。
    <a href="http://www.runoob.com/css3/css3-multiple-columns.html">点击这里</a>可以简单看看CSS3多列属性的介绍,下面是示例代码:

    /* HTML */
    <div class="main">
        <div class="pic pic1">
            <h3>111</h3>
        </div>
        <div class="pic pic2">
            <h3>222</h3>
        </div>
        <div class="pic pic3">
            <h3>333</h3>
        </div>
        <div class="pic pic4">
            <h3>444</h3> 
        </div>
        <div class="pic pic5">
            <h3>555</h3>
        </div>
        <div class="pic pic6">
            <h3>666</h3>
        </div>
        <div class="pic pic7">
            <h3>777</h3>
        </div>
        <div class="pic pic8">
            <h3>888</h3>
        </div>
        <div class="pic pic9">
            <h3>999</h3>
        </div>
        <div class="pic pic10">
            <h3>1010</h3>
        </div>
    </div>
    /* CSS */
    * { 
        padding: 0;
        margin: 0;
     }
    .main {
        width: 1200px;
        margin: 60px auto;
        padding: 20px;
        box-shadow: 2px 2px 6px #ccc;
        column-width: 218px; /* 大盒子的宽度除以列宽会计算出有多少列 */
        column-count: 5; /* 也可以自己指定列数 */
        column-gap: 5px; /* 列间距其实自己也会计算 */
    }
    .main .pic {
        display: inline-block; /* 加上这个属性让每一块不错位 */
        width: 188px;
        min-height: 100px;
        padding: 0px 15px;
        margin: 10px 0;
        box-shadow: 2px 2px 6px #ccc;
    }
    .main .pic h3 {
        line-height: 100px;
        text-align: center;
    }
    /*给一些高度加以区分*/
    .pic1, .pic3 {
        height: 120px;
    }
    .pic2 {
        height: 180px;
    }
    .pic5 {
        height: 160px;
    }
    .pic7, .pic10 {
        height: 130px;
    }
    .pic8 {
        height: 150px;
    }
    
最终实现的效果
  • 利用display的其他布局办法
    CSS的display属性还为我们提供了更多的布局办法,比如利用inline-block的行内特性实现多行元素在不同容器宽度下显示不同个数的效果,然而痛点是解决左右两侧不居中的问题;再有比如display: tabledisplay: table-cell利用表格实现垂直居中,并且支持文本内容纵向扩展为多行的效果。下面则是个人认为未来将成为主流的布局方案。

承前启后的弹性盒子display: flex

对于这个属性,前后出现过几次候选标准,所以网上搜索的时候往往会出来很多不同的属性名称,可能会让人感到困惑,同样它的兼容性问题在现在仍然是一个很烦人的事,明明这么方便的属性却不能痛快地使用。弹性盒子的教程我推荐两个,看过之后其实已经完全可以上手用起来了,今后如果我有更多的使用心得也会再做笔记:

<a href="http://zh.learnlayout.com/flexbox.html">这个网站除了flexbox也有讲其他布局的基础知识,非常推荐</a>
<a href="http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html">阮一峰flex布局教程:语法篇</a>
<a href="http://www.ruanyifeng.com/blog/2015/07/flex-examples.html">阮一峰flex布局教程:实战篇</a>
阮一峰的教程已经将这个属性讲得足够透彻了,实战部分也为我们提供不少布局案例的flex实现方法,这里我也就不再赘述。关键就是理解主轴和纵轴的概念。

即将支持的新标准 CSS3 Grid Layout

display: grid这个属性,我是在第三届中国CSS CONF上通过大漠老师的介绍了解到的,目前已经成为候选标准,而据大会主持人透露,似乎今年(2017)各大主流浏览器就将全面支持该属性,更可喜的是这个标准是由微软提出的,而且早已率先在IE10实现了。( ⊙o⊙ )

<a href="http://www.w3cplus.com/blog/tags/355.html">W3C网站上</a>有对于该属性详细的介绍和教程,虽然目前几乎不太可能用于实际的项目,但还是列在这里也是希望能够对这个新的技术方案保持关注。

就我在大会上看到的演示的直观感受来说,这个属性的强大完全不亚于display: flex,并且作为网格布局,比flexbox更适合作为页面大结构的布局方案,而flexbox更适合盒子内容的排版,相信正如大漠老师所看好的,今后二者的结合或许会成为我们主流的布局手段。

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 8,467评论 1 85
  • 1标准的CSS的盒子模型?与低版本IE的盒子模型有什么不同的? CSS盒子模型:由四个属性组成的外边距(margi...
    秦小婕阅读 136评论 0 1
  • •前端面试题汇总 一、HTML和CSS 21 你做的页面在哪些流览器测试过?这些浏览器的内核分别是什么? ...
    Simon_s阅读 1,081评论 0 8
  • 首先带着书中的问题去阅读。 问题:第一, 为什么有些学习方式更有效,而有些却不行? 第二,想让职场工作者快速增加能...
    徐美美惠阅读 140评论 0 1
  • 1.从我做起 改变,从我做起。如果每个人都在等着别人先开始,那就不会有任何的改变发生了。 2.非暴力沟通 先说具体...
    宵汀阅读 12评论 1 1