CSS 实用程序类:您的可扩展样式库
潜在的缺点是什么?
作者:Russell Bishop✏️
使用 CSS 实用类构建代码,可以极大地提升效率和组织性。它允许你定义一个property: value
包含所有样式的库,并通过一个目录进行管理。
在本文中,我们将介绍:
- 什么是 CSS 实用程序类以及为什么要使用它们
- 流行框架中的快速演示
- 如何构建你自己的超级实用程序库
什么是实用程序类?
实用程序类是自描述的、单一用途的 CSS 类:
.flex {
display: flex;
}
开发人员使用这些功能类来构建而无需编写额外的 CSS,因为如果样式在库中,您可以反复使用它……
<aside class="flex flex-column bg-black">
<div class="flex align-center justify-center">
<img src="#" />
</div>
<div class="flex flex-column">
<h1>Jamie Thrift</h1>
<p class="flex align-center">
<svg>…</svg>
Head of HR
</p>
</div>
</aside>
这些类准确地告诉我们它们的作用,因此开发人员可以直观地看到这些元素的布局方式,而不需要扫描底层 CSS。
实用程序类是如何组合在一起的?
实用程序类是作为框架的一部分为您生成的。其中一些流行的工具和框架提供了许多开箱即用的样式,因此您可以放心使用class="padding-10"
,因为样式已经存在。其他一些工具和框架则提供工具,让您仅定义项目所需的实用程序。
通过在一个地方配置样式库,您可以避免代码库中充斥着异常值,并且避免忽略哪些是新的 CSS 以及哪些已经在其他地方编写。
使用实用程序可以极大地增强项目的组织性,并提高开发人员和最终用户的可预测性和一致性。
…就像一个设计系统!
这种思维模式在 UI 设计领域也得到了体现,如今,定义、复用和维护一个中央样式库的做法正在兴起。这些样式通常位于设计系统的“样式指南”部分。
风格指南主要涵盖色彩、排版、间距、网格和图像等基础内容。这些底层基础是设计师为创作一致作品而遵循的一套规则。
实用优先的 CSS 框架
多年来,许多框架、库和工具在这个领域逐渐流行起来。以下是一些知名选项的简要介绍,以及一些采用“实用优先”方法的开发者的案例研究。
2013
低音
- 标语: “闪电般快速的模块化 CSS,无副作用”
- GitHub: basscss/basscss
超光速粒子
- 标语: “无需编写 CSS 即可快速构建和设计新的 UI”
- GitHub: tachyons-css/tachyons
2014
胡须
- 标语: “为有更重要事情要做的人提供的 CSS 框架”
- 延伸阅读: Sascha Wolff 撰写的《实用至上的 CSS:几乎适用于所有设计的超快前端开发》
- GitHub: monarkee/beard
2015
炮塔
- 标语: “旨在使响应式、可访问性和可扩展的 CSS 开发变得简单且可预测”
- 延伸阅读: Cole Peters 撰写的“构建和交付函数式 CSS ”
- GitHub: turretcss/turretcss
2017
Tailwind CSS
- 标语: “一个实用优先的 CSS 框架,用于快速构建自定义设计。”
- 进一步查看: Andre Madarang 的“ Tailwind CSS – 重建 Twitter ”
- GitHub: tailwindcss/tailwindcss
2018
stemCSS
- 标语: “构建主干——不要重复自己,不要颠覆自己”
- 延伸阅读: Debra Ohayon 撰写的“转向实用优先的 CSS 框架”
- GitHub: wearelighthouse/stemCSS
Tailwind CSS | 超光速粒子 | iotaCSS | |
最佳功能 | 出色的文档![]() ![]() |
预组合组件,快速开发 | 组件、对象和实用程序的直观组织 |
最差的特点 | Java脚本配置 | 难以定制 | 每组实用程序都是一个包 |
类语法 | .text-2xl 或者… .propertyalias-valuealias |
.f-headline 或者… .propertyalias-valuealias |
.o-type-small 或者... .namespace-propertyalias-valuealias |
响应式语法 | .md:flex-shrink-0 或者… .breakpointalias:propertyalias-valuealias |
.w-100-m 或者… .propertyalias-valuealias-breakpointalias |
u-text-right@sm 或者… .namespace-propertyalias-valuealias-breakpointalias |
悬停/焦点语法 | .focus:outline-none 或者… .state:propertyalias-valuealias |
.hover-orange 或者... 有限的组合.state-propertyalias-valuealias |
.u-color-orange@hover 或者… .namespace-propertyalias-valuealias-state |
内置 | Java脚本(配置) CSS | CSS | SCSS |
将实用类付诸实践
让我们构建一个简单的组件来了解这些工具类是如何工作的。这里,我们将复制你可能在Stack Overflow上见过的 author 组件。
我将使用Tailwind CSS中的语法作为此示例。
代码笔
<div class="p-3">
<p class="text-gray-600">answered <span title="2017-08-07 08:46:14Z">Aug 7 '17 at 8:46</span></p>
<figure class="flex items-center mt-3">
<img class="w-10 h-10" src="https://assets-us-01.kc-usercontent.com/525f1d2d-c241-00a9-f169-8b2061aeb3da/e53a3a2b-f539-4ab9-a3ec-7cf7a2b6a664/aleksandra_zamojc.png" alt="Photo of Konrad Albrecht" />
<figcaption class="ml-2">
<p>
<a class="text-blue-600" href="/user">Konrad Albrecht</a>
</p>
<p class="flex items-center mt-1">
<span class="font-bold text-gray-800" title="reputation score">138</span>
<span class="rounded-full w-2 h-2 bg-orange-400 ml-2"></span>
<span class="ml-1 text-gray-600" title="badge count">10</span>
</p>
</figcaption>
</figure>
</div>
没什么可说的
“一旦你给它起了名字,你就会对它产生依恋。”
请注意,这些标记都不需要我们命名任何东西——因为命名本身就很难。我们不必遵循类似.user
或 的结构.author
……这使我们能够更快地构建组件,并减少维护工作。如果我们稍后添加另一个包含作者的组件,我们也不必重新考虑命名方案。
可预测性
说实话,在开始构建该组件之前,我只是快速浏览了Tailwind CSS 文档,并且只需要查找两个我无法猜到的类(.rounded-full
和items-center
)。
这意味着构建组件可以在一个文件中进行,而不必在两个文件中切换。
潜在的缺点是什么?
正如您在开发中采用的任何方法一样,总会有一些妥协。让我们来探讨一下其中的一些复杂之处。
长类组合
一行无休止的、换行的文本很难辨认。在编写普通 CSS 时,我们有一些技巧可以帮助我们扫描一组属性:
- 每行一条规则
- 缩进
- 规则维护属性排序顺序
- 断点可以与预处理/后处理嵌套
当我们将那一长串样式重新放到标记中时,我们倾向于放弃上述做法,因为这会使 HTML 代码变得非常冗长。这会导致类名字符串变得非常长,难以理解,甚至难以进行 lint 检查。
尝试在这里找到您想要调整的重量属性:
<a href="https://myurl.com/" class="bgcolor-blue bg-color-darkblue@hover bg-color-darkblue@focus radius-tlbr-3 radius-trbl-12 font-europa color-light-grey color-white@hover color-white@focus weight-medium shadow-black-thin padv-3 padh-4 whitespace-nowrap size-6rem line-thin pos-relative top-3@hover top-3@focus">Incredible pace in this place</a>
值得一提的是,这个例子甚至没有任何响应样式——如果它在一个或两个断点处改变属性,那么长度可能会加倍。
文件大小管理
如果我们全力以赴,有些框架会为每个断点和每个状态的每个属性的每个可能值生成一个类。这会造成巨大的 CSS 垃圾,而你可能只使用了其中的 1%。框架知道这一点,并且有自己的处理方法。
Tailwind CSS(最小化后为 350kB)有一个关于控制文件大小的部分,建议设置PurgeCSS。
Purge 会扫描你的项目文件,查找你使用过的类,并从编译后的 CSS 文件中删除剩余的类。这意味着构建过程中会多出一个步骤,如果你的代码库相当庞大,甚至可能导致脚本运行延迟。此外,你的编程语言可能会掩盖类名(class="bgcolor-${foo}"
)的准确性,这需要你多加注意。
复杂选择器
CSS 的许多实用快捷键都包含在其强大的选择器中。例如,假设有一个表单部分,当用户不与其交互时,它会逐渐消失:
.fieldset:not(:focus-within):not(:hover) {
opacity: .5;
}
或者仅使用伪元素:
.title:after {
position: absolute;
bottom: -2px;
left: 0;
right: 0;
border-bottom: 2px solid orange;
content: '';
}
或者甚至像斑马条纹表格行这样简单的东西:
.table-row:nth-child(odd) {
background-color: grey;
}
要使用(相当常见的!)此类样式,您可能最终需要在实用程序框架旁边维护一些自定义 CSS,或者使用iotaCSS 中的组件。
失去级联
不仅如此,使用级联进行定位还可以在布局元素时节省大量时间 - 就像部署每个人都喜欢的猫头鹰选择器一样:
.margin-top-siblings-2 > * + * {
margin-top: 2rem;
}
我见过的任何框架都没有默认提供这些节省时间的样式,这意味着您最终需要在标记中添加更多的类来进行补偿。
构建您自己的实用程序框架
在提供了有关实用程序类的两个方面的故事之后,我们可以肯定地看到现有框架的提供方式还有改进的空间。
为了在 SCSS 中构建我们自己的实用程序框架,我们将遵循以下原则:
- 只生成你需要的类
- 减少编译 CSS 中的浪费
- 造型类型的自然分离
为了实现这一目标,我们将利用:
为什么要使用命名空间?
为您的类类型使用一个小的命名空间,您就可以通过一种结构化的方式根据类的类型来指示类的功能。我们之前提到的大多数框架都不会这样做,因为它们只是为了满足实用程序而编写的。
我们将在以下所有示例中使用命名空间。
定义样式的占位符
占位符是 SCSS 中用于定义样式的“幽灵”定义。它们的独特之处在于,除非你在某处引用它们,否则它们不会编译通过。就好像你定义了一个$variable
但从未使用过它一样。
// Define the placeholder…
%u-display-flex {
display: flex;
}
// This will not compile, until…
.u-flex {
@extend %u-display-flex;
}
// Now we have our utility class ready to use…
.u-flex {
display: flex;
}
因为占位符只有在我们需要时才会出现,所以我们还可以继续为每个断点和样式的状态变化创建一个占位符。
@extend
应用我们的占位符
定义好占位符后,我们现在可以决定如何使用它们了。通过使用@extend
(而不是Tailwind CSS 的@apply
),我们还可以减少 CSS 中的重复次数。
举个例子,假设我们的许多组件都使用特定的边框。如果我们的 CSS 代码如下,就会显得过大:
.c-sidebar {
border: 1px solid #222;
}
.c-card {
border: 1px solid #222;
}
.c-header {
border: 1px solid #222;
}
当我们持续使用低特异性类时,我们可以将这些样式组合在一起并消除最终结果:
.c-sidebar {
@extend %border-grey;
}
.c-card {
@extend %border-grey;
}
.c-header {
@extend %border-grey;
}
// Which compiles to…
.c-sidebar,
.c-card,
.c-header {
border: 1px solid #222;
}
我们的组件可以@extend
使用库中现有的任何样式,通过引用占位符,我们知道不会创建浪费资源的新样式定义。让我们重写一下之前那个凌乱的组件示例:
.c-featured-link {
@extend
%u-pos-relative,
%u-padv-3,
%u-padh-4,
%u-bgcolor-blue,
%u-shadow-black-thin,
%u-radius-tlbr-3,
%u-radius-trbl-12,
%u-weight-medium,
%u-size-6rem,
%u-line-thin,
%u-font-europa,
%u-color-light-grey,
%u-whitespace-nowrap;
}
嵌套在组件内
我们还可以通过嵌套断点和状态来大大提高这些组件的可读性。
.c-featured-link {
// …
&:hover,
&:focus {
@extend
%u-top-3,
%u-bg-color-darkblue,
%u-color-white;
}
}
构建我们需要的工具
最后,我将向您介绍在 SCSS 中实现上述功能所需的工具。我将首先描述它们的工作原理,以便您可以根据自己的喜好重新构建它们,然后提供一个已经包含完整工具集的框架链接。
制作占位符
@mixin make-placeholder($utility-name, $properties) {
// No breakpoint
%#{$utility-name} {
@each $property, $value in $properties {
#{$property}: $value;
}
}
// Every breakpoint
@each $breakpoint-key, $breakpoint-value in $global-breakpoints {
%#{$utility-name + $global-breakpoint-separator + $breakpoint-key} {
@include breakpoint($breakpoint-key) {
@each $property, $value in $properties {
#{$property}: $value;
}
}
}
}
}
上面的 mixin 首先为$properties
参数中发送的每个属性创建一个占位符。然后,我们循环遍历 map 中其他地方定义的所有断点,$global-breakpoints
以确保我们也拥有断点占位符。
这将为我们提供如下占位符:
@media (min-width: 600px) {
%u-bgcolor-primary\@medium {
background-color: blue;
}
}
我们转义 @ 符号(我们的断点和状态标识符),因为它是一个无效字符;欢迎您选择自己的分隔符来替换它。
从占位符创建类
@mixin make-class-from-placeholder($utility-name, $properties) {
.#{$utility-name} {
@extend %#{$utility-name};
}
}
由于我们需要(在下一个 mixin 中),直接从我们的 new 创建一个实用程序类%placeholder
。
制作实用程序
@mixin make-utility($args) {
$class: map-use(
$args,
class,
false
);
$args: map-remove($args, class);
// If 'alias' key exists in $args, use that for the placeholder-name,
// otherwise use the key and value of the first property in $args
$utility-name: map-use(
$args,
alias,
first(map-keys($args)) + '-' + first(map-values($args))
);
$utility-name: 'u-' + $utility-name;
$properties: map-remove($args, alias);
@include make-placeholder($utility-name, $properties);
@if ($class) {
@include make-class-from-placeholder($utility-name, $properties);
}
}
最后一步是支持别名,这使您可以决定是否喜欢在类名中逐字母命名(justify-content-space-between
)或者是否喜欢使用小快捷方式(jc-sb
)。
这完全取决于您 - 较长的名称意味着任何开发人员都可以加入并立即了解模式,但较短的名称更容易扫描并且输入速度更快。
确定别名(或留空)后,我们会检查您是否要使用 Make Class 并完成占位符。
用循环和控制指令把它们组合在一起
为了充分利用我们的占位符构建,您需要列出要添加到库中的属性和值。
这里有一些有用的循环可以帮助您:
@each $wrap in (nowrap, wrap, wrap-reverse) {
@include make-utility((
alias: 'fw-' + $wrap,
flex-wrap: $wrap
));
}
@each $property in (translateX, translateY) {
@each $value in (-100, -50, 0, 50, 100) {
@include make-utility((
alias: $property + '-' + $value + $global-unit-percent,
transform: $property + '(' + $value + '%)'
));
}
}
@each $property in (margin-top, margin-right, margin-bottom, margin-left) {
@for $value from 1 through 30 {
@include make-utility((
$property: #{$value + 'rem'};
));
}
}
如果您想快速启动自己的框架,可以获取最新版本的stemCSS。
概括
近期的工具和框架将实用类的概念推向了主流,并且它们也从其出色的文档中获益良多(感谢Tailwind CSS)。显然,实用类已经彻底改变了开发者使用低优先级 CSS 类作为一致性样式方法的格局。
样式表的未来将会给我们带来什么?
编者注:觉得这篇文章有什么问题?您可以在这里找到正确版本。
插件:LogRocket,一个用于 Web 应用的 DVR
LogRocket是一款前端日志工具,可让您重播问题,就像它们发生在您自己的浏览器中一样。您无需猜测错误发生的原因,也无需要求用户提供屏幕截图和日志转储,LogRocket 允许您重播会话以快速了解问题所在。它可与任何应用程序完美兼容,无论使用哪种框架,并且提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的更多上下文。
除了记录 Redux 操作和状态之外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头 + 正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,以记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能重现像素完美的视频。
免费试用。
CSS 实用程序类:您的可扩展样式库首先出现在LogRocket 博客上。
鏂囩珷鏉ユ簮锛�https://dev.to/bnevilleoneill/css-utility-classes-your-library-of-extendable-styles-4h3c