重新思考响应式网页设计
从想法到革命性的网络网格系统;)
我们现在做得怎么样了?
说实话,我们大多数人从来没有做过响应式网页设计,我们都只是制作自适应网页(我们只关心几个特定的屏幕尺寸)并称之为响应式!
响应式的意思更接近上述内容。但我们能实现它吗?
多年来,我一直在大学里做用户体验讲座,向学生们展示这张图片,告诉他们这是一个真正的响应式设计,但我意识到,我们做不到。实际上,没有一个网格框架(就我所知)能够完成这项任务。
由于响应式网页设计没有终极解决方案,我们大多数人坚持使用类似 Bootstrap 网格系统之类的系统。不幸的是,这类网格系统远非响应式设计,而且与当前的 FE 技术栈配合得也不好。
从现在开始,我会经常提到 Bootstrap 网格系统。这样一来,我指的是一组网格系统(Bootstrap、Foundation、Tailwind CSS 等),而不是单指 Bootstrap。
类似 Bootstrap 的网格有什么问题?
最好先从一个例子开始。为了说明还有更多类似于 Bootstrap 的网格系统,我们来看一下 Tailwind 的网格:
<div class="flex flex-wrap">
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/6 mb-4 bg-gray-500"></div>
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/6 mb-4 bg-gray-400"></div>
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/6 mb-4 bg-gray-500"></div>
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/6 mb-4 bg-gray-400"></div>
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/2 xl:w-1/6 mb-4 bg-gray-500"></div>
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/2 xl:w-1/6 mb-4 bg-gray-400"></div>
</div>
开发人员对于上述代码有什么抱怨?
- 这是很多代码。
- 它很难读。
- 这很难推理。
开发人员想要的是摆脱这种情况,w-full sm:w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/6 mb-4 bg-gray-400
转而采用如下方式:col-2
用户体验设计师对于网格有什么抱怨呢?
- 它不支持大屏幕。
- 它与组件配合得不太好。
- 它允许您声明针对特定条件的行为,而不是让您声明针对该行为的规则。
让我们更详细地研究一下上述三点。
1)宽屏支持
只需在 4k 或 8k 显示器上尝试随机 Web 即可;)
类似 Bootstrap 的网格是自适应的。这意味着它们有一些屏幕尺寸限制;它们使用断点。最大的尺寸——xl,通常代表 1200px 左右。任何大于 1200px 的尺寸都无关紧要,你需要自行处理其响应速度。很多网页在大屏幕上会卡住或看起来非常奇怪。
2)网格与组件
类似 Bootstrap 的网格在组件内部几乎毫无用处。为什么?
假设你有一个宽度为 400px 的组件,其中包含四个宽度为 200px 的子元素。显然,它的内容需要与同一组件的另一个宽度为 800px 的实例表现不同,对吧?在第一种情况下,你更喜欢 2x2 的网格,在另一种情况下,你更喜欢 4x1 的网格。不幸的是,由于两个实例的组件代码相同,并且屏幕尺寸是给定的,你……
Bootstrap 示例中的列宽由百分比定义
由于我们已经处于组件时代(React、Angular、Vue、Svelte、Web Components)……类似引导程序的网格似乎注定要失败,但没有什么东西可以取代它们,至少没有可以在所有前端框架中使用的通用的东西。
您仍然可以使用类似 Bootstrap 的网格系统以及面向组件的框架进行页面/应用布局。但说实话,当您看到这种网格系统会生成多少 CSS 代码,以及实际布局中会用到多少类时,最好还是放弃它。您随时可以实现自定义解决方案。
解决方案,修复上面的 Bootstrap 示例表单,似乎是具有最小和最大宽度的列的定义。
列采用所需的宽度并按比例消耗剩余部分。
3)网格的行为
引导网格“让你声明特定条件下的行为,而不是让你声明该行为的规则”,这种说法听起来有点哲学。翻译成人类的语言:
Bootstrap 网格允许您在特定断点处声明某些块的大小(您需要用自己的过度声明式代码覆盖所有页面宽度的情况,例如 Tailwind 示例)。通常,必须编写大量代码才能实现一个简单的目标:使元素在任何屏幕尺寸下都保持相同的大小。Bootstrap 网格系统的附加价值仅仅是将内容与网格对齐。
我们真正想要的是声明一组规则,并让网格的内容基于这些规则进行流动、跨越、收缩和拉伸。
我们能否跳过所有屏幕尺寸的声明,并自动对齐到网格?是的,我们可以!
我知道,当你切换到智能“自动”网格时,与你的设计师/产品负责人争论起来可能会很困难。你只需要向他们解释,即使他们为你提供了移动端、平板和桌面端的设计,也总会有一些介于两者之间的设计,而这些设计是他们无法控制的。设计应该基于指南、组件库以及对布局规则的基本理解,而不是仅仅基于几个像素级完美的模拟;)
新网格——设计头脑风暴
真正响应的网格系统应该具备哪些特征?
- 与屏幕尺寸无关(可在组件中使用)。
- 网格应包含与虚拟网格对齐的元素(网格间隙应在所有方向上精确对齐)
- 网格容器的子容器可以跨越多个虚拟列(最好也可以跨越行)
- 列应具有最佳大小,并以诸如或 之类的单位
px
rem
给出,而不是百分比。仅以百分比定义列的问题在于,它迫使我们针对特定断点(例如 sm、md、lg)定义元素的行为。百分比在不同条件下代表不同的实际大小。 - 网格应该由列定义,而不是像 Bootstrap 那样反过来。Bootstrap 网格始终有 12 列;对于移动设备来说太多,而对于超高清屏幕来说又太少。
- 列应该适应容器大小(如果列比容器宽,它应该缩小到容器的宽度)。
新电网——选择技术
使用什么技术?可用的技术似乎是 Flex-box 和 CSS Grid。它们似乎都能满足我们的大部分需求,但并非全部。让我们看看这些方法的不足之处:
弹性盒
如果我们想避免以父元素的百分比定义列,我们需要通过类似以下的方式定义网格元素:
.col {min-width:100px; max-width:200px;}
遗憾的是,这仅在最后一个 .col 元素与网格容器右边缘对齐时才有效。如果第一行有 3 个 .col 元素,而第二行只有 2 个 .col 元素,则这些元素将不再与虚拟网格对齐。此行为无法修复。这对于 Flex-box 来说是行不通的。
网格
display: grid表现更好一些,我们可以使用:
grid-template-columns: repeat(auto-fit, minmax($grid-col-width, 1fr));
auto-fit
告诉网格拉伸现有列,直到有空间容纳新列。minmax()
定义列的最小宽度和最大宽度。最小值表示所需的列宽,最大值(1fr) 表示列的宽度为 1/列数,如果列数不足,则以最小宽度填充容器。
它的作用与上面的弹性框示例完全相同,不同之处在于它始终适合网格布局,太棒了!但它还有另一个缺陷。当你告诉一个元素跨越三列,但只有两列适合容器时,跨越的元素就会溢出容器。这个问题唯一有意义的解决方案似乎是期待已久的元素查询(“响应式条件适用于页面元素,而不是浏览器的宽度或高度”的查询)。由于它们仍处于开放提案的形式,我不得不自己编写“元素查询”程序。
元素查询
我最初尝试使用某种 polyfill,但那些支持良好的 polyfill 速度慢且相对较大(大约一千行代码)。由于我的目标是构建一个超小型的网格框架,所以不得不另辟蹊径。最合适的方案是使用新的“ResizeObserver”和“customElements”JS API。它们的支持程度并非最佳(约占全球市场的 70%),但速度很快,并且完全符合需求。我的网格元素查询大约只需要 35 行代码,这太棒了。
所需的 CSS 和 JS 代码大小为 1KB(gzip 压缩后),并涵盖了头脑风暴部分的所有要求。实际上,它的功能远不止于此!
我现在跳过实施细节并向您展示结果,我的网格可以做什么;)
“Eq 网格”
为了避免分别提供 JS 和 CSS、为 DOM 元素添加事件监听器、监听 DOM 变化等等,我创建了一个“自定义元素”,您只需导入并初始化即可。它会根据提供的参数生成所有必需的 CSS 类。您只需要:
npm i eq-grid --save
然后在你的 main.js 文件中:
import { initEqGrid } from 'eq-grid';
initEqGrid(120, 10, 'px', 10); // ['column width', 'gap width', 'units', 'max-columns-span/collapse']
从那时起,您可以<eq-grid>
在 html 模板中使用元素,并且所有神奇的操作都在后台完成。
它几乎可以在任何地方运行,包括纯 JS/HTML、React、Angular、Vue、Svelte 和其他现代框架。
让我们看一些例子...只需以全屏模式打开以下沙箱并尝试调整它们的大小。
用于内容的网格:
适用于文章预告、卡片、缩略图等
在上面的例子中,您可以看到类.eq-col-3-5 .eq-col-2-4
,您可以使用它们以声明方式覆盖网格的自动行为。
嵌套网格:
通过嵌套,您可以避免某些元素自由地跨“行”排列。这在创建如下所示的布局时非常有用。
用于布局的网格:
.eq-col-1
你可能会想,为什么右侧的 网格这么宽?这怎么可能?这里的根网格只有两.eq-col-1
列,左侧的所有内容都位于一个嵌套网格中。每列的最小宽度为 100px,最大宽度为 1fr(父级宽度的一部分)。在这种情况下,最大值占主导地位。如果根网格中有很多元素,则改用 min(100px) 规则。这适用于左侧的嵌套网格。
请记住,子网格的内容不会对其父网格产生影响。
顺便说一下,这个示例有 3 个嵌套网格。使用这种嵌套技术,您可以更好地控制折叠或拉伸的内容、时间以及如何折叠或拉伸。
这对于大型布局非常有用。
Eq Grid 和 rem
rem
当您将网格设置为使用单位时,它还可以提供另一个很酷的功能:
initEqGrid(10, 1, 'rem');
任何使用rem
单位的元素都会根据其字体大小计算其大小HTML
。这使我们能够通过HTML
元素上的媒体查询来缩放列。如果我们使用多边形流体尺寸调整技术,则可以线性缩放字体。下面我将字体稍微缩放到 1280px。然后,我开始以与窗口增长相同的速度缩放。
html {
font-size: 14px;
}
@media screen and (min-width: 320px) {
html {
font-size: calc(14px + 4 * ((100vw - 320px) / 960));
}
}
@media screen and (min-width: 1280px) {
html {
font-size: calc(18px + 158 * ((100vw - 1280px) / 10000));
}
}
@media screen and (min-width: 11280px) {
html {
font-size: 176px;
}
}
上述操作与 eq-grid 结合使用的效果rems
非常棒。一旦窗口宽度超过 1280px,所有内容(网格、字体等)都会像放大一样开始缩放。这样,您就可以在 8k 显示器上清晰地查看网页。您可以设置缩放和添加新列之间的比例——只需调整字体大小18px + 158
和176px
即可。
请参阅此处的示例(您需要点击“打开沙盒按钮”,否则它将无法工作)。然后缩小视图查看其工作原理 ;)
结论
我希望 Eq Grid 系统能够满足所有常见的开发/用户体验需求。您可以以非常简单和自动化的方式使用它,让内容像第一张图片中的水一样流动。或者,您可以更具声明性,根据网格大小微调网格元素的折叠和收缩方式。这取决于您的需求。
-
它确实反应灵敏。
-
它可以从零扩展到无限,而不会影响用户体验。
-
这也是我自己的想法的第一次实现,它肯定可以改进,所以......
如果您能留下评论,我将不胜感激——您的想法、您错过的内容,或者您喜欢/不喜欢的地方。任何反馈都将不胜感激!也许我们可以让网格系统在组件时代再次变得可行。
您可以在 npm 上查看 eq-grid 网格:eq-grid on npm。
这里描述了所有 Eq-grid 类——它们的作用以及如何使用它们。
您还可以查看如何在 React 应用中使用网格。