IFE-Task-03:三栏式布局

前端的学习需要更多的实践和思考。百度前端技术学院是一个不错的学习地方,其通过任务驱动前端学习者实践和思考。其官网IFE已经结束报名,不过还是可以自行练习。

对于有过页面时间,但想更深入学习的喔从task3开始学习。task3原地址在,目标是实现左右两栏宽度固定,中间一栏根据父元素宽度填充满。我尝试使用position和float两种方法来解决问题。

  1. position属性

    CSS的 position 属性设置元素的定位方式,为将要定位的元素定义定位规则。(摘自MDN)

    其默认值为static,当元素的position属性不为static就被称之为定位元素。其包括相对定位元素、绝对定位元素、粘性定位元素三种类型。最后的粘性属性sticky还处在实验状态中。其余3种属性值如下:

    • relative 为相对定位。relative元素依据自身的原始位置进行相对变化,其原始位置保留不会改变布局。和static一样不会脱离文档普通流(normal-flow)。什么是文档普通流
    • absolute 为绝对定位。absolute元素会递归查找该元素的祖先元素,如果最近的祖先元素position设置为relavite/absolute/fixed,则依据该祖先元素进行偏移。如果不存在包含上述属性的祖先元素,该元素的位置则由初始包含块(initial containing block)定位。absolute的元素会脱离普通流。
    • fixed 为固定定位。fixed元素通过相对于屏幕视窗的位置来指定元素空间。fixed元素依然会脱离普通流。

    说完理论,我们思考barelyfitz上的案例。我们知道absolute会脱离普通流,那在使用中出现案例第五步中的遮盖问题。两个absolute定位的元素#div-1a、#div-1b将普通流中的#div-1c遮盖掉。作者使用float属性来解决这个问题。

  2. float属性

    CSS的float 属性可以使一个元素脱离正常的文档流,然后被安放到它所在容器的的左端或者右端,并且其他的文本和行内元素围绕它安放。(摘自MDN)

    float能生成一个文字环绕的效果。在解决元素覆盖的问题时,就可以通过float将#div-1a、#div-1b向左浮动 {float:left;}。除了文字环绕有时会成为问题,float还会由于脱离普通流会导致父元素没有抱住浮动元素。

    这就需要需要清除浮动。这大致上有3类方法。

    • 空标记法: 空标记法通过添加一个带clear:both属性的标记来解决问题,比如下列形式。

      <br style="clear:both" />
      

      直接添加标记会产生没有语义的标记,并且也不能复用。

    • overflow属性方法: 直接对父元素设置如下列的css。

      .container {
        overflow: hidden; /* 形成新的块级格式化上下文 */
        display: inline-block; /* 触发IE的hasLayout */
        display: block;
      }
      

      该方法通过触发为块级格式化上下文(BFC),BFC是一个独立的布局环境,其中的元素布局是不受外界的影响。这样就能避免文字环绕和包含浮动元素了。除了overflow:hidden可以触发BFC外,还有浮动元素、绝对定位元素(absolute、fixed),和属性display值为inline-blocks、table-cells、table-captions或者属性overflow为hidden、auto、scroll值的元素。

    • clearfix方法: 在浮动元素的父元素上添加一个clearfix class。

      .clearfix::after {
        content:" ";
        display:block;
        clear:both;
      }
      .clearfix {*zoom:1;}/*IE/7/6*/
      

      使用伪元素 :after 在元素上下文之后产生新的上下文,属性 clear:both 如同在元素闭合标签前插入 '

      ' 。

    这就是与任务相关的几个CSS属性知识。

  3. 实践

    由于postion会比较麻烦,我先实现float方法。

    示例图

    由示例图可以看出整个页面包含3栏。 我这里参考涂根华的布局方法,采用先载入左右固定栏的方式。

    <div class="float clearfix">
        <div class="left">左侧固定栏
            <img class="team_logo" src="url" alt="group">
            <div class="team_name">标题栏</div>
        </div>
        <div class="right">右侧固定栏
            <img class="member_logo" src="url" alt="member1">
            <img class="member_logo" src="url" alt="member2">
            <img class="member_logo" src="url" alt="member3">
            <img class="member_logo" src="url" alt="member4">
        </div>
        <div class="main">
            <article>
                中间文章
            </article>
        </div>
    </div>
    

    首先应题目要求,将所有元素的padding属性设置为20px,并且将margin也统一设置为0px;将img标记的大小都设置为指定80px,这里我依据示例图展示的img内边距padding为0px。

    * {
      margin: 0px;
      padding: 20px;
    }
    body{
      font-family: "微软雅黑";
      font-size: 15px;
    }
    img {
        height: 80px;
        width: 80px;
        padding: 0px;
    }
    

    然后将float class元素的背景设置为#eee色和颜色为#999的边框。

    .float {
        border: 1px solid #999;
        background: #eee;
        min-width: 600px; /* 极端情况:小尺寸屏幕展开元素 */
    }
    

    设置完外部框后,就是left、right、main的三列样式了。首先把宽度、边框和背景色抽离出来。由于left class、right class脱离了普通流。我们需要通过main class的外边距确定main的位置。main与left的空隙(20px) + left的padding和left的witdh就是main的左边距,同样的,main的右边距也需要预留right的大小。最终就能将main填充满中间的空间。并且由于元素默认使用20px的内边距,三列元素的真实width应该设置为目标宽度-20px。这是由于在大部分浏览器中的w3c标准盒子模型的width为内容 content的宽度,IE盒子则有所不同,盒子元素的宽度width为 左右border + 左右padding + content width 。我们可以通过设置属性 box-sizing 为 content-box 来指定默认的CSS 盒模型对元素宽高的计算方式。

    .left {
        width: 160px;
        border: 1px solid #999;
        background: #fff;
        padding: 0px; /* 依据示意图将padding重设为0px而不是20px */
    }
    .right {
        width: 120px;
        border: 1px solid #999;
        background: #fff;
        padding: 0px; /* 依据示意图 */    
    }
    .main {
        border: 1px solid #999;
        background: #fff;
        margin-left:220px; /* 边距 */
        margin-right:140px;
    }
    

    接下来就是使用浮动布局来实现任务目标,当然不要忘记上述清除浮动的方法。

    .float > .left {
        float: left;
    }
    .float > .right {
        float: right;
    }
    /* 清除浮动 */
    .clearfix::after {
        content:"";
        display:block;
        clear:both;
    }
    .clearfix { 
        *zoom:1; /* 兼容IE/7/6 */
    }
    

    在这里还有一个问题,div是块元素,其会另起一行并占据整行。这样就会以下图的方式展示,但是我们需要div与img同一行。当然我们可以更改为内联标记。

    左列

    不过,我们还可以通过将图片作左浮动。同时我们将文章的样式加粗和居中。这里还需将宽度width设置为180px,以使得文字在剩余空间内居中。

    .team_logo {
        float: left; /* 使得img和div并排 */
    }
    .team_name {
        width: 180px;
        font-weight: bold;
        text-align: center;
        padding: 0px;
    }
    

    最后的布局图如下,其灰色底框由于清除了浮动会自动包含子元素最高的元素。

    float方法

    在已有样式下修改成使用position方法的布局方案。

    <div class="position">
        <div class="left">左侧固定栏
            <img class="team_logo" src="url" alt="group">
            <div class="team_name">标题栏</div>
        </div>
        <div class="right">右侧固定栏
            <img class="member_logo" src="url" alt="member1">
            <img class="member_logo" src="url" alt="member2">
            <img class="member_logo" src="url" alt="member3">
            <img class="member_logo" src="url" alt="member4">
        </div>
        <div class="main">
            <article>
                中间文章
            </article>
        </div>
    </div>
    

    如上代码,我使用position class来修饰整个内容框。使用relative来修饰position 类,以便absolute子元素能够定位。position下的left和right都通过父元素偏移到正确的位置。

    .position {
        position:relative; /* 给absolute定位 */
        border: 1px solid #999;
        background: #eee;
        min-width: 600px; /* 极端小的情况展开元素 */    
    }
    .position > .left {
        position: absolute;
        top: 20px;
        left: 20px;
    }
    .position > .right {
        position: absolute;
        top: 20px;
        right: 20px;
    }
    

    这样会有什么问题呢?问题还比较大。当前无法满足“改变中间一栏的内容长度,以确保在中间一栏较高和右边一栏较高时,父元素的高度始终为子元素中最高的高度。”。因为position设置为absolute的元素会被脱离正常的普通流,父元素就不会被absolute的子元素撑大。在我的页面中,容器的宽度会被main class的撑开。

    我使用degzhr的方法,自动获取当前分辨率下子元素的高度,并且更新到父元素上。代码如下,其getClassName函数为获得父元素内的指定子元素数组。

    // 获取position类下等高布局元素组和其父元素
    var positionElement = document.getElementsByClassName("position")[0];
    var leftElement = getClassName(positionElement, "left")[0];
    var rightElement = getClassName(positionElement, "right")[0];
    var mainElement = getClassName(positionElement, "main")[0];
    // 获取等高元素组的元素的高度值
    var elementsHeight = [];
    elementsHeight.push(leftElement.offsetHeight);
    elementsHeight.push(rightElement.offsetHeight);
    elementsHeight.push(mainElement.offsetHeight);
    // 获取等高元素组中最大高度的元素的高度值
    var maxHeight = 0;
    for(var i = 0, len = elementsHeight.length; i < len; i++) {
        maxHeight = maxHeight > elementsHeight[i] ? maxHeight : elementsHeight[i];
    }
    // 给每一个等高元素组的定义高度为最大高度maxHeight
    console.log(maxHeight);
    console.log(elementsHeight);
    positionElement.style.height = maxHeight + "px";
    

    这样每次打开页面就会将position的高度设置为子元素高度的最大值。当然这只在每次刷新页面时起效果,我调整页面大小时还是会出现问题的。这又需要通过脚本持续获得页面更新消息。总的来说,在这里使用position方法是不太好的,除非能确认容器的高度就能做一个初始高。这个布局方法既脱离了父元素,但子元素定位又以父元素为基准。这种特性可以用作瀑布流布局。


demo地址

References plus:

  1. 定位实例

  2. 清除浮动

  3. 浮动原理、清除

  4. css知多少

  5. box-sizing

  6. IE浏览器和CSS盒模型