我最近写了一个非常基本的Sass循环,输出几个padding和margin实用类。没有什么花哨的,真的,只是一个包含11个间距值的Sass地图,通过循环来创建每一侧的padding和margin的类。正如我们将看到的,这很有效,但它最终导致了相当大的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 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)。它仍然压缩得很好,但这有点可笑。
我知道可以在CSS中使用 [attr() 函数引用data-* 属性,所以我想知道是否有可能使用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变量。Aha!
因此,在CSS中可以做一些类似于<div style="--p: 4;"> 。
: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字节,gzipped时为284字节。
如果你想把这个扩展到断点,不幸的是,你不能在CSS变量名中加入"@"字符(尽管表情符号和其他UTF-8字符被奇怪地允许)。所以你也许可以设置一些变量名,比如p_sm 或sm_p 。你必须添加一些额外的CSS变量和一些媒体查询来处理所有这些,但它不会像用Sass for-loop创建的传统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)) !important;
}
[style*='--pt#{$bp}:'] {
padding-top: calc(0.25rem * var(--pt)) !important;
}
[style*='--pr#{$bp}:'] {
padding-right: calc(0.25rem * var(--pr)) !important;
}
[style*='--pb#{$bp}:'] {
padding-bottom: calc(0.25rem * var(--pb)) !important;
}
[style*='--pl#{$bp}:'] {
padding-left: calc(0.25rem * var(--pl)) !important;
}
[style*='--px#{$bp}:'] {
padding-right: calc(0.25rem * var(--px)) !important;
padding-left: calc(0.25rem * var(--px)) !important;
}
[style*='--py#{$bp}:'] {
padding-top: calc(0.25rem * var(--py)) !important;
padding-bottom: calc(0.25rem * var(--py)) !important;
}
[style*='--m#{$bp}:'] {
margin: calc(0.25rem * var(--m)) !important;
}
[style*='--mt#{$bp}:'] {
margin-top: calc(0.25rem * var(--mt)) !important;
}
[style*='--mr#{$bp}:'] {
margin-right: calc(0.25rem * var(--mr)) !important;
}
[style*='--mb#{$bp}:'] {
margin-bottom: calc(0.25rem * var(--mb)) !important;
}
[style*='--ml#{$bp}:'] {
margin-left: calc(0.25rem * var(--ml)) !important;
}
[style*='--mx#{$bp}:'] {
margin-right: calc(0.25rem * var(--mx)) !important;
margin-left: calc(0.25rem * var(--mx)) !important;
}
[style*='--my#{$bp}:'] {
margin-top: calc(0.25rem * var(--my)) !important;
margin-bottom: calc(0.25rem * var(--my)) !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() ,但分享了一些你可以在内联样式中使用变量赋值的神奇之处。
The postEfficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc()appeared first onCSS-Tricks.你可以通过成为MVP支持者来支持CSS-Tricks。