在定义了规范后,元素、属性(attribute)及属性(attribute)值的语义还要受到开发商共同选择和实现的影响,这就导致了后期规范化的约定好的语义要进行修改(这是HTML设计的一个原则)。
关于语义
语义是研究符号和标志,以及他们所代表的事物之间的关系的。在语言学里,主要是研究符号所表达的意思(如单词、短语或声音)。Web前端开发中,语义主要涉及到了约定好的HTML元素、属性(attribute)及属性(attribute)值的意义(包括一些扩展,如微数据)。这些约定好的语义,通常是正式的规范,可以用来更好的理解一个网站的各方面信息。但是,在定义了规范后,元素、属性(attribute)及属性(attribute)值的语义还要受到开发商共同选择和实现的影响,这就导致了后期规范化的约定好的语义要进行修改(这是HTML设计的一个原则)。
区分不同类型的HTML语义
编写“具有语义的HTML”原则是现代、专业前端开发的一个基础。大多数的语义和自然存在的或预期内容的多方面相关(例如,h1元素、lang属性(attribute)、type属性(attribute)的email值、微数据)。
然而,并非所有的语义需要根据内容衍生出来的。类名不能是“非语义”的。无论使用什么名字,他们都需要有意义、有目的。类名语义跟HTML语义不同。我们可以使用HTML元素,某些HTML属性(attributes)、微数据等约定的“global”语义,他们的目的不会和那些网站、特定的应用程序的“local”语义产生混淆。那些“local”的语义是包含在属性(attribute)值里的,如class属性(attribute)。
尽管HTML5 specification section on classes再次假定“最佳实践”是:
“鼓励作者使用[类属性(attribute)]值来描述实际内容的本质,而不是描述那些所需的内容。”
本身没有理由这样做的。实际上,在开发大型网站或应用程序时,这样经常会是一个阻碍。
内容层语义本来服务于HTML元素和其他属性(attribute)。
类名给机器或者访问者提供很少或没有用的语义信息,除非是商定好的名字(并且是机器可读的)的一小部分——微格式。
使用类名主要的目的是为了使用CSS和javascript。如果你不需要在你的web文档里添加表现和行为,那么在你的HTML里,就可能不需要类名。
类名应该传达有用的信息给开发者。当你在阅读一个DOM代码段的时候,它有助于理解一个特定的类名是做什么的,特别是在多个开发者的团队里,前端工作者并不是唯一开发HTML组件的。
看一个非常小的例子:
<div class="news">
<h2>News</h2>
[news content]
</div>
这里的类名“news”不会告诉你任何信息,从内容上看,它本身就不是很明显。关于这个组件的总体结构没有给你提供信息,它不能够用于非“news”的内容。试着将你的类名语义和实际的内容紧密结合,会降低架构可扩展性的能力和被其他开发者使用的易用性。
内容独立的类名
在一个设计里,另一种方法是根据重复结构和功能模式衍生类名语义。复用性最高的组件是那些内容独立的类名。
我们不应该担心层与层之间的连接是否清晰明确,应该担心那些反映具体内容的类名。这样做并不是意味着类名“无语义”,它仅仅意味着这些类名的语义不是根据内容衍生出来的。如果一些额外的HTML元素有助于创造更强大的、灵活的、可重用的组件,我们就不介意使用额外的HTML元素,这只是意味着你使用了中国足彩网的元素来标记内容。
前端架构
一个组件、模版、面向对象的架构的目的是能够开发一些可重用的组件,这些组件包含了一系列不同的内容类型。类名语义在大型应用程序里的重要性在于它是由实用性衍生出来并且很好的服务于这个组件的主要目的-为开发人员提供有意义,灵活,可复用的表现或行为性的接口,以便使用。
可复用、可组合的组件
总的来说,可扩展的HTML或CSS必须依赖HTML里的类来创建可复用的组件。一个灵活的、可复用的组件既不是依赖于存在的DOM树里的某个部分,也不需要使用特定的元素类型。它应该能够适应不同的容器,并且可以容易的加上样式。如果有必要,添加额外的HTML元素(除了那些仅用来标记内容的HTML元素)可以用来创建更强健的组件。一个很好的例子就是Nicole Sullivan所说的的media object.
易于组合的组件得益于使用选择器类型,而避免使用类型选择器。下面的例子降低了btn部分和uilist部分易组合性。问题在于.btn的优先级比.uilist a(会覆盖重复的属性样式)的优先级低,并且uilist需要a作为子结点。
.btn { /* styles */ }
.uilist { /* styles */ }
.uilist a { /* styles */ }
<nav class="uilist">
<a href="#">Home</a>
<a href="#">About</a>
<a class="btn" href="#">Login</a>
</nav>
提高一个组件部分和uilist部分的易组合性的一个方法是使用类给子DOM元素添加样式。这样可以减少样式规则的特性,但是最大的好处就是允许你将结构上的样式应用到任何类型的子节点上。
.btn { /* styles */ }
.uilist { /* styles */ }
.uilist-item { /* styles */ }
<nav class="uilist">
<a class="uilist-item" href="#">Home</a>
<a class="uilist-item" href="#">About</a>
<span class="uilist-item">
<a class="btn" href="#">Login</a>
</span>
</nav>
Javascript特定类
使用javascript特定类有助于降低因组件主题样式或结构改变而导致原来应用在上面的javascript不起作用的风险。我发现一个方法是使用某些只给javascript使用的类——js-*——不给这些特殊类添加任何样式呈现。
<a href="/login" class="btn btn-primary js-login"></a>
改变组件的结构或主题将无意中影响任何所需的JavaScript行为和复杂的功能,使用这种方法,你可以减少这种无意中的情况发生。
组件修饰符
组件经常会有些变化,和基组件的呈现有稍微的不同,如不同颜色背景或者边框。有两种模式可以使用来达到这些变化。我把这两种模式称为“单类”模式和“多类”模式。
“单类”模式
.btn, .btn-primary { /* button template styles */ }
.btn-primary { /* styles specific to save button */ }
<button class="btn">Default</button>
<button class="btn-primary">Login</button>
“多类”模式
.btn { /* button template styles */ }
.btn-primary { /* styles specific to primary button */ }
<button class="btn">Default</button>
<button class="btn btn-primary">Login</button>
在使用“单类”模式时,如果你使用了预处理器,你可能使用Sass的@extend功能减少一些维护的工作。然而,即使有预处理器的帮助,我优先选择的是使用“多类”模式,在HTML里添加类修饰符。
我发现“多类”模式是一个更具有扩展性的模式。例如,使用基于btn的组件,进一步添加了5种按钮类型和3种按钮尺寸。使用“多类”模式,最终你需要9个类,通过混合匹配达到效果。但是使用“单类”模式你需要24个类。
如果需要,使用组件修饰符也比较容易根据上下文调整一个组件。比如你可能在另一个组件里对btn的外观要做些细微的调整,可以这么做:
/* "multi-class" adjustment */
.thing .btn { /* adjustments */ }
/* "single-class" adjustment */
.thing .btn,
.thing .btn-primary,
.thing .btn-danger,
.thing .btn-etc { /* adjustments */ }
“多类”模式意味着你只需要一个单独的内部组件选择器去匹配任意类型的btn——在这个组件里被应用了btn的样式元素。“单类”模式意味着你需要写出任何可能的按钮类型,并且在任何时候改变了一个按钮的外观,你都需要调整这个选择器。
结构化类名
当创建组件时——并添加了“主题“——一些类用于区分不同的组件,一些类用于组件修饰符,一些类和DOM结点关联,他们被包含在一个较大的抽象呈现组件里。
很难判断btn(组件)、btn-primary(修饰符)、btn-group(组件)和btn-group-item(组件子对象)之间的关系,因为这些名字不能清楚的使人明白这些类的用途。没有一致的模式。
在过去的一年里我一直在尝试命名模式,这种模式能够帮助我更快的理解DOM片段里结点间的关系,而不是试着来回的在HTML、CSS和JS文件之间查看, 拼凑出网站的架构。这种模式主要受BEM系统方法命名的影响,但是,被我改编成一种更容易阅读的形式。
t-template-name t-template-name--modifier-name t-template-name__sub-object t-template-name__sub-object--modifier-name component-name component-name--modifier-name component-name__sub-object component-name__sub-object--modifier-name is-state-type js-action-name js-component-type
我把一些结构当做抽象的“模板”,其它则当作更清晰的组件(通常建立在“模板”上)。但是这种区分并非总是必要的。
这只不过是我目前发现的一个命名模式而已。它可以采取任何形式。但是这种模式的好处在于它消除了那些只依赖(单)连接符,或者下划线,或者是驼峰格式的模糊的类名。
原始文件大小和HTTP压缩
任何关于模块化与可扩展的CSS的讨论关心的是文件大小和“膨胀“。Nicole Sullivan的言论中经常会提到文件大小存储(以及维护改进),并提到了像Facebook这样的公司采用这种方法的经历。进一步的,我想我会分享我在预处理输出时的HTTP压缩效果,以及大量使用HTML类的一些事情。
当Twitter 的Bootstrap刚面世时,我重写了编译好的CSS,以便更好地与手动操作的文件比较大小。在最小化两个文件之后,手动操作的CSS文件比预处理程序输出的小10%。但是当两个文件都通过gzip压缩后,预处理程序输出的CSS文件比手动操作的小了5%。
这强调了比较HTTP压缩后文件大小的重要性,因为减小文件大小并不能说明一切。它暗示了有经验的CSS开发者在用预处理程序时不必太过关心编译后的CSS中有一定程度的重复,因为经过HTTP压缩后,文件将变得更小。通过预处理程序处理更易于维护的CSS代码所带来的好处,要胜过关注原始CSS和压缩后输出的CSS的美观或文件大小。
在另一个实验中,我从网站上弄了一个60KB的HTML文件(由很多可重用的组件组成),删除了它的每一个class属性(attribute)。这样处理之后,文件减小到25KB。当原始文件与修改过的文件都通过gzip压缩后,它们的大小分别变为7.6KB和6KB——只相差1.6KB。自由使用class所导致的实际文件大小的结果已经不值得再去强调了。
我如何学会停止担忧的…
多年来,许多技术熟练的开发人员的经验,已经改变了大型网站和应用程序的开发方式。尽管如此,对于个人来说,放弃“语义化的HTML”的观念,意味着将由内容来决定类名(即使非要这么做,也只能作为最后的手段来使用),这通常需要你开发完一个大型的应用程序后,才能够强烈地意识到这种做法不靠谱的一面。你必须准备好抛弃老观念,寻找替代方案,甚至重新审视以前已被你摒弃的方法。
一旦你开始写大型的网站和应用,你和其他人不仅必须去维护它,而且还得积极地迭代改进。你很快会意识到即使你尽了最大的努力,你的代码仍然开始变得越来越难以维护。已经有一些人提出了他们自己的方法来解决这些问题:Nicole的博客和面向对象的CSS项目,Jonathan Snook的可扩展的模块化结构CSS,以及Yandex开发的BEM方法。这些方法值得我们花时间去探索。
当你选择了以减少编写和编辑CSS的时间为目的的方式来编写HTML和CSS时,那么如果你想改变它们的样式,你就必须接受花中国足彩网的时间去改变HTML元素的class。无论是前端还是后端开发者 —— 任何人都可以重新排列预建的“乐高积木”,这将是相当实用的;事实证明,没有人会CSS魔法。