View on GitHub

Yinjie - GitHub.io

Yinjie - GitHub.io

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 方式了。