响应式网格布局

发布于 大漠

从文章标题中不难发现这篇文章要和大家聊的是两个东东,即响应式网格。从布局系统来说,响应式布局和网格布局都不是什么新东西了。但是他们结合在一起,可以让响应式布局变得更灵活。正如@Keir Watson所说,只要20行代码就可以实现响应式网格布局。不过,我们今天来看两个比较有意思的布局,看上去并不是很容易实现的布局效果。

案例背景和特征

接下来的案例是来自于CodePen上的。在节前看到两个有关于网格的布局,觉得蛮有意思的,所以想以这两个案例为基础,来和大家聊聊怎么通过CSS Grid来实现看上去较为复杂的响应式布局。

第一个案例是来自于@Andy Barefoot的《Responsive CSS Grid - Books》:

第二个案例是来自于@Andy Barefoot的《Responsive Product Grid with layered background》:

这两个案例都有着一些共同的特征,比如说都是响应式布局(即,随着视窗大小,布局会有所差异),就拿第一个案例来说,你尝试着拖动视窗大小时,能看到类似下图这样的效果:

第二个特征是视觉效果有层的叠加,层级看上去较为复杂:

第三个特征就技术上采用了相似的CSS特性,比如CSS的Grid布局自定义属性媒体查询等。

简单分析层级

两个案例都有着相似的层级结构,其中HTML都非常的简单

<!-- HTML -->

<li>
    <img />
</li>

但每个卡片有四层:

除了HTML元素之外还借助了CSS的伪元素::before::after。他们的层级关系是:

我们用动画来模拟这几个层合并的过程:

相对而言,第一个案例要比第二个稍向复杂,毕竟第一个示例未采用图片资源,而且采用了一些新的CSS特性。接下来我们将以第一个案例为例。

实现单个图形效果

稍微花一点点时间来看看单个图形的实现过程,以及运用了哪些技术。

其实这个效果有点类似于《CSS如何实现交叉布局》一文中提到的交叉效果。不过相比而言要更简单一点。

我们来看怎么实现的。

前面已经介绍了它的实现原理和层级的关系。有了这个基础之后,我们就可以快速使用CSS来完成。它的HTML结构很简单,就是一个元素标签(比如div)包裹了一个<img>标签。

<!-- HTML -->
<div class="box">
    <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/881020/book34.jpg" alt="">
</div>

从效果上可以得知,容器是一个1:1的尺寸,在CSS中有多种方式可以实现该效果,大家可能熟悉的就是通过padding-bottom: 100%来实现1:1的容器(即根据纵宽比来完成):

.box {
    background-color: #eebc1f;
    width: 20vw;
    padding-bottom: 20%;
    transform: rotate(45deg)
}

注意,padding取值为%时根据父元素的width来计算。这个时候你看到的效果如下:

有关于纵宽比方面的内容在社区中讨论比较多了:

但随着aspect-ratio属性的出现,实现1:1的效果更为容易:

.box {
    aspect-ratio: 1 / 1;
}

在这个基础上,通过::before::after两个伪元素来构建另两个层:

.box::before,
.box::after {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    background-color: #068d7e;
}

在这个示例中有一点不一样,采用了最新的渐变属性conic-gradient()绘制所需要的形状效果:

