使用内联CSS自定义属性和calc()的高效无限效用帮助器

742 阅读6分钟

我最近写了一个非常基本的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_smsm_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中)并没有在 "计算 "样式标签中报告使用这种技术的样式。

另外值得一提的是,我使用了好的旧的paddingmargin 属性与-top,-right,-bottom, 和-left ,但你可以使用相当的逻辑属性,如 padding-blockpadding-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。