CSS Grid和自定义属性带来的变化

发布于 大漠

好久没有整理有关于CSS方面的文章了,说实在心理还是痒痒的,但取舍有度。不过最近看了几篇有关于CSS的文章还是蛮有意思的。两篇是关于页面布局的,另外一篇是关于动画函数的。事实上,布局动画在CSS中都是较为重要的部分。当然,今天要提的知识点并不是什么非常新的知识点,但也是有创意和创新的知识点。比如不通过媒体查询实现响应式布局,比如说容器单位构建强大的布局,比如说动画函数(缓动函数)的反转。听听这些是不是觉得非常有意思,如果你和我也一样,请继续往下阅读。

在继续下面的内容之前,先说一下这几个概念的出处:

不采用媒体查询实现响应式布局

首先要声明一点,在这里不会具体介绍什么是媒体查询,也不会说什么是响应式设计。虽然如此,但大家或许和我类似,在心里有一个概念,响应式设计会在不同的断点有不同的响应(即不同的布局效果),而这个不同的断点就是依赖于媒体查询来控制的。随着Web布局的发展,实现响应式布局我们从此可以抛开媒体查询来实现。正如@Juan Martín García的《Look Ma, No Media Queries! Responsive Layouts Using CSS Grid》文章中提到的相关技术。

中国第五届CSS大会(即将就要到来),知名CSS专家、Nexmo开发大使 @陈慧晶 老师的主题《新时代CSS布局》或许会给我们带来一些更新的布局技巧和姿势。就我自己而言,或许能猜到该主题要介绍的内容,但我还是非常期待。因为我一直有关注@陈慧晶 老师的文章和在国外分享的相关话题。

或许很多人会有疑问,不使用媒体查询怎么来实现响应式布局。针对该问题,我以前也同样没有思考过,自从最近阅读了@Juan Martin Garcia的文章,才恍然大悟,原来还可以这么玩,同时也再次验证了我的想法,未来的布局是CSS Grid的天下。

在小站上有关于CSS的Grid布局的教程也有不少,有关于相关的概念在相关的文章中也有相应的介绍。如果下文中提到的相关东西要是从未理解过,建议花点时间阅读一下有关于CSS Grid布局相关的教程

正因为CSS Grid布局有非常优秀的特性,比如fr单位repeat()函数minmax()函数auto-fitgrid-auto-flow。所以在没有媒体查询前提下,也同样可以很轻易的实现响应式设计。甚至说可以比依赖媒体查询实现响应式设计更为轻巧。

通过示例来向大家演示,怎么借助CSS Grid布局来实现响应式设计。

你可以打开上面的示例,尝试着改变浏览器视窗的大小,你可以看到相应的变化:

在整个页面中主要分为两个部分,一个是全屏banner区域,另一个是卡片列表页,随着浏览器视窗大小变化,布局也会相应的变化。就如上图所示。

往往实现这样的效果都是会借助于媒体查询来实处理,但上面这个示例,如果你查阅了代码,你会发现我们并没有使用媒体查询,而是使用CSS Grid布局中的一些特性来处理的,关键代码如下:

/* 全屏banner区域*/
.hero {
    ...
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    align-items: center;
}

/* 卡片列表 */
.breweries > ul {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    grid-gap: 1rem;
}

从上面的代码中不难发现,这里最为关键的是在grid-template-columns中通过repeat()函数为网格列布局做为重复填充,其中之一是按auto-fill(自动填充)和minmax()来做相应的处理。有关于这几个概念,这里简单的进行一下陈述:

  • repeat():表示网格列表的重复,可以让我们以更紧凑的形式来处理网格列或行的布局模式。它也接受两个值:重复的次娄和重复的值
  • auto-fill:给repeat()函数使用这个关键词,来替代重复次数。这可以根据每列的宽度灵活的改变网格的列数
  • minmax():能够让我们用最简单的CSS控制网格轨道的大小;minmax()函数接受两个参数,一个最小值和一个最大值
  • fr:代表网格容器中可用空间的一等份

是不是非常的简单,只要你了解了CSS Grid布局中相关的概念,可以让我们很轻易的实现响应式的布局。也就是说,我们今后可以在不依赖于媒体查询的条件之下也可以实现响应式布局,而且这种方式要更为简单和轻巧。

有关于这方面更详细的介绍,可以阅读:

采用容器单元构建强大的布局

容器单元这个概念我也是第一次听说,不得不佩服国外同行的创新和想法。简单地说一下,容器单元是什么?

容器单元是一组CSS自定义属性的集合,允许我们使用列和间距构建的网格系统,通过构建出来的网格系统来实现页面的布局或组件的布局。

简单的说,借助以前网格系统(此网格非彼网格)。在我们最早接触的网格系统,比如说960gs网格,我们将一个容器等分成1224列,列与列之间有一个间距,比如下图这样:

仅通过列宽,列间距和列数三个变量提供一组用于度量和计算的体系。

回过头来思考一下,我们不难发现,大多数网格布局都会依赖于它们们的父容器。而这里提到的容器单元可以帮助我们如何使用CSS自定义属性来克服这样的一个限制,以及如何使用容器单元来构建布局,而且是一个健壮的布局。

如果你从未接触过CSS的自定义属性相关的知识,建议你花一点时间阅读有关于这方面的相关知识。

接下来的内容,所设你对CSS自定义属性有所了解了。那么回到我们最为关心的问题:

  • 如何创建一个容器单元
  • 如何通过容器单元构建布局

