Bystep15.GitHub.io

宝宝树前端团队

Follow me on GitHub
Daenerys Targaryen
Daenerys Targaryen
Tyrion Lannister
Tyrion Lannister
Jamie Lannister
Jamie Lannister
Jon Snow
Jon Snow
Robb Stark
Robb Stark
Cersei Lannister
Cersei Lannister

基于Flex的简单响应式 Grid 布局

为了庆祝一下 HBO 终于推出了《冰与火之歌:权力的游戏4》,也同时帮助我一个一年级的学生重新设计一下他那相当复杂的设计, 我决定今天教程的主题是 Grid Layout (栅格布局)。

其实以上的设计可以用很多方法实现,包括 display: table-cell,inline-block, 或甚至是用 float, 但是我觉得最好的或是最简洁的实现方法就是用 flexbox (当然是在高级浏览器下)。

HTML 代码

这套方案有一个很高大上的地方,就是所用到的标签很简洁,而且没有那些适配的 hacks。 因为插图的缘故,我只在容器 DIV 中展示头两个 figure 元素,所用的插图来自于《冰与火之歌:权力的游戏》,作者:Gavin Bond。

<div id="got-gridbox">
    <figure>
        <img src="daenerys-targaryen.png" alt="Black and white photograph of Emilia Clarke as Daenerys Targaryen">
        <figcaption>Daenerys Targaryen</figcaption>
    </figure>
    <figure>
        <img src="tyrion-lannister.png" alt="Black and white photograph of Peter Dinklage as Tyrion Lannister">
        <figcaption>Tyrion Lannister</figcaption>
    </figure>
    …
</div>

主体 CSS

首先,我们将 DIV 容器设置成 display: flexbox, 用 justify-content 将里面的元素分隔开来。 行式布局依靠 flex-flow:row-wrap 属性. (同理的,在我最近的一篇文章中用flex实现的磁贴式布局是一种列式布局,用到flex-box: column-wrap属性) 全局盒模型 box-sizing 的覆写是为了避免在我给 figure 元素添加边框后导致整个布局的错位。

* {
    box-sizing: border-box;
}

#got-gridbox {
    display: flex;
    flex-flow: row wrap;
    justify-content: space-between;
}

(浏览器前缀没有加是为了让展示的代码看起来更加简洁,比较新版的浏览器对于 flexbox 的支持已经不再需要特定前缀了,除了Safari。)

下一步,我们来设置 figure 元素。

#got-gridbox figure {
    border: 1px solid #333;
    position: relative;
    font-size: 0;
    margin: 2% 0;
    flex: 0 0 48%;
}

对每个 figure 加上 position: relative 属性,这样可以给里面的标题文本进行绝对定位。 设定 font-size: 0 的目的是为了“吸光 figure 元素里的空气”---这样不会让其出现莫名的空隙。

样式的关键当然是 flexbox 属性了,以后会对其有更详细的说明。现在,加之之前几篇文章的讨论,我们已经对它有了足够多的了解。

首先确保 figure 的宽度是容器的48%,在元素间留出4%的空隙。 空隙通过 margin 来实现,上下各有2%的外补白,这样可以在上下两行间产生4%的行距。 另一种可以的方法是 flex: 0 0 50%,不用设置 margin ,这样也可以作出无空白的两列式栅栏。

接着,将 figure 中的图片改成响应式的。

#got-gridbox figure img {
    width: 60%;
    height: auto;
    margin-top: -2rem;
}

用到的每一张图片都是同样大小与透明度的 PNG 图,填充到 figure 的方式也完全一致。 我在 margin-top 上用了负值,这样可以让人物“走出” figure 元素,让每个 PNG 图片穿过容器顶部。

唯一不同的地方就是在右边框的图片需要向右对齐:

#got-gridbox figure:nth-child(even) img {
    float: right;
}

在左边这一栏中,<figcaption>元素应该在<figure>的右边,我们(先)将这种情况定高默认值。

#got-gridbox figure figcaption {
    position: absolute;
    right: 4%;
    top: 5%;
    font-family: Deseret, Trajan Pro, Trajan, Requiem, serif;
    text-transform: uppercase;
    font-size: 1.6rem;
    text-align: right;
}

Deseret 是一种只针对大写字幕的字体,而且不是每台机器里都有,所以我们得在这里作降级处理,给它设定几个后备字体。

在右栏的<figcaption> 元素是在对应的方向上。

#got-gridbox figure:nth-child(even) figcaption {
    left: 4%;
    right: auto;
    text-align: left;
}

这里呈现的是一些主要用到的属性。在这充分运用了 flexbox 天生的响应式能力, 除此之处,我唯一添加的也就是在屏幕变小时适当的改变一下字体的大小。

最后整理一下

在Cersei Lannister的黑白图上面附一个带有透明度的蒙板,这样桌子看起来是在图片的底边上

最后的两张图片我给自己加了一点有趣的点缀:两张图里都有一张桌子,把桌子给抠掉或是裁掉不是一个很好的选择。 不管的话真实图片又不会充满父级元素,期待着用 Photoshop 把图再修一篇也不是个好选择。 结果呢,单独处理这个系统的最后两张图片就显得有点棘手了:绝大多数的开发者应该都会在这两个标签上加一个类,我不太想这么干。

换个思路,我决定用 CSS linear gradient 来给它 画一个虚拟的桌子。用伪类选择符 nth-last-child 来选取最后两个 figure 元素。

#got-gridbox figure:nth-last-child(-n+2) {
    background-image: linear-gradient(transparent, transparent 80%, #111 81%);
}

最后,在适当的视窗宽度范围内调整一下标题字体的大小。

@media screen and (max-width: 900px) {
    #got-gridbox figure figcaption {
        font-size: 1.4rem;
    }
}

@media screen and (max-width: 800px) {
    #got-gridbox figure figcaption {
        font-size: 1.25rem;
    }
}

当然也可以用 vm 作为单位来对字体大小进行设定,同时用 @media 对其上限和下限进行限定。

@media screen and (max-width: 750px) {
    #got-gridbox figure {
        flex: 0 0 100%;
        margin-bottom: 7%;
    }
    #got-gridbox figure figcaption {
        font-size: 1.6rem;
    }
}

@media screen and (max-width: 450px) {
    #got-gridbox figure figcaption {
        font-size: 1.2rem;
        width: 50%;
    }
}

就是这样!如你所见,flexbox 让页面中的栅栏式布局变得更简单,再不用找那些非主流方法甚至是 hacker 方式了。