.box::before {
    background: conic-gradient(#eebc1f 25%, #068d7e 0 50%, #eebc1f 0) 100% 100% /
    180% 180%;
}

.box::after {
    background: conic-gradient(#eebc1f 75%, #068d7e 0) 0 0 / 180% 180%;
}

离我们的目标越来越近了,为了让<img>的上下两层不会遮盖整个图像,这里需要再用到clip-path属性,裁剪出所需要的效果:

.box::before {
    clip-path: polygon(0 0, 100% 0, 100% 20%, 20% 20%, 20% 100%, 0 100%);
}

.box::after {
    clip-path: polygon(80% 20%, 100% 0, 100% 100%, 0% 100%, 20% 80%, 80% 80%);
}

clip-path中每个点的坐标对应如下:

这个时候,将它位合成在一起就得到我们想要的效果:

实现单个效果之后,要实现整个列表效果就要轻易地多了。

网格布局

在上面我们看到的只是一个列表项的效果,如果我们将N个这样的效果放到一起,就会离我们想要的效果越来越近。虽然列表项目的数量多起来了,但它们的结果是相同的:

<!-- HTML -->
<ul>
    <li><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/881020/book10.jpg" alt=""></li>
    <!-- N个li -->
</ul>

在这里采用的是grid布局

ul{
    display:grid;
    grid-template-columns: repeat(var(--columns),1fr);
}

注意,在上面的示例代码中有--columns,这个是CSS的自定义属性,在:root选择器中显式声明了:

:root {
    --columns: 3;
}

li:nth-child(2n){
    grid-column-start:2;
}

在构建响应式布局时,依旧是以移动端先行,正如上面的代码所示。在类似手机端的设备,你看到的效果类似下图:

响应式布局

在很多开发者的印象中,构建一个响应式布局是离不开CSS媒体查询特性。特别是CSS的Grid布局到来之时,实现一些响应式布局效果可以不需要媒体查询特性:

比如:

.auto-grid {
    --auto-grid-min-size: 16rem;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--auto-grid-min-size), 1fr));
    grid-gap: 1rem;
}

就可以轻易的实现类似下图的布局效果:

如果你对这方面感兴趣的话,可以阅读《Create a responsive grid layout with no media queries, using CSS Grid》一文。

但是,针对这个案例,要实现响应式布局还是需要依赖媒体查询做相应的处理:

@media (min-width:600px){
    :root {
        --columns: 5;
    }
    li:nth-child(2n){
        grid-column-start:auto;
    }
    li:nth-child(4n-1){
        grid-column-start:2;
    }
}

@media (min-width:900px){
    :root {
        --columns: 7;
    }
    li:nth-child(4n-1){
        grid-column-start:auto;
    }
    li:nth-child(6n-2){
        grid-column-start:2;
    }
}

@media (min-width:1200px){
    :root {
        --columns: 9;
    }
    li:nth-child(6n-2){
        grid-column-start:auto;
    }
    li:nth-child(8n-3){
        grid-column-start:2;
    }
}

@media (min-width:1500px){
    :root {
        --columns: 11;
    }
    li:nth-child(8n-3){
        grid-column-start:auto;
    }
    li:nth-child(10n-4){
        grid-column-start:2;
    }
}

@media (min-width:1800px){
    :root {
        --columns: 13;
    }
    li:nth-child(10n-4){
        grid-column-start:auto;
    }
    li:nth-child(12n-5){
        grid-column-start:2;
    }
}

@media (min-width:2100px){
    :root {
        --columns: 15;
    }
    li:nth-child(12n-5){
        grid-column-start:auto;
    }
    li:nth-child(14n-6){
        grid-column-start:2;
    }
}

在该基础上再针对性的添加一些样式,比如说对不同的列表使用不同的颜色,就能得到我们想要的布局效果。

注意,在这里除了使用了媒体查询之外,还使用了CSS的伪类选择器,按一定的规则对li设置不同的肤色和控件其在网格空间中的位置。还有就是,CSS的自定义属性和媒体查询结合在一起,可以让响应式布局更容易一点。如果你对这方面感兴趣的话,可以阅读:

小结

是不是很简单,虽然在该示例中运用到了不少的CSS特性,比如CSS自定义属性、媒体查询、clip-path、渐变等。但他们都已不是最新的特性了。这也再次证明,将不同的特性结合在一起可以构建出任意你想要的效果。在整个效果中,不管是网格布局还是响应式布局都不是难点,其中最为复杂的是交叉布局效果。但运用好层级关系和裁剪规则,这些都是易事。

如果你感兴趣的话,建议你自己亲手写个Demo。体验一下,也能加深印象。如果你在这方面有更好的建议或经验,欢迎在下面的评论中与我们共享。