让我们想象一下,我们正在建立一个HomeButton
组件,我们可以在标题中插入一些东西,使用户很容易找到回家的路。
这就是我们目前的情况。
function HomeButton() {
return (
<LinkButton href="/">
<BackIcon />
Go back home
</LinkButton>
);
}
render(<HomeButton />);
我们在正确的轨道上,但我们的箭头图标完全 "挤 "在了文字上*。我觉得这绝对是一种幽闭恐惧症。😬
我们可以通过用<span>
,并给它一些边距来解决这个问题。
function HomeButton() {
return (
<LinkButton href="/">
<BackIcon />
<span
style={{ marginLeft: 16}}
>
Go back home
</span>
</LinkButton>
);
}
render(<HomeButton />);
不过,margin
真的是最好的工具吗?
这里有一个替代解决方案。
function HomeButton() {
return (
<LinkButton href="/">
<BackIcon />
<Spacer size={16} />
Go back home
</LinkButton>
);
}
render(<HomeButton />);
我不使用margin,而是明确地创建一个新元素,在图标和文本之间增加一些空间
这不是一个新的想法--事实上,它是一个非常古老的想法。而我认为现在是卷土重来的时候了。
一些历史背景
在90年代末,如果你打开一个典型网站的源代码,你可能会遇到这个好奇的家伙。
<img alt="" src="spacer.gif" width="1" height="1" />
当时还不存在CSS,网页布局是用HTML表格来构建的。表格很不稳定,空的单元格会塌陷,破坏布局,所以开发者会把这张图片扔到表格单元格中,使其保持开放。
使用GIF是因为GIF是唯一支持透明度的图像格式(这是在PNG之前)。我们的隔板朋友由一个透明像素组成,是一个完全空的图像。
这个多功能的工具还有一个用途:它可以被拉伸和压扁成任何大小或形状,在元素之间形成一个无形的缓冲区。例如,如果你想在两张桌子之间留出一点空隙,那么间隔GIF就是你快速拨号的第一号。
那么,它怎么了?好吧,在动荡的十年里,间隔GIF只是一个大转变中的一小部分。
CSS被添加到浏览器中,为造型提供了一个替代方案。这种语言在设计之初并没有考虑到布局问题,但开发人员很快就意识到,通过使用一些巧妙的float
,它可以完全取代表格布局!这一点在过去被争论不休。
这一点在网上被争论不休。守旧派对他们的表格布局、间隔GIF和他们舒适的生活方式感到高兴,而新的喜鹊派*则主张放弃HTML布局,让CSS全权负责所有展示性的东西。
这场辩论与网络上的另一场变革同时发生:我们正在构建的东西变得越来越复杂,越来越像应用程序,而不像文档。"果酱式开发 "*对于快速出货来说是很好的,但是它很混乱,难以维护,而且没有可扩展性。
通过分离我们的关注点--HTML负责结构,CSS负责布局和展示,JS负责行为--我们有一个惯例,可以帮助我们降低复杂性,最终使事情更容易维护。
在HTML中做任何展示性的工作都是一种假象。<font>
、<center>
、<strike>
和<marquee>
等标签被驱逐,并被CSS替代。<table>
被保留给真正的表格。
十年来,每个人都认为,为每个关注点设置不同的支柱是一个好主意。然后,Facebook发布了FaxJSReact.js。
事情越变越多...
React.js的一个决定性特征是,HTML是从JS内部创建的。再加上像styled-components这样的工具,我们的三大支柱已经合二为一了*。我们已经走过了一圈,Jambalaya开发又开始流行了。
不可否认的是,当涉及到管理像Facebook这样的大型网络应用的复杂性时,React是一个强大的工具。这是否意味着社区做出了错误的选择,所有这些年,当我们用技术来区分关注点时?
我不这么认为。我记得在21世纪初,我还在修补网络开发,那是一个狂野的西部;我记得用PHP来动态生成JS,更新HTML来改变CSS。那是一个混乱的局面。
因此,结构是一个伟大的想法,它仍然是一个伟大的想法,但它不是唯一的伟大想法。构建一个产品并没有一个正确的方法。诀窍是要有某种惯例,以便随着应用程序的规模和复杂性的增加,事情不会变成面条。
我非常喜欢这张现在很有名的图片。
重要的是,你可以画出边界线。重要的是你可以画出边界线,至于这些线是在哪个轴上画的就不重要了。
在最初的Jambalaya表格布局时代,间隔线GIF是一种美味的补充成分。当我们转而制作解构的三明治时,它的味道就不那么好了。但现在,我们中的许多人都在使用组件驱动的架构,我们的代码可能会从一撮间隔GIF中受益。*
代码
下面是我的间隔组件的写法。
// Spacer.js
import styled from 'styled-components';
function getHeight({ axis, size }) {
return axis === 'horizontal' ? 1 : size;
}
function getWidth({ axis, size }) {
return axis === 'vertical' ? 1 : size;
}
const Spacer = styled.span`
display: block;
width: ${getWidth}px;
min-width: ${getWidth}px;
height: ${getHeight}px;
min-height: ${getHeight}px;
`;
export default Spacer;
如果你不使用styled-components/Emotion,这里有一个普通的React替代品。
// Spacer.js
import React from 'react';
const Spacer = ({
size,
axis,
style = {},
...delegated,
}) => {
const width = axis === 'vertical' ? 1 : size;
const height = axis === 'horizontal' ? 1 : size;
return (
<span
style={{
display: 'block',
width,
minWidth: width,
height,
minHeight: height,
...style,
}}
{...delegated}
/>
);
};
export default Spacer;
唯一需要的道具是size
。默认情况下,它产生一个正方形。
// Produces a 16px × 16px gap:
<Spacer size={16} />
你也可以指定一个单轴。
// Produces a 32px × 1px gap:
<Spacer axis="horizontal" size={32} />
这个组件使用像素值,因为我发现经常需要选取超出比例的值来进行光学排列。也就是说,这个模式可以很容易地改编为使用设计令牌来代替。
<Spacer space="sm" />
<Spacer space="md" />
<Spacer space="lg" />
<Spacer space="xl" />
你可能想知道为什么我对我的Spacer代码做了某些决定。让我们来探讨一下其中的一些
一个块状的跨度
最初,我的<Spacer>
组件渲染的是div
,而不是span
,但我发现这有点局限性。根据HTML规范,div不应该被放在某些元素中,比如p
和button
。
span
嵌入元素是一个更灵活的标签,但它的默认值是 ,而内联元素真的不是为布局任务而设计的;你不能给它们一个明确的 或 ,这就是我们想要一个Spacer的全部原因!display: inline
width
height
一般来说,我希望我的<Spacer>
,以分隔块级元素,所以给它display: block
,而不是display: inline-block
。在极少数情况下,如果我想分隔内联元素,可以通过一些组合来实现。
const InlineSpacer = styled(Spacer)`
display: inline-block;
`;
最小尺寸
除了设置width
和height
,我还设置了min-width
和min-height
。
这样做是因为width
其实更像是一个建议,而不是一个硬约束。考虑一下这种情况。
function HomeButton() {
return (
<LinkButton href="/" style={{ maxWidth: 200 }}>
<BackIcon />
{/* Quick Spacer implementation */}
<span
style={{
display: 'inline-block',
width: 16,
height: 16,
background: 'hotpink',
}}
/>
Go back home
</LinkButton>
);
}
render(<HomeButton />);
在这个例子中,容器的宽度受制于maxWidth
,而我们没有足够的空间。我把<Spacer>
换成了一个粉红色的盒子,这样我们就可以看到发生了什么。
这个粉红色的盒子希望是16px乘16px,但从渲染的输出结果来看,可能很明显它被挤压了;它不是一个正方形
我们把浏览器放在了一个很困难的地方:没有足够的空间来渲染所有的东西!在默认情况下,浏览器会根据实际情况做出判断。默认情况下,它做了一个有根据的猜测,并决定挤压空的孩子。这是一个合理的假设,但在这种情况下,它不是我们想要的!
min-width
是一个更顽强的属性。它不会被推来推去。这让我们告诉浏览器,这个元素很重要,我们不希望它被挤掉。它应该找一个不同的元素来挑剔。
试着把粉色方框的width
改为minWidth
,看看这个动态的作用吧!
为什么我们的Spacer比它的兄弟姐妹们更重要?因为当涉及到维持一个专业的、有光泽的用户界面时,一致的间距是绝对关键的。我希望能够相信我页面上的每个按钮在图标和文本之间都有一致的间隙,即使这意味着有截断的或多行的文本。
响应式版本
上面显示的<Spacer />
组件不是响应式的;它在每个视口都占用相同的空间。
我倾向于在像上面描述的那种情况下使用这个组件,即不需要动态间隔的情况。但是,如果我真的遇到了需要响应式间隔的情况,我会更新它以使用像这样的API。
<Spacer
size={32}
when={{
lgAndUp: 64,
xlAndUp: 96,
}}
/>
我喜欢把道具名称when
,用于上下文的事情;它读起来很好(至少从消费者的角度看是这样。这是最重要的观点,IMO)。
这个道具的实现将取决于你的特定造型方案和主题。
优点和缺点
好吧,那么我究竟为什么要这样做呢?为什么不像其他人那样使用margin?
我有几个原因。
-
从语义上讲,有时我觉得这样做很奇怪。在我们的home-button的例子中,页边距应该放在后面的箭头上,还是放在文本上?我不觉得任何一个元素应该 "拥有 "这个空间。这是一个明显的布局问题。
-
边距是很古怪的。它们以奇怪和令人惊讶的方式折叠。在上面的例子中,边距没有塌陷,但有一个内在的心理负担;我总是需要记住它,并把它考虑进去。
-
还有结构上的影响。在上面的例子中,我将文本包裹在一个
<span>
,这在某些情况下会造成问题(例如,网格中的孩子)。在父代和子代之间多放一层可能会有问题。 -
边缘从根本上说是与现代组件架构不一致的。它们会渗出,渗出组件的边界,渗出到邻近的元素。
越来越多的开发者放弃了页边,转而依赖布局组件。<Spacer>
,是这个工具包中的一个伟大工具。
那 "gap "呢?
当你使用CSS Grid时,一个天赐的属性变得可用:grid-gap
。通过grid-gap
,你可以以一种直观的、语义化的方式设置容器中所有子节点之间的间距。我喜欢grid-gap
。
CSS规范最近增加了一个gap
属性,其工作方式完全相同,但也可以与flexbox一起使用。这真是太好了。💯
浏览器的支持越来越好,但它还没有准备好进入黄金时段。
当它被完全支持的时候呢?这将使我的Spacer
组件成为多余的吗?我不这么认为。我仍然花大量的时间在 "流式布局 "中工作,在网格/柔性父体之外。gap
将解决我们的许多问题,但它不会解决所有的问题。
几年前我开始尝试使用<Spacer>
组件,在那段时间里,我在这个博客上增加了大约100个实例(包括像我的Effective Portfolio书和Operator Lookup的托管项目)。
说实话,这很不错。我真的没有任何抱怨!
我最担心的是DOM大小问题。谷歌建议将网页保持在1500个DOM节点以下,而我的一些更复杂的网页却超过了这个界限。
不过我还没有发现我需要添加那么多的间隔物。在实践中,我把它当作我的<ShiftBy>
,它是我可以在特定情况下部署的一个 "特殊代理"。大多数页面只需要一小部分。
我的大部分间距问题都是由padding、其他布局组件和grid-gap
/gap
来处理的。而且,是的,我有时还是会使用margin,尽管我越来越少地使用它了。
避免DOM污染还有其他原因,比如可访问性和SEO。但据我所知,一些额外的空span
,在这些方面并无害处。
一个增长的趋势?
当我为这篇博文做一些研究时,我偶然发现了spacerGIF.org,一个提供spacer gifs的API!它并不是以前的遗迹。它也不是十年前的遗物,它在过去一年中增长了30倍
我猜想许多读者,特别是那些在游戏中呆了很久的人,会对这个想法有一种内脏的负面反应。它带有很多网络早期混乱时期的包袱。但是今天的生态系统已经不同了,这条老狗出人意料地适合于一个组件系统。不要这么快就把它撇开了!