了解960gs网格系统的同学都应该知道,构建一个12列还是24列的网格系统,他们都有三个关键元素,即容器宽度网格列宽列与列间距,如果我们用自定义属性来表达的话,可以用下面的方式来描述:

:root {
    --grid-width: 960;
    --grid-column-width: 60;
    --grid-columns: 12;
}

这三个值定义了列的宽度网格比例(就上面的代码所示,网格的比例是60/960)。而列间距是从剩余的空间中自动计算出来。

如果我们给容器宽度设置一个值,比如:

:root {
    --container-width: 84vw;
}

如果借助媒体查询,我们可以在不同的断点下设置不同的容器宽度,比如:

@media (min-width: 800px) {
    --container-width: 90vw;
}
    
@media (min-width: 1200px) {
    --container-width: 85vw;
}
    
@media (min-width: 1400px) {
    --container-width: 1200px;
}

也就是说,我们可以将上面三个核心的概念分成三个不同的单位,也就是容器单元该具备的单元:

  • --column-unit
  • --gutter-unit
  • --column-and-gutterr-unit

结合起来就下面这样,看起来有点复杂,只要你懂CSS自定义属性,就不会觉得复杂:

:root {
    /* 网格属性 */
    --grid-width: 960;
    --grid-column-width: 60;
    --grid-columns: 12;

    /* 网格逻辑(列间距数量) */
    --grid-gutters: calc(var(--grid-columns) - 1);

    /* 网格比例逻辑 列宽 / 网格宽度 */
    --column-proportion: calc(var(--grid-column-width) / var(--grid-width));
    --gutter-proportion: calc((1 - (var(--grid-columns) * var(--column-proportion))) / var(--grid-gutters));

    /* 容器单元 */
    --column-unit: calc(var(--column-proportion) * var(--container-width));
    --gutter-unit: calc(var(--gutter-proportion) * var(--container-width));
    --column-and-gutter-unit: calc(var(--column-unit) + var(--gutter-unit));

    /* 容器宽度 */
    --container-width: 80vw;
}

/* 媒体查询改变容器宽度 */
@media (min-width: 1000px) {
    :root {
        --container-width: 90vw;
    }
}

@media (min-width: 1400px) {
    :root {
        --container-width: 1300px;
    }
}

在使用网格的时候,我们有的时候会需要跨列合并,使用的时候可以像下面这样:

.panel {
    width: calc(6 * var(--column-and-gutter-unit) - var(--gutter-unit));
}

有关于这方面更详细的教程,可以阅读@Russell BishopBuilding Robust Layouts With Container Units一文。

缓动函数的反转

熟悉CSS中animationtransition的同学,都知道这两个属性中都有缓动函数的概念,即animation-timin-functiontransition-timing-function对应的属性值。这两个属性常见的属性值主要有:linearease-inease-outease-in-out等。除了这几个还有贝塞尔曲线函数cubic-bezier()

从上图中可以看出,cubic-bezier()函数主要由两个点来控制,比如说点(x1, y1)(x2,y2),结合起来就是cubic-bezier(x1,y1,x2,y2)。而在animation中还有另一个属性animation-direction可以让动画反转(animation-direction: reverse)。

为了反转动画的缓动曲线,我们需要在它的轴上旋转180度,找到一个全新的坐标

如果缓动曲线的初始坐标为x1, y1, x2, y2,那么反转之后的坐标即为**(1-x2), (1-y2), (1-x1), (1-y1)**。既然知道了基本原理之后,我们同样可以借助CSS自定义属性,用代码来表示:

:root {
    --x1: 0.45;
    --y1: 0.25;
    --x2: 0.6;
    --y2: 0.95;
    
    --originalCurve: cubic-bezier(var(--x1), var(--y1), var(--x2), var(--y2));
}

根据上面的公式,可以计算出反转后的缓动曲线:

:root {
    --reversedCurve: cubic-bezier(calc(1 - var(--x2)), calc(1 - var(--y2)), calc(1 - var(--x1)), calc(1 - var(--y1)));
}

为了更易于理解,把上面的代码稍作调整:

:root {
    /* 原始坐标值 */
    --x1: 0.45;
    --y1: 0.25;
    --x2: 0.6;
    --y2: 0.95;
    
    --originalCurve: cubic-bezier(var(--x1), var(--y1), var(--x2), var(--y2));
    
    /* 反转后的坐标值 */
    --x1-r: calc(1 - var(--x2));
    --y1-r: calc(1 - var(--y2));
    --x2-r: calc(1 - var(--x1));
    --y2-r: calc(1 - var(--y1));
    
    --reversedCurve: cubic-bezier(var(--x1-r), var(--y1-r), var(--x2-r), var(--y2-r));
}

最后来看一个@Michelle Barker的《Reversing an Easing Curve》文中提供的一个示例:

小结

虽然文章中提到的知识点非常的有意思,不管是使用CSS Grid实现响应式设计,还是容器单元构建页面布局,或者说缓动曲线的反转。看上去有点高级和复杂,但简单地说,都是基于一些基础知识来构建的,比如CSS的Grid布局和CSS自定义属性。其实这两个知识点都不是很新的概念,但把这些东西结合起来,可以帮助我们达到更为神奇和强大的功能。如果你还未接触过这方面的知识,那么你需要开始去学习了,在未来的CSS中,这两个东东都是非常重要的知识点,也是非常有用的知识的。在不久的将来,这两个部分都可以运用于你的项目中,我在去年的项目中就已经尝试使用了CSS的自定义属性,基本上能达到预期的效果。让我更易于维护和修改项目的需求。如果你在这方面有更多的经验,欢迎在下面的评论中与我们一起分享。air max 90 essential UK