モジュラーデザインが大好きです。私は長年、Web サイトをページではなくコンポーネントに分割し、それらのコンポーネントをインターフェースに動的にマージすることに熱中していました。このアプローチは柔軟で効率的であり、保守が簡単です。
でも、自分のデザインが無関係なもので構成されているように見えることは望ましくありません。私が作っているのはインターフェースであり、シュールな写真ではありません。
幸いなことに、この問題を解決するために特別に設計された CSS と呼ばれるテクノロジーがすでに存在します。 CSS を使用すると、HTML コンポーネント間のどこにでもスタイルを渡すことができ、最小限の労力で一貫したデザインを確保できます。これは主に 2 つの CSS 機能のおかげです:
継承
カスケード (CSS の C、カスケード)
これらの機能により、DRY かつ効率的な方法で Web ドキュメントにコンテンツを追加できますが、CSS が存在する理由であるスタイル、明らかに支持を失っています。 BEM や Atomic CSS など、プログラムによって CSS モジュールをカプセル化する一部の CSS 手法では、多くの場合、これらの機能を回避または抑制するために最善を尽くしています。これにより、開発者は CSS を制御する機会が増えますが、これは頻繁な介入に基づくアドホックな制御にすぎません。
ここでは、モジュラーインターフェイス設計に関して、継承、カスケード、スコープを再検討します。私がお伝えしたいのは、これらの機能を使用して CSS コードをより簡潔にし、適応性を高め、ページのスケーラビリティを向上させる方法です。
font-family
JavaScript と同様に、ローカル スコープは親スコープとグローバル スコープにアクセスできますが、CSS ではローカル スコープが継承に役立ちます。
たとえば、ルート (グローバル)
html
font-family
attribute を定義すると、このルールがドキュメント内のすべての祖先要素に確実に適用されます (一部の例外を除き、これについては後述します) このセクションで説明します)。
html {
font-family: sans-ser
if;}
/*
このルールは必要ありません ↷
p {
font-family: sans-serif;
}
*/
と同じようにJavaScript と同様に、ローカル スコープで特定のルールを定義すると、それらはグローバルまたは祖先スコープでは無効になり、独自の子スコープでのみ有効になります (上記のコードの
p
1.5
line-height
html
p
a
line-height
html {
font-family: sans-serif;
}
p {
line-height: 1.5;
}
/*
このルールは必要ありません ↷
p a {
line-height: 1.5;
}
継承の最大の利点は、非常に少量のコードを使用して、一貫したビジュアル デザインの基盤を確立できることです。これらのスタイルは、まだ作成していない HTML にも適用されます。私たちは将来性のあるコードについて話しているのです。
代替方法
もちろん、パブリック スタイルを提供する別の方法もあります。たとえば、
.sans-serif
.sans-serif {
font-family: sans-serif;
}
... を作成して、このスタイルを持たせたい要素に適用できます。
Lorem ipsum.
sans-serif
りー
と
style="font-family: sans-serif"
的用法差不多 – 除了前者意味着要同时在样式表和 HTML 当中添加代码。使用继承,我们就可以在其中一个少写点,而另外一个则不用再写了。相比给每个字体样式写一个类,我们可以只在一个声明里,给
html
元素添加想要的规则。
html {
font-size: 125%;
font-family: sans-serif;
line-height: 1.5;
color: #222;
}
inherit
某些类型的属性是不会默认继承的,而某些元素则不会继承某些属性。但是在某些情况下,可以使用
[property name]: inherit
来强制继承。
举个例子,
input
元素在之前的例子中不会继承任何字体的属性,
textarea
也一样不会继承。为了确保所有元素都可以从全局作用域中继承这些属性,可以使用通配选择符和
inherit
关键字。这样,就可以最大程度地使用继承了。
* {
font-family: inherit;
line-height: inherit;
color: inherit;
}
html {
font-size: 125%;
font-family: sans-serif;
line-height: 1.5;
color: #222;
}
注意到我忽略了
font-size
。我不想直接继承
font-size
的原因是,它会将 heading 元素(译者注:如
h1
)、
small
元素以及其他一些元素的默认 user-agent 样式给覆盖掉。这么做我就可以节省一行代码,并且让 user-agent 决定想要什么样式。
另外一个我不想继承的属性是
font-style
:我不想重设
em
的斜体,然后再次添加上它。这将成为无谓的工作并会产生多余的代码。
现在,所有不管是可以继承或者是强制继承的字体样式都是我所期望的。我们已经花了很长时间只用两个声明区块来传递一个一致性的理念和作用域。从现在开始,除开一些例外情况,没有人会在构造组件的时候还需要去考虑
font-family
、
line-height
或者
color
了。这就是层叠的由来。
我可能想要主要的 heading 元素(
h1
)采用相同的
font-family
、
color
和
line-height
。使用继承就是很好的解决方案,但是我又想要它的
font-size
不一样。因为默认的 user-agent 样式已经给
h1
元素提供了一个大号的
font-size
(但这时它就会被我设置的相对基础字体大小为 125% 的样式覆盖掉),可能的话我不需要这里发生覆盖。
然而,难道我需要调整所有元素的字体大小吗?这时我就利用了全局作用域的优势,在局部作用域里只调整我需要调整的地方。
* {
font-family: inherit;
line-height: inherit;
color: inherit;
}
html {
font-size: 125%;
font-family: sans-serif;
line-height: 1.5;
color: #222;
}
h1 {
font-size: 3rem;
}
如果 CSS 元素的样式默认被封装,那么下面的情况就不可能了:需要明确地给
h1
添加所有字体样式。反而,我可以将样式分为几个单独的样式类,然后通过空格分隔来逐一给
h1
添加样式:
h1
。使用层叠,我已经给大部分元素赋上了想要的样式,并且只在一个方面使得
h1
成为一个例外。层叠作为一个过滤器,意味着样式只在添加新样式覆盖的时候才会发生改变。
我们已经开了个好头,但想要真正地掌握层叠,还需要尽可能多地给公共元素添加样式。为什么?因为我们的混合组件是由独立的 HTML 元素构成,并且一个屏幕阅读器友好的界面充分利用了语义化结构标记。
换句话说,让你的界面“分子化”(使用了 atomic 设计术语)的 “atoms” 样式应该在很大程度上可定位并且使用元素选择符。元素选择符的优先级很低,所以它们不会覆盖你之后可能加进来的基于类的样式。
首先应该做的事情就是给所有你即将需要使用的元素添加样式:
a { … }
p { … }
h1, h2, h3 { … }
input, textarea { … }
/* etc */
如果你想在无冗余的情况下有个一致性界面的话,那么下一步非常重要:每当你创建一个新组件的时候,如果它采用了一些新元素,那么就用元素选择符来给它们添加样式。现在不是时候去使用限制性、高优先级的选择符,也没有任何需要去编写一个样式类。语义化元素就使用其本身。
举个例子,如果我还没有给
button
元素 (就像前一个例子)添加样式,并且新组件加入了一个
button
元素,那么这就是一个给整个界面的
button
元素添加样式的好机会。
button {
padding: 0.75em;
background: #008;
color: #fff;
}
button:focus {
outline: 0.25em solid #dd0;
}
现在,当你想要再写一个新组件并且同样加入按钮的时候,就少了一件需要操心的事情了。在不同的命名空间下,不要去重写相同的 CSS,并且也没有类名需要记住或编写。CSS 本就应该总是致力于让事情变得简单和高效 – 它本身就是为此而设计的。
使用元素选择符有三个主要的优势:
生成的 HTML 更加简洁(没有多余的各种样式类)。
生成的样式表更加简洁(样式在组件间共享,不需要在每个组件里重写)。
生成的添加好样式的界面基于语义化 HTML。
使用类来专门提供样式常常被定义为“关注点分离”。这是对 W3C 的关注点分离原则的误解。它的目的是用 HTML 和 CSS 样式来描述整个结构。因为类专门是为了样式目的而制定,而且是在结构标记里出现,所以无论它们在哪里使用,技术上都是在打破分离,你不得不改变实质结构来得到样式。
不管在哪里都不要依赖表面的结构标记(样式类,内联样式),你的 CSS 应该兼容通用的结构和语义化的约定。这就可以简单地扩展内容和功能而无需它也变成一个样式的任务。同样在不同传统语义化结构的项目里,也可以让你的 CSS 变得更加可复用(但是这一点 CSS 的“方法论”可能会有所不同)。
特殊情况
在有人指责我过分简单化之前,我意识到界面上不是所有的按钮都做同样的事情,我还意识到做不同事情的按钮在某种程度上可能应该看起来不一样。
但这并不是说我们就需要用样式类、继承或者层叠来处理了。让一个界面上的按钮看起来完全不一样是在混淆你的用户。为了可访问性和一致性,大多数按钮在外观上只需要通过标签来进行区分。
记住样式并不是视觉上唯一的区分方法。内容同样可以在视觉上区分,而且在一定程度上它更加明确一些,因为你可是在文字上告诉了用户不同的地方。
大多数情况下,单独使用样式来区分内容都不是必要或者正确的。通常,样式区分应该是附加条件,比如一个红色背景或者一个带图标的文本标签。文本标签对那些使用声音激活的软件有着特定的效果:当说出 “red button” 或者 “button with cross icon” 的时候并没有引起软件的识别时。
我将在“工具类”部分探讨关于添加细微差别到看起来相似的元素上的话题。
语义化 HTML 并不仅仅关于元素。标签属性定义类型、样式属性和状态。这些对可访问性来说也很重要,所以它们需要写在 HTML 里合适的地方。而且因为都在 HTML 里,所以它们还提供了做样式钩子的机会。
举个例子,
input
元素有一个
type
属性,那么你应该想要利用它的好处,还有像
aria-invalid
属性是用来描述状态的。
input, textarea {
border: 2px solid;
padding: 0.5rem;
}
[aria-invalid] {
border-color: #c00;
padding-right: 1.5rem;
background: url(images/cross.svg) no-repeat center 0.5em;
}
这里有几点需要注意一下:
这里我不需要设置
color
、
font-family
或者
line-height
,因为这些都从
html
上继承了,得益于上面使用的
inherit
关键字。如果我想在整个应用的层面上改变
font-family
,只需要在
html
那一块对其中一个声明进行编辑就可以了。
border 的颜色关联到
color
,所以它同样是从全局
color
中继承。我只需声明 border 的宽度和风格。
[aria-invalid]
属性选择符是没有限制的。这意味着它有着更好的应用(它可以同时作用在
input
和
textarea
选择符)以及最低的优先级。简单的属性选择符和类选择符有着同样的优先级。无限制使用它们意味着之后任何写在层叠下的样式类都可以覆盖它们。
BEM 方法论通过一个修饰符类来解决这个问题,比如
input--invalid
。但是考虑到无效的状态应该只在可通信的时候起作用,
input--invalid
还是一定的冗余。换句话说,
aria-invalid
属性不得不写在那里,所以这个样式类的目的在哪里?
只写 HTML
在层叠方面关于大多数元素和属性选择符我绝对喜欢的事情是:组件的构造变成更少地了解公司或组织的命名约定,更多地关注 HTML。任何精通写出像样 HTML 的开发者被分配到项目中时,都会从已经写到位的继承样式当中获益。这些样式显著地减少了读文档和写新 CSS 的需要。大多数情况下,他们可以只写一些死记硬背应该知道的(meta)语言。Tim Baxter 同样为此在 Meaningful CSS: Style It Like You Mean It 里写了一个案例。
目前为止,我们还没有写任何指定组件的 CSS,但这并不是说我们还没有添加任何相关样式。所有组件都是 HTML 元素的组合。形成更复杂的组件主要是靠这些元素的组合顺序和排列。
这就给我们引出了布局这个概念。
主要我们需要处理流式布局 – 连续块元素之间的间距。你可能已经注意到目前为止我没有给任何元素设置任何的外边距。那是因为外边距不应该考虑成一个元素的属性,而应该是元素上下文的属性。也就是说,它们应该只在遇到元素的时候才起作用。
幸运的是,直接相邻选择符可以准确地描述这种关系。利用层叠,我们可以使用一个统一默认贯穿所有连续块级元素的选择符,只有少数例外情况。
* {
margin: 0;
}
* + * {
margin-top: 1.5em;
}
body, br, li, dt, dd, th, td, option {
margin-top: 0;
}
使用优先级极低的猫头鹰选择符确保了任意元素(除了那些公共的例外情况)都通过一行来间隔。这意味着在所有情况下都会有一个默认的白色间隔,所有编写组件流内容的开发者都将有一个合理的起点。
在大多数情况下,外边距只会关心它们自己。不过因为低优先级,很轻易就可以在需要的时候覆盖掉那基础的一行间隔。举个例子,我可能想要去掉标签和其相关元素之间的间隔,好表示它们是一对的。在下面的示例里,任意在标签之后的元素(
input
、
textarea
、
select
等等)都不会有间隔。
label {
display: block
}
label + * {
margin-top: 0.5rem;
}
再次,使用层叠意味着只需要在需要的时候写一些特定的样式就可以了,而其他的元素都符合一个合理的基准。
需要注意的是,因为外边距只在元素之间出现,所以它们不会和可能包括在容器内的内边距重叠。这也是一件不需要担心或者预防的事情。
还注意到不管你是否决定引入包装元素都得到了同样的间隔。就是说,你可以像下面这样做并实现相同的布局 – 外边距在
p
之间出现比在标签和输入框之间出现要好得多。
<form>
* + *
隐式控制的
first-child
这种例外情况:
color
、
background-color
以及其他属性建立独立的样式类,因为 atomic CSS 不会控制继承或者元素选择符。
html
)样式并强制继承,
流式布局方法及部分例外(使用猫头鹰选择符),
元素及属性样式。
我们还没有编写一个特定组件或者构思一个 CSS 样式类,但我们大部分的样式都已经写好了,前提是如果我们能够将样式类写得合理且可复用。
关于样式类它们有一个全局作用域:在 HTML 里任何地方使用,它们都会被关联的 CSS 所影响。对大多数人来说,这都被看做一个弊端,因为两个独立的开发者有可能以同样的命名来编写一个样式类,从而互相影响工作。
CSS modules 最近被用来解决这种情况,通过以程序来生成唯一的样式类名,绑定到它们的局部或组件作用域当中。
忽略掉生成代码的丑陋,你应该能够看到两个独立组件之间的不同,并且可以轻易地放在一起:唯一的标识符被用来区分同类的样式。在这么多更好的努力和冗余代码下,结果界面将要么不一致,要么一致。
没有理由对公共元素来进行唯一性区分。你应该对元素类型添加样式,而不是元素实例。谨记 “class” 意味着“某种可能存在很多的东西的类型”。换句话说,所有的样式类都应该是工具类:全局可复用。
当然,在这个示例里,总之
.button
类是冗余的:我们可以用
button
元素选择符来替代。但是如果有一种特殊类型的按钮呢?比如,我们可能编写一个
.danger
类来指明这个按钮是做危险性操作,比如删除数据:
.danger {
background: #c00;
color: #fff;
}
因为类选择符的优先级比元素选择符的优先级高,而和属性选择符优先级相同,所以这种方式添加在样式表后面的样式规则会覆盖前面元素和属性选择符的规则。所以,危险按钮会以红色背景配白色文本出现,但它其他的属性,比如内边距,聚焦轮廓以及外边距都会通过之前的流式布局方法添加,保持不变。
如果多位开发人员长时间在同样的代码基础上工作,那么偶尔就会发生命名冲突。但是有几种避免这种情况的方法,比如,噢,我不太知道,但对于你想要采用的名称我建议首先做一个文本搜索,看看是否已经存在了。因为你不知道,可能已经有人解决了你正在定位的问题。
局部作用域的各种工具类
对于工具类来说,我最喜欢做的事情就是把它们设置在容器上,然后用这个钩子去影响内部子元素的布局。举个例子,我可以快速对任意元素设置一个等间隔、响应式以及居中的布局。
.centered {
text-align: center;
margin-bottom: -1rem; /* adjusts for leftover bottom margin of children */
}
.centered > * {
display: inline-block;
margin: 0 0.5rem 1rem;
}
使用这个方法,我可以把列表项、按钮、按钮组合以及链接等随便什么元素居中展示。全靠
> *
的使用,在这个作用域中,它意味着带有
.centered
样式的元素下最近的子元素将会采用这些样式,并且还继承全局和父元素的样式。
而且我调整了外边距,好让元素可以自由进行包裹,而且不会破坏使用
* + *
选择符设置的垂直设定。这少量的代码通过对不同元素设置一个局部作用域,就提供了一个通用、响应式的布局解决方案。
我的一个小型(压缩后 93B)的基于 flexbox 网格布局系统 就是一个类似这种方法的工具类。它高度可复用,而且因为它使用了
flex-basis
,所以不需要断点干预。我只是用了 flexbox 布局的方法。
.fukol-grid {
display: flex;
flex-wrap: wrap;
margin: -0.5em; /* adjusting for gutters */
}
.fukol-grid > * {
flex: 1 0 5em; /* The 5em part is the basis (ideal width) */
margin: 0.5em; /* Half the gutter value */
}
使用 BEM 的方法,你会被鼓励在每个网格项上放置一个明确的“元素”样式类:
> *
所影响。仅有的区别就是充斥了大量样式类的标记。
所以,现在我们已经开始合并样式类,但只在通用性上合并,和它们所预期的效果一样。我们仍然还没有独立地给复杂组件添加样式。反而,我们在以一种可复用的方式解决一些系统性的问题。当然,你将需要在注释里写清楚这些样式类是如何使用的。
像这些的工具类同时采用了 CSS 的全局作用域、局部作用域、继承以及层叠的优点。这些样式类可以在各个地方使用,它们实例化局部作用域从而只影响它们的子元素,它们从父级或全局作用域中继承没有设置在自身的样式,而且我们没有过度使用元素或类选择符。
下面是现在我们的层叠看上去的样子:
全局(
html
)样式和强制性继承,
流式布局方法和一些例外(使用猫头鹰选择符),
元素和属性样式,
通用的工具类。
当然,可能没有必要去编写所有这些示例工具类。重点是,如果在使用组件的时候出现了需求,那么解决方案应该对所有组件都有效才行。一定要总是站在系统层面去思考。
特定组件样式
我们从一开始就已经给组件添加了样式,并且学习样式结合组件的方法,所以很多人有可能会忽略掉马上要讲到这个部分。但值得说明的是,任何不是从其他组件中创建的组件(甚至包括单个 HTML 元素)都是有必要存在的。它们是使用 ID 选择符的组件,以及有可能成为系统问题的风险。
事实上,一个好的实践是只使用 ID 来给复杂组件标识(“molecules”、“organisms”),并且不在 CSS 里使用这些 ID。比如,你可以在登录表单组件上写一个
#login
,那么你就不应该在 CSS 里以元素、属性或者流式布局方法的样式来使用
#login
,即使你可能会发现你在创造一个或两个可以在其他表单组件里使用的通用工具类。
如果你确实使用了
#login
,那么它只会影响那个组件。值得提醒的是如果这么做,那么你就已经偏离了开发一个设计系统方向,并且朝着只有不停纠结像素的冗长代码前进。
私が BEM のような方法論や CSS モジュールのようなツールを使用していないことを人々に話すと、ほとんどの人は私が次のように CSS を書くだろうと考えるでしょう:
header nav ul li {
display: inline-block
}
header nav ul li a {
background: #008;
}
私はそれをしませんでした。ここでは、避けるべき注意事項とともに、明確な声明が示されています。 BEM (OOCSS、SMACSS、アトミック CSS などと併せて) が、複雑で管理不可能な CSS を回避する唯一の方法ではない、ということだけは言っておきます。
優先順位の問題を解決するために、多くの方法論はほぼすべてクラス セレクターの使用を選択します。問題は、これによって多くのスタイル クラスが作成されることです。あらゆる種類のマジック コードが HTML マークアップを肥大化させ、ドキュメントへの焦点を失わせるため、新しい開発者が作業しているシステムについて混乱し混乱する可能性があります。
スタイル クラスを広範囲に使用すると、スタイル システムも管理する必要があります。このシステムは HTML システムとは大きく異なります。この不適切ないわゆる「関心事の分離」は、冗長性、あるいはさらに悪いことにアクセシビリティ不能につながる可能性があります。アクセシブルな状態のビジュアル スタイルに影響を与える可能性があります:
多数の書き込みクラスとさまざまなスタイル クラスを置き換えるために、他のメソッドをいくつか見つけました。
一貫性制御のため、前提条件を設定するために継承します。要素と属性のセレクターを最大限に活用して、透明性と標準ベースの組み合わせスタイルをサポートします。
シンプルな流動的なレイアウト システムを使用します。
いくつかの非常に汎用的なツール クラスをマージして、複数の要素に共通するレイアウトの問題を解決します。
これらのメソッドはすべて、新しいコンポーネントの作成を容易にし、プロジェクトが成熟するにつれて新しい CSS コードの追加への依存を減らすデザイン システムを作成することを目的としています。そして、これは厳密な名前付けとマージの利点ではなく、むしろそれらがないことによる利点です。
ここで私が推奨する特定のテクニックには興味がないかもしれませんが、この記事が少なくともコンポーネントとは何かを再考するきっかけになれば幸いです。それらはあなたが独自に作成するものではありません。標準の HTML 要素の場合、それが自分で作成したものではない場合もあります。コンポーネントが他のコンポーネントから借用すればするほど、インターフェースのアクセシビリティと視覚的な一貫性が向上し、最終的にはそれらの実装に使用する CSS の量が減ります。
(これらの問題) CSS はそれほど責められるべきものではありません。実際、あなたにたくさんのことをさせてくれるのは素晴らしいことですが、私たちはそれを活用していないだけです。
以上がCSS継承分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。