引进AM-CSS属性模块

本文由大漠根据Glen Maddern的《Introducing AM – Attribute Modules for CSS》一文所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://glenmaddern.com/articles/introducing-am-css

——作者:Glen Maddern

——译者:大漠

在几个月之前,我阅读了Harry Roberts写的《Grouping related classes in your markup》一文。他介绍了一个很有趣的概念——用来处理CSS类名。在这篇文章中介绍了使用[]来使用相关的属性,快速选择需要的目标元素。介绍的示例中,讨论了使用类名还是属性声明更易于一目了然选择元素。

<div class="[ foo  foo--bar ]  [ baz  baz--foo ]">

我必须承认,最初感觉使用这种技术十分不爽。他只是使用[]来匹配类名,而不是匹配CSS,重复使用同一个类属性,似乎只是给人提供而不是给浏览器。实际上我仍然还在思考,关键是对HTML结构和语义化的认识,在此要非常感谢Harry Roberts。

看着它,似乎跟其他人的想法非常的类似,比如Ben Everard使用/或者Stephen Nolan使用|,但总感觉他们都有点做作。我始终认为:

你有很多个类名,你要如何让这些类名更具可读性。

因为,我非常清楚,这样做有些疯狂。可读是用来描述一个HTML的全局性,但这样做有些东西已打破了基于样式上的描述。

顺便介绍一下多类

让你惊讶的是,标记中的多类让我感到困惑,而且不能像Harry那样阐述的有说服力。不能像OOCSS那样有说服力和自己的严则,以及根据自己从业多年经验创建一套日益复杂而且吸引人的东西。最近我在思考,如何做这样的一件事情,让我觉得非常的开心。

以前我通过BEM来强调重用性,每个新的块不会继承默认样式,允许组件单独开发,并且避免组件在其他网站上使用的风险。但这样做是有代价的,当你发现自己有十个不同的链接样式、12种蓝色色调和18种略有不同样式按钮等。Nicole Sullivan在去年讲OOCSS主题时说到这个问题,如何创建共性和覆盖。

对我来说,感觉就像被动使用CSS预处理器能力来处理BEM和OOCSS,我使用了下面的方法来替代:

<a class='btn large rounded'>
.btn { 
    /* 按钮样式 */ 
}
.large { 
    /* global large-type modifier */ 
}
.rounded { 
    /* global rounded-border modifier */ 
}

你可能会使用下面这样的方式:

<a class='btn btn--large btn--rounded'>
.btn { 
    /* button styles */
}
.btn--large {
    @extend %large-type;
}
.btn--rounded {
    @extend %rounded-borders;
}

最后我会在文件中使用mixins或者%placeholder,例如_typography.scss_brand.scss文件。让他们保持是碎片分离,也让他们在默认情况之下,每个组件样式是分离的。一段时间以来,我都觉得这样使用是好的。

修饰符:如何打破BEM中的M

做任何有关于CSS的类命名和维护的研究,你注定都会围绕Nicolas Gallagher写的那篇优秀文章《“About HTML semantics and front-end architecture”》。其中有一部分让我特别关注,就是修饰符使用单类还是多类。总结一下,你的HTML结构大多存在这样的两个版本:

<a class='btn--large'> <!-- 单类 -->
<a class='btn btn--large'> <!-- 多类 -->

对应的CSS样式版本:

/* 单类 */
.btn, .btn--large { 
    /* base button styles */ 
}
.btn--large { 
    /* large button styles */ 
}

/* 多类 */
.btn { 
    /* base button styles */ 
}
.btn--large { 
    /* large button styles */ 
}

这里的区别就是.btn-large样式是取决于自身还是依赖于.btn样式。单类模式给人感觉更简单,避免在结构中忘记添加.btn类名。也减少了使用的重复性,类似于Sass中的@extend一样,不想在CSS方面承受更多的负担。但它真的有一个致命的缺陷。

上下文的覆盖

比如说,除了顶部导航栏上的按钮之外的其他所有按钮都有同一种背景颜色。在多类模式下,所有按钮,大的或小的、圆的或方的等等,他们都会包括一个.btn类名。所以你可以这样写:

header > nav > .btn { background: none; }

而在单类模式下,我们不知道哪种按钮要覆写,我们不得不这样写:

header > nav {
  .btn, .btn--large, .btn--rounded { background: none; }
}

