我最近编写了一个非常基本的 Sass 循环,它输出多个填充和边距实用程序类。 没什么特别的,只是一个包含 11 个间距值的 Sass 地图,循环以在每侧创建填充和边距类。 正如我们将在下面看到的,这有效,但最终会产生大量 CSS。 我们将对其进行重构以使用 CSS 自定义属性 并使系统更加精简。
这是原始 Sass 实现
$space-stops: (
'0': 0,
'1': 0.25rem,
'2': 0.5rem,
'3': 0.75rem,
'4': 1rem,
'5': 1.25rem,
'6': 1.5rem,
'7': 1.75rem,
'8': 2rem,
'9': 2.25rem,
'10': 2.5rem,
);
@each $key, $val in $space-stops {
.p-#{$key} {
padding: #{$val} !important;
}
.pt-#{$key} {
padding-top: #{$val} !important;
}
.pr-#{$key} {
padding-right: #{$val} !important;
}
.pb-#{$key} {
padding-bottom: #{$val} !important;
}
.pl-#{$key} {
padding-left: #{$val} !important;
}
.px-#{$key} {
padding-right: #{$val} !important;
padding-left: #{$val} !important;
}
.py-#{$key} {
padding-top: #{$val} !important;
padding-bottom: #{$val} !important;
}
.m-#{$key} {
margin: #{$val} !important;
}
.mt-#{$key} {
margin-top: #{$val} !important;
}
.mr-#{$key} {
margin-right: #{$val} !important;
}
.mb-#{$key} {
margin-bottom: #{$val} !important;
}
.ml-#{$key} {
margin-left: #{$val} !important;
}
.mx-#{$key} {
margin-right: #{$val} !important;
margin-left: #{$val} !important;
}
.my-#{$key} {
margin-top: #{$val} !important;
margin-bottom: #{$val} !important;
}
}
这非常有效。 它输出我们需要的全部实用程序类。 但是,它也会很快膨胀。 在我的情况下,它们大约有 8.6kb 未压缩,压缩后不到 1kb。(Brotli 为 542 字节,gzip 为 925 字节。)
由于它们极度重复,因此压缩效果很好,但我仍然无法摆脱这种感觉,即所有这些类都是多余的。 另外,我还没有进行任何小/中/大断点,而这些断点对于这类辅助类来说是相当典型的。
以下是一个经过设计示例,展示了添加小/中/大类后的响应式版本。 我们将重用之前定义的 $space-stops
地图并将重复的代码放入 mixin 中
@mixin finite-spacing-utils($bp: '') {
@each $key, $val in $space-stops {
.p-#{$key}#{$bp} {
padding: #{$val} !important;
}
.pt-#{$key}#{$bp} {
padding-top: #{$val} !important;
}
.pr-#{$key}#{$bp} {
padding-right: #{$val} !important;
}
.pb-#{$key}#{$bp} {
padding-bottom: #{$val} !important;
}
.pl-#{$key}#{$bp} {
padding-left: #{$val} !important;
}
.px-#{$key}#{$bp} {
padding-right: #{$val} !important;
padding-left: #{$val} !important;
}
.py-#{$key}#{$bp} {
padding-top: #{$val} !important;
padding-bottom: #{$val} !important;
}
.m-#{$key}#{$bp} {
margin: #{$val} !important;
}
.mt-#{$key}#{$bp} {
margin-top: #{$val} !important;
}
.mr-#{$key}#{$bp} {
margin-right: #{$val} !important;
}
.mb-#{$key}#{$bp} {
margin-bottom: #{$val} !important;
}
.ml-#{$key}#{$bp} {
margin-left: #{$val} !important;
}
.mx-#{$key}#{$bp} {
margin-right: #{$val} !important;
margin-left: #{$val} !important;
}
.my-#{$key}#{$bp} {
margin-top: #{$val} !important;
margin-bottom: #{$val} !important;
}
}
}
@include finite-spacing-utils;
@media (min-width: 544px) {
@include finite-spacing-utils($bp: '_sm');
}
@media (min-width: 768px) {
@include finite-spacing-utils($bp: '_md');
}
@media (min-width: 1024px) {
@include finite-spacing-utils($bp: '_lg');
}
这大约有 41.7kb 未压缩(Brotli 约为 1kb,gzip 约为 3kb)。 它仍然压缩得很好,但有点荒谬。
我知道可以使用 data-*
属性 从 CSS 内部引用 [attr()
函数,所以我很好奇是否可以使用 calc()
和 attr()
结合在一起,通过 data-*
属性创建动态计算的间距实用程序辅助函数 - 例如 data-m="1"
或 data-m="1@md"
- 然后在 CSS 中执行类似于 margin: calc(attr(data-m) * 0.25rem)
的操作(假设我正在使用一个以 0.25rem
间隔递增的间距刻度)。 这将非常强大。
但这个故事的结局是:不,你(目前)无法 将 attr()
与除 content
属性之外的任何属性一起使用。 真可惜。 但在搜索 attr()
和 calc()
信息时,我发现 Simon Rigét 在 Stack Overflow 上发表的这篇有趣的评论 建议将 CSS 变量直接设置在内联样式属性中。 哈哈!
因此可以执行类似于 <div style="--p: 4;">
的操作,然后在 CSS 中
:root {
--p: 0;
}
[style*='--p:'] {
padding: calc(0.25rem * var(--p)) !important;
}
在 style="--p: 4;"
示例的情况下,你将有效地得到 padding: 1rem !important;
。
… 现在你有一个可无限扩展的间距实用程序类怪物辅助函数。
以下是 CSS 中可能的样子
:root {
--p: 0;
--pt: 0;
--pr: 0;
--pb: 0;
--pl: 0;
--px: 0;
--py: 0;
--m: 0;
--mt: 0;
--mr: 0;
--mb: 0;
--ml: 0;
--mx: 0;
--my: 0;
}
[style*='--p:'] {
padding: calc(0.25rem * var(--p)) !important;
}
[style*='--pt:'] {
padding-top: calc(0.25rem * var(--pt)) !important;
}
[style*='--pr:'] {
padding-right: calc(0.25rem * var(--pr)) !important;
}
[style*='--pb:'] {
padding-bottom: calc(0.25rem * var(--pb)) !important;
}
[style*='--pl:'] {
padding-left: calc(0.25rem * var(--pl)) !important;
}
[style*='--px:'] {
padding-right: calc(0.25rem * var(--px)) !important;
padding-left: calc(0.25rem * var(--px)) !important;
}
[style*='--py:'] {
padding-top: calc(0.25rem * var(--py)) !important;
padding-bottom: calc(0.25rem * var(--py)) !important;
}
[style*='--m:'] {
margin: calc(0.25rem * var(--m)) !important;
}
[style*='--mt:'] {
margin-top: calc(0.25rem * var(--mt)) !important;
}
[style*='--mr:'] {
margin-right: calc(0.25rem * var(--mr)) !important;
}
[style*='--mb:'] {
margin-bottom: calc(0.25rem * var(--mb)) !important;
}
[style*='--ml:'] {
margin-left: calc(0.25rem * var(--ml)) !important;
}
[style*='--mx:'] {
margin-right: calc(0.25rem * var(--mx)) !important;
margin-left: calc(0.25rem * var(--mx)) !important;
}
[style*='--my:'] {
margin-top: calc(0.25rem * var(--my)) !important;
margin-bottom: calc(0.25rem * var(--my)) !important;
}
这非常像上面的第一个 Sass 循环,但没有 11 次循环 - 而且它是无限的。 它大约有 1.4kb 未压缩,Brotli 为 226 字节,gzip 为 284 字节。
如果你想将它扩展到断点,不幸的是你不能将“@”字符放在 CSS 变量名中(尽管奇怪的是允许表情符号和其他 UTF-8 字符)。 因此,你可能可以设置类似于 p_sm
或 sm_p
的变量名。 你需要添加一些额外的 CSS 变量和一些媒体查询来处理这一切,但它不会像使用 Sass for 循环创建的传统 CSS 类名那样呈指数级增长。
以下是等效的响应式版本。 我们将再次使用 Sass mixin 来减少重复
:root {
--p: 0;
--pt: 0;
--pr: 0;
--pb: 0;
--pl: 0;
--px: 0;
--py: 0;
--m: 0;
--mt: 0;
--mr: 0;
--mb: 0;
--ml: 0;
--mx: 0;
--my: 0;
}
@mixin infinite-spacing-utils($bp: '') {
[style*='--p#{$bp}:'] {
padding: calc(0.25rem * var(--p#{$bp})) !important;
}
[style*='--pt#{$bp}:'] {
padding-top: calc(0.25rem * var(--pt#{$bp})) !important;
}
[style*='--pr#{$bp}:'] {
padding-right: calc(0.25rem * var(--pr#{$bp})) !important;
}
[style*='--pb#{$bp}:'] {
padding-bottom: calc(0.25rem * var(--pb#{$bp})) !important;
}
[style*='--pl#{$bp}:'] {
padding-left: calc(0.25rem * var(--pl#{$bp})) !important;
}
[style*='--px#{$bp}:'] {
padding-right: calc(0.25rem * var(--px#{$bp})) !important;
padding-left: calc(0.25rem * var(--px)#{$bp}) !important;
}
[style*='--py#{$bp}:'] {
padding-top: calc(0.25rem * var(--py#{$bp})) !important;
padding-bottom: calc(0.25rem * var(--py#{$bp})) !important;
}
[style*='--m#{$bp}:'] {
margin: calc(0.25rem * var(--m#{$bp})) !important;
}
[style*='--mt#{$bp}:'] {
margin-top: calc(0.25rem * var(--mt#{$bp})) !important;
}
[style*='--mr#{$bp}:'] {
margin-right: calc(0.25rem * var(--mr#{$bp})) !important;
}
[style*='--mb#{$bp}:'] {
margin-bottom: calc(0.25rem * var(--mb#{$bp})) !important;
}
[style*='--ml#{$bp}:'] {
margin-left: calc(0.25rem * var(--ml#{$bp})) !important;
}
[style*='--mx#{$bp}:'] {
margin-right: calc(0.25rem * var(--mx#{$bp})) !important;
margin-left: calc(0.25rem * var(--mx#{$bp})) !important;
}
[style*='--my#{$bp}:'] {
margin-top: calc(0.25rem * var(--my#{$bp})) !important;
margin-bottom: calc(0.25rem * var(--my#{$bp})) !important;
}
}
@include infinite-spacing-utils;
@media (min-width: 544px) {
@include infinite-spacing-utils($bp: '_sm');
}
@media (min-width: 768px) {
@include infinite-spacing-utils($bp: '_md');
}
@media (min-width: 1024px) {
@include infinite-spacing-utils($bp: '_lg');
}
这大约有 6.1kb 未压缩,Brotli 为 428 字节,gzip 为 563 字节。
我认为编写像 <div style="--px:2; --my:4;">
这样的 HTML 代码是否赏心悦目或具有良好的开发人员人体工程学… 不,不是特别。 但是这种方法在您(由于某种原因)需要极少量的 CSS 或根本不需要外部 CSS 文件的情况下可行吗? 是的,我当然认为可行。
值得在此指出的是,在内联样式中分配的 CSS 变量不会泄漏。 它们仅作用于当前元素,不会全局更改变量的值。 感谢上帝! 我到目前为止发现的一个奇怪之处是,DevTools(至少在 Chrome、Firefox 和 Safari 中)不会在“计算”样式选项卡中报告使用此技术的样式。
还需要提及的是,我使用了传统的 padding
和 margin
属性,以及 -top
、-right
、-bottom
和 -left
,但你可以使用等效的 逻辑属性,如 padding-block
和 padding-inline
。 甚至可以通过选择性地混合和匹配逻辑属性和传统属性来减少一些字节。 我设法将 Brotli 压缩到 400 字节,gzip 压缩到 521 字节。
其他用例
这似乎最适合那些处于(线性)增量尺度上的东西(这就是为什么填充和边距似乎是一个很好的用例),但我可以看到这可能适用于网格系统中的宽度和高度(列号和/或宽度)。 **也许** 用于排版刻度(但可能不适合)。
我非常关注文件大小,但可能还有其他一些我没想到的用途。 也许**你**不会以这种方式编写代码,但一个关键的 CSS 工具可能会重构代码以使用这种方法。
深入研究
当我深入研究时,我发现 Ahmad Shadeed 在 2019 年的博客文章 中讨论了将 calc()
与内联样式中的 CSS 变量分配相结合,特别是用于头像大小。 Miriam Suzanne 在 2019 年发表在 Smashing Magazine 上的文章 没有使用 calc()
,但分享了一些使用内联样式中的变量分配可以做到的惊人事情。
这篇文章真有意思,谢谢! 我最近在我的项目中一直在使用 Tailwind,虽然我喜欢它让我能够避免重复的 CSS 声明的方式,但我经常希望它能够操作我的源文档,使其更加高效! 如果它有一些 Babel 插件可以将 CSS 实用程序样式转换为像这样内联变量样式,以便超级减少文档大小,那将非常棒… 罢了,一个女孩可以做梦。
是的! 我喜欢这个想法。 正是我希望其他人想到的!
以下是一个 CodePen,供任何想要玩转样式实用程序的人使用 https://codepen.io/andyford/pen/MWmzxMv
我不使用内联样式,但这项技术似乎很有趣!
作为一名热衷于实用程序类的粉丝,我认为这很棒。 这是一种非常新颖的方法,并且很好地利用了“包含”匹配选择器。
我爱实用程序类! 我绝对认为这是一个绝妙的发现! 我可以想象它将用于生成的标记。
把它写成纯香草 HTML 确实让我有点害怕。 CodePen 链接让我在内心哭笑不得。 也许我老了。
如果问题只是文件大小,我们可以始终使用 PurgeCSS 来删除未使用的 CSS,对吧?
我觉得我的“内联 CSS 变量很酷”观点被我对 Sass 和文件大小的关注所掩盖。 以下是一个非常基本的仅使用 CSS 的行夹断示例 https://codepen.io/andyford/pen/KKrLEXX