Flexbox 和 Sass Grid 教程:如何简化响应式设计
已发表: 2022-03-11最近,我面临创建自己的网格系统的挑战,由于重新发明轮子作为一种学习体验总是有用的,我就去做了。 我知道这将是一个有趣的挑战,但结果竟然如此简单,让我感到惊讶!
在这个实验中,我们将研究 Flexbox 布局以及它们如何允许优雅地实现布局而不做任何疯狂的 hack。 此外,如果您不熟悉 Sass,我们将了解它的工作原理并使用一些方便的 Sass 实用程序。 你甚至可以学到一些关于 CSS 网格的新知识,比如 Bootstrap 中的网格。
Sass 和 Flexbox 的简短介绍
Sass 基本上是一个可以让你避免 CSS 的一些缺点的工具,它是一种可以解释为 CSS 的脚本语言。 如果您已经在编写 CSS 样式,那么语法看起来非常熟悉,但它的工具箱包括变量、可重用的 mixin 以及 if、for、each 和 while 指令等。 Sass 最方便的事情之一是任何有效的 CSS 代码都是有效的 Sass,因此您可以逐步转换您的代码库。
for循环的一个简单示例:
@for $i from 1 through 3 { .a-numbered-class-#{$i} { width: (20 * $i) * 1px; } }
这个简单的循环从 1 迭代到 3 并创建类。 迭代的索引将被方便地存储在$i
中。 我们还可以进行数学运算并打印.a-numbered-class-X
三次,每次具有不同的宽度。 此代码输出:
.a-numbered-class-1 { width: 20px; } .a-numbered-class-2 { width: 40px; } .a-numbered-class-3 { width: 60px; }
正如我们所看到的,我们可以抽象出许多您必须在 CSS 中完成的工作。 在 CSS 中,您必须手动复制粘贴和修改,这显然更容易出错且不太优雅,如果您还没有尝试过,请不要再浪费时间了!
Flexbox 代表 Flexible Box,一种动态定位和分布元素的 CSS3 布局系统。 它是一个非常强大的工具,可以以最小的努力实现灵活的布局。 有关如何学习 Flexbox 的更多详细信息,请查看 Chris Coyier 的 Flexbox 完整指南。
网格
继续讨论网格本身,让我们从它的基本元素开始。 它们将受到 Bootstrap 的网格元素的启发:容器、行和列,每一个都包含在前者中。
我们将对类的名称使用 BEM 命名约定。 BEM 约定使用起来非常简单,并且添加了很多关于元素及其上下文的信息。 简而言之,你有:
- Blocks ,它“封装了一个独立的实体,它本身就有意义”:
.block
。 - 元素,它们是“块的一部分,没有独立的意义”,由块名称、两个下划线和元素表示:
.block__elem
- 修饰符,例如“块或元素上的标志”,用两个破折号表示:
.block .block--mod
。
容器
这是网格的最外层元素,它将包含我们的行元素。 有两种类型的容器: .container
和.container--fluid
。
.container
的行为定义为某个点下方宽度的 100%,其上方具有最大固定宽度,并且左右边距相等:
$grid__bp-md: 768; .container { max-width: $grid__bp-md * 1px; margin: 0 auto; }
通过扩展和收缩“输出”窗口来玩它
对于始终具有 100% 宽度的流体容器,我们只需使用修改器覆盖这些属性:
&--fluid { margin: 0; max-width: 100%; }
在这里玩。
那很简单! 我们现在已经实现了两个容器。 让我们继续下一个元素。
行
行将是我们内容的横向组织者。
我们将使用 Flexbox 来定位行的子元素,使它们环绕以防止溢出,并在行内为它们提供 100% 的宽度(以便我们以后可以嵌套它们)。
&__row { display: flex; flex-wrap: wrap; width: 100%; }
如果子元素的宽度总和大于自身,这会将子元素并排放置并换行。 我们现在只需要添加一些 div,它看起来像这样:
通过扩展和收缩“输出”窗口来玩它。
事情开始成形,但这还不是 CSS 网格。 它不见了……
列
列是网站内容所在的地方。 他们定义了行被划分为多少部分以及它们占据了多少。 我们将进行十二列布局。 这意味着我们可以将行分成一个或最多十二个部分。
首先,一些基本的数学。 当我们想要一列时,它的宽度应该是 100%。 如果我们想要十二列。 那么每个应该占据宽度的 8.333…% 或 100/12。
使用 Flexbox,要以这种方式分发内容,我们可以使用flex-basis
。
为了分成四列,我们现在添加如下内容:
flex-basis: (100 / 4 ) * 1%;
这样,我们可以让每个元素占据 25% 的宽度——或者我们想要的任何百分比。
在这里玩。
让我们让它更有活力。 由于我们希望这反映我们可能的类,让我们调用.col-1
,一个列 div 的类,将具有 8.333% 的宽度,因为它们中的 12 个在它们必须换行之前应该适合。 该百分比将自始至终递增,直到.col-12
占据 100%。
$grid__cols: 12; @for $i from 1 through $grid__cols { .col-#{$i} { flex-basis: (100 / ($grid__cols / $i) ) * 1%; } }
只是为了澄清发生了什么,假设我们想将宽度分成四个相等的部分。 我们需要.col-3
,因为它在 12 中适合 4 次,这意味着.col-3
应该有 25% 的 flex-basis:
100 / ($grid__cols / $i) 100 / (12 / 3) = 25
这已经开始看起来像一个网格了!
在这里玩。
屏幕宽度相关的列
我们现在希望能够拥有一个在移动设备上具有一定宽度但在平板电脑上具有不同宽度的元素等等。 我们将根据窗口的宽度使用某些断点。 我们的 UI 将对这些断点做出反应,并适应适合不同设备屏幕尺寸的理想布局。 我们将按大小命名断点:小 (sm)、中 (md) 等等, .col-sm-12
将是一个在sm
断点之前至少占据 12 列的元素。
让我们重命名.col-*
类.col-sm-*
。 由于我们的网格将首先是移动的,因此我们将其属性应用于所有屏幕尺寸。 对于我们需要在更大的屏幕上表现不同的那些,我们将添加类: .col-md-*
。
想象一个带有.col-sm-12
和.col-md-4
的元素。 预期的行为将是,在断点“md”(中)之下,它将有 100% 的宽度,而在它之上,它将有 33.333%——这是很常见的情况,因为在移动设备上,您可能需要将元素堆叠在顶部而不是旁边当您的宽度受到更多限制时,彼此。

为此,我们需要在断点处添加一个媒体查询(一个包含仅在特定宽度之上或之下或在特定设备上执行的代码的表达式),并像我们之前为sm
所做的那样创建我们的md
列:
@media screen and (min-width: $grid__bp-md * 1px) { @for $i from 1 through $grid__cols { &__col-md-#{$i} { flex-basis: (100 / ($grid__cols / $i) ) * 1%; } } }
在这里玩。
这已经接近有用的东西了。 这有点湿(明白吗?它不干......),所以让我们让它更抽象。
正如我们所看到的,我们需要为每个断点创建一个媒体查询,所以让我们创建一个 mixin 来接收一个动态创建媒体查询的断点。 它可能看起来像这样:
@mixin create-mq($breakpoint) { @if($breakpoint == 0) { @content; } @else { @media screen and (min-width: $breakpoint *1px) { @content; } } }
现在,让我们将用于创建__col
类的内容包装在一个名为create-col-classes
的 mixin 中,并使用create-mq
mixin。
@mixin create-col-classes($modifier, $grid__cols, $breakpoint) { @include create-mq($breakpoint) { @for $i from 1 through $grid__cols { &__col#{$modifier}-#{$i} { flex-basis: (100 / ($grid__cols / $i) ) * 1%; } } } }
就是这样。 为了使用它,我们现在在 Sass 映射中定义断点,并对其进行迭代。
$map-grid-props: ('-sm': 0, '-md': $grid__bp-md, '-lg': $grid__bp-lg); @each $modifier , $breakpoint in $map-grid-props { @include create-col-classes($modifier, $grid__cols, $breakpoint); }
我们的网格系统基本完成了! 我们已经定义了一个默认的.container__col-sm-*
类,我们可以使用container__col-md-*
和container__col-lg-*
在更大的屏幕上修改它的行为。
我们甚至可以嵌套行! 在这里玩。
这样做的好处是,如果我们现在希望它具有与 Bootstrap v4 相同的断点,我们只需要这样做:
$grid__bp-sm: 576; $grid__bp-md: 768; $grid__bp-lg: 992; $grid__bp-xl: 1200; $map-grid-props: ( '': 0, '-sm': $grid__bp-sm, '-md': $grid__bp-md, '-lg': $grid__bp-lg, '-xl': $grid__bp-xl );
就是这样! 在这里玩。
请注意 Bootstrap 如何采用比我们最初讨论的更完整的移动优先方法。 最小的窗口大小没有sm
或md
之类的后缀,原因是等效于.container__col-X
的类不仅适用于 0 到 576px 的窗口宽度; 如果我们不明确地覆盖它,它将是每个窗口大小的列数。 否则,我们可以添加类.container__col-sm-Y
使其在sm
断点之间的宽度为 Y 列。
偏移量
偏移量将相对于前一列增加一个边距。 .container__col-offset-4
将在所有屏幕尺寸上添加margin-left: 33.333%
。 .container__col-md-offset-4
将执行相同的操作,但高于md
断点。
现在的实现很简单; 我们在创建类的同一个循环中添加了一个-offset
属性,但是我们编写了属性margin-left
而不是flex-bases
。 我们还必须为-offset-0
做一个额外的操作,因为我们可能想在更大的屏幕上清除边距:
@mixin create-col-classes($modifier, $grid-cols, $breakpoint) { @include create-mq($breakpoint) { &__col#{$modifier}-offset-0 { margin-left: 0; } @for $i from 1 through $grid-cols { &__col#{$modifier}-#{$i} { flex-basis: (100 / ($grid-cols / $i) ) * 1%; } &__col#{$modifier}-offset-#{$i} { margin-left: (100 / ($grid-cols / $i) ) * 1%; } } } }
我们现在有了功能齐全的偏移量! 在这里玩。
可显示性
我们有时想在某个点下方或上方显示/隐藏元素。 为此,我们可以像 Bootstrap v4 那样提供类。
例如,类.hidden-md-up
将从md
断点向上隐藏任何具有此类的元素; 相反, .hidden-md-down
会将其从断点向下隐藏。
代码很简单:我们只是迭代断点并for each
断点创建一个.hidden-*
类。 不过,我们将create-mq
类修改为更抽象一点:
@each $modifier , $breakpoint in $map-grid-props { @if($modifier == '') { $modifier: '-xs'; } @include create-mq($breakpoint - 1, 'max') { .hidden#{$modifier}-down { display: none !important; } } @include create-mq($breakpoint, 'min') { .hidden#{$modifier}-up { display: none !important; } } }
作为旁注,这不是!important
的少数几个好的用途之一吗? 请注意,该元素可能具有任意更大的特定性display: block
规则,但我们仍然希望将其隐藏在断点下方或上方。 如果您不同意这种方法,请在评论中告诉我!
就是这样:我们现在有一个可展示性系统。
在这里玩。
结论
虽然这个“框架”还没有为生产做好准备,但它展示了 Flexbox 布局的强大功能以及 Sass 的便捷性。 只需几行代码,我们就实现了 CSS 框架/网格的核心功能。
它也可以作为一个教训,即几乎任何软件的基本版本都可以非常容易地实现。 现实世界的具体问题开始加起来并变得困难。
我创建了一个 GitHub 存储库,您可以在其中提交问题或拉取请求。
您希望实现哪些功能? 实现可以简化还是更优雅?
随时让我知道您对以下评论的看法。