很显然,这样做很不理想——如果添加一个新类型按钮,意味着要要添加另一个类名覆盖样式。这样做将是一个死胡同,所以很多人主张回归到多类名模式(Nicholas GallagherBen Smithett)。我也看到有些人提出新的建议,例如Tommy MarshallBen Frain提出的使用CSS属性选择器前缀^=。允许你通过某个字符串开始或某个特定字符串来检测,例如:

<a class='btn--large'>
[class^='btn'] { 
    /* base button styles */ 
}
.btn--large { 
    /* large button styles */ 
}
header > nav > [class^='btn'] { 
    /* Overrides for all buttons */ 
}

这样在单类模式下容易实现上下文样式覆盖,但太脆弱了,不是一个严谨的选择器。最致命的是,如果有一个类名出现在btn-large,这样就无法匹配。另外,带没有明显的方式可以允许匹配多个类型,如:btn--large--rounded

我很欣赏这样的创造性方法,但也是一个死胡同。而且我也正在此处被卡住,直到后面的事情发现为止。

为什么要使用类?

谁能给我一个理由,类是唯一添加样式的地方?下面引用HTML的标准来做阐述:

3.2.5.7 The class attribute

The attribute, if specified, must have a value that is a set of space-separated tokens representing the various classes that the element belongs to.

There are no additional restrictions on the tokens authors can use in the class attribute, but authors are encouraged to use values that describe the nature of the content, rather than values that describe the desired presentation of the content.

所以,我们使用类来描述内容本质是很有道理,感觉就是我们要使用更有意义的类名属性。这一属性拥有一切,从BEM的模式,如primary-nav__sub-nav--current到公用的,如u-textTruncateleftclearfix,到JavaScript调用的类js-whatevs。会花大量的时间来定义这些类名,为了避免与其他类名冲突,但这些类名仍然具有可读性。

正是有这些约定,更易于管理。正如文章开头提到的Harry写的文章,但事实上,对于一个全局的命名我们是没有理由来修改他,这也是AM与众不同的地方。

在具体谈AM之前,先温习一下CSS鲜为人知的功能。

魔力选择器~=

IE7已经支持这种选择器,称为空间分离属性选择器,也是此处要介绍的CSS技巧。它将匹配由空格分隔开的任意属性值,所以下面的两行CSS是等效的:

.dat-class { /* dem styles */ };
[class~='dat-class'] { /* dem styles */ };

在同一种方式<div class='a b c'>~=选择器不在呼abc在类名中顺序,或者是否还有其他东西存在。~=选择器都能正常工作。这也是~=不是有限类属性,任何属性都可以工作。关键是,这是一种全新方式。

属性模块

属性模块或者说AM,其核心就是关于定义命名空间用来写样式。先来看一个简单的示例,就是有关于网格的,先来看类的命名方式:

<div class="row">
    <div class="column-12">Full</div>
</div>
<div class="row">
    <div class="column-4">Thirds</div>
    <div class="column-4">Thirds</div>
    <div class="column-4">Thirds</div>
</div>
.row { /* max-width, clearfixes */ }
.column-1 { /* 1/12th width, floated */ }
.column-2 { /* 1/6th width, floated */ }
.column-3 { /* 1/4th width, floated */ }
.column-4 { /* 1/3rd width, floated */ }
.column-5 { /* 5/12th width, floated */ }
/* etc */
.column-12 { /* 100% width, floated */ }

接下来看使用属性模块。主要有两个模块,行和列。到目前为止,行没有什么变化,列有变化,他有12列:

<div am-Row>
    <div am-Column="12">Full</div>
</div>
<div am-Row>
    <div am-Column="4">Thirds</div>
    <div am-Column="4">Thirds</div>
    <div am-Column="4">Thirds</div>
</div>
[am-Row] { /* max-width, clearfixes */ }
[am-Column~="1"] { /* 1/12th width, floated */ }
[am-Column~="2"] { /* 1/6th width, floated */ }
[am-Column~="3"] { /* 1/4th width, floated */ }
[am-Column~="4"] { /* 1/3rd width, floated */ }
[am-Column~="5"] { /* 5/12th width, floated */ }
/* etc */
[am-Column~="12"] { /* 100% width, floated */ }

你会注意到第一件事情就是有am-前缀。这也是AM核心部分,确保属性模块不会与现有属性冲突。你可以使用你自己喜欢的任何前缀名,我常使用的是ui-css-或者其他前缀,但这些示例中使用的是am-前缀。HTML的有效性对你或你的项目来说是非常重要,就类似于使用data-前缀开头定义的属性类似。

你可能会注意到的第二件事情就是类似于1412这样的值,使用类名变得极为麻烦——造成冲突的机会很多。但定义了我们自己的命名空间,实际上将空间变得很小,用于工作中不会造成冲突。为了更好的工作,可以自由选择最简明而且有意义的标记。

灵活的属性值

到目前为止,差别相当的小。因为每个模块定义了自己的命名空间,所以可以让值略有不同:

<div am-Row>
    <div am-Column>Full</div>
</div>
<div am-Row>
    <div am-Column="1/3">Thirds</div>
    <div am-Column="1/3">Thirds</div>
    <div am-Column="1/3">Thirds</div>
</div>
[am-Row] { /* max-width, clearfixes */ }
[am-Column] { /* 100% width, floated */ }
[am-Column~="1/12"] { /* 1/12th width */ }
[am-Column~="1/6"] { /* 1/6th width */ }
[am-Column~="1/4"] { /* 1/4th width */ }
[am-Column~="1/3"] { /* 1/3rd width */ }
[am-Column~="5/12"] { /* 5/12ths width */ }
/* etc */

可以使用类似于宽度的1/3来命名,使命名空间变得更有意义,而其对应的就是12列中的4列。同时定义了所有列为默认样式,也就是没有值的属性列视为宽度是100%。为了更好的完成布局,将其设置左浮动。

根据属性和值定义样式

这是这种方法的主要优点之一。根据存在的属性定义基本样式,比如am-Button。然后每个特定的属性值都可以使用这个基本样式。

在上面的网格示例中,标记中的am-Column="1/3"匹配am-Columnam-Column~="1/3",所以结果就是基本样式加上其他的自定义样式。这种方式不需要使用多类名或者Sass的@extend就能获取所有列。

无类名的BEM修饰符

回到BEM中修饰符使用单类名还是多类名讨论中,AM给了一个全新的参考方式,就是不需要任何类名。对于上面的示例,结构做如下修改:

<a am-Button>Normal button</a>
<a am-Button='large'>Large button</a>
<a am-Button='rounded'>Rounded button</a>
<a am-Button='large rounded'>Large rounded button</a>
[am-Button] { /* base button styles */ }
[am-Button~="large"] { /* large button styles */ }
[am-Button~="rounded"] { /* round button styles */ }

通过创建一个新的属性模块am-Button,可以将所有按钮的基本样式分离出来,如大按钮或带圆角的按钮。不仅如此,还可以随意的组合,如am-Button="large rounded",而且还可以针对上下文进行样式的覆写:

header > nav > [am-Button] { background: none; }

按钮选择使用什么变量并不重要,他可以有很多的变化,也可以是自定义,都可以匹配选择器am-Button,所以知道重写将是有效的。

关于AMCSS项目

我、Ben SchwarzBen Smithett开始在为AM制定官方规范。如果您想要阅读更多有关于这方面技术的扩展如块、元素、断点等等,你都可以在上面阅读,如果你发现有任何问题,可以给我们提Bug

扩展阅读

AMCSS虽然是一个新东西,但在Sass方面也有相关的mixinsfunctions。如下所示:

Sass中AM模块函数

@function am($module, $trait: false) {
  @if $trait == false  {
    @return '[am-' + $module + ']';
  } @else {
    @return '[am-' + $module + '~="' + $trait + '"]';
  }
}

//use
#{am('module')} {
  color: red;
}

#{am('module', 'blue')} {
 color: blue;
}

#{am('module', 'large')} {
 font-size: 2em;
}
//Output
[am-module] {
  color: red;
}

[am-module~="blue"] {
  color: blue;
}

[am-module~="large"] {
  font-size: 2em;
}

Sass中AM的混合宏

@mixin am($module, $trait: null) {
  @if $trait != null {
    [am-#{$module}~="#{$trait}"]{
      @content;
    }
  }
  @else {
    [am-#{$module}]{
      @content;
    }
  }
}
//use
@include am(module) {
  color: red;
}

@include am(module, blue) {
  color: blue;
}

@include am(module, large) {
  font-size: 2em;
}
//output
[am-module] {
  color: red;
}

[am-module~="blue"] {
  color: blue;
}

[am-module~="large"] {
  font-size: 2em;
}

译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!

原文链接:https://www.w3cplus.com/css/introducing-am-css.html

发表评论

登录后才能评论