關於語意
語意研究的是標誌與符號之間的關係,以及它們所代表的意義。在語言學中,它主要是研究這些標誌(如單詞,短語,或聲音)在語言中的意義。而在前端開發領域,語意主要涉及的是HTML元素、屬性和屬性值(包括像Microdata這樣的擴充)所約定的意義。這些在規範中常用的正式約定語義,可以幫助程式(以及後來參與開發的人)更好地理解一個網站各方面的資訊。然而,即使這些元素、屬性和屬性值的語意是正式化的,它們依然得服從於開發者的適應程度以及共同選擇的結果。這使得正式的約定語意也可能在未來被修改(而這正是HTML設計原則之一)。
區分不同類型的HTML語意
遵守編寫「語意化的HTML」這個原則,是現代專業前端開發的基礎之一。絕大多數的語意都與目前或預期的內容性質有關(如:h1元素,lang屬性,type屬性的email值,Microdata)。
然而,並非所有的語意都需要以內容為導向。類別名稱不能“無語義”。不管是用什麼名字命名,它們都必須有意義與目的。類別名的語意可以和那些HTML元素不同。我們可以藉助HTML元素、某些HTML屬性、Microdata等所具有的「全域性」語義,然後利用網站或應用的「局部性」特定語意加以區分,這些特定語意通常包含在屬性值中,例如class屬性。
儘管在HTML5規範的class屬性這一章節中重申了這個假定的「最佳實踐」…
…鼓勵開發者使用class屬性值來描述實際內容,而不是描述期望展現的內容。
…並沒有什麼內在的原因非這樣做不可。事實上,當這種方法在大型網站或應用程式中運用時,它往往會成為一種障礙。
HTML元素和其它屬性已經提供了內容層的語意
對機器或訪客來說,類別名稱所能揭露的有用的語意資訊非常少,甚至沒有。除非它是已經約定好的那一小部分名稱(機器同樣可讀) —— Mircoformats
類別名稱的主要用途是成為CSS和JavaScript的鉤子。如果你不需要為你的頁面添加表現和行為,那麼你或許不必在你的HTML裡添加類別名稱
類別名稱應該為開發者傳達有用的訊息。當你閱讀一個DOM片段時,它將有助於理解某個類別名稱的具體作用。尤其是在多人協作的開發團隊裡,與HTML元件打交道的可不光只有前端開發者。
舉一個很簡單的例子:
<p class="news"> <h2>News</h2> [news content] </p>
當內容還不明顯的時候,這個類別名稱news不能告訴你任何事情。它沒有向你提供關於這個組件整體結構的信息,而且一旦內容不再是“新聞”時,使用這個類名就顯得非常不妥。類別名的語意過度貼近內容,架構既不容易擴展,也不容易為其他開發人員所用。
與內容無關的類別名稱
從某個設計模式的結構與功能中提取類別名稱的語意是更好的方法。那些類別名稱與內容無關的元件可重複使用性較高。
我們不應該害怕讓各層之間的關係變得清晰而明確(這裡應該是指結構層、內容層等,譯者註),而不是用類名嚴格地反應明確的內容。這樣做不會使類別名稱“無語義”,這只是表明它們的語義並不取決於內容。我們也不應該害怕使用額外的HTML元素,只要它們能幫助你創造出更強壯、更靈活且更具重複使用性的元件。這樣做不會使HTML變得“無語義”,這僅僅意味著你標記內容所使用的元素數量超過了最小值而已。
前端架構
元件、模板、物件導向的體系結構的目的是能夠開發出一個數量有限的可重複使用的元件,它可以在一定範圍內包含不同的內容類型。在大型的應用程式中,對類名語義來說最重要的事情是,能夠用實用主義服務於它們的主要目的—— 提供有意義的、靈活的、可重複使用的表現或行為的鉤子供開發者使用。
可重複使用且可組合的元件
總的來說,可擴充的HTML/CSS必須依賴HTML中的class,以便建立可重複使用的元件。一個靈活的、可重複使用的元件,既不依賴DOM樹的某一部分,也不需要使用特定類型的元素。它應該能適應不同的容器,並且可以輕鬆更換主題。如果有必要,額外的HTML元素(超出標記內容所需的元素之外的元素)可以讓元件更強壯。 Nicole Sullivan所說的media object就是一個很好的例子。
避免用类型选择器支持class,可以让组件更容易合并。下面这个例子中,btn组件与uilist组件不易于合并。问题在于.btn的权重比.uilist a要小(这将覆盖任何共享属性)。而且ulist组件需要锚点作为子节点。
.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组件与其它组件轻松组合的方法是,uilist的子级DOM元素用class来添加样式。尽管这会降低权重,但是它的主要好处在于,它为你提供了处理子节点的任何结构样式的选择权。
.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 { /* 按钮模板样式 */ } .btn-primary { /* 主按钮的特殊样式 */ } <button class="btn">Default</button> <button class="btn-primary">Login</button>
多类名模式
.btn { /* 按钮模板样式 */ } .btn-primary { /* 主按钮的特殊样式 */ } <button class="btn">Default</button> <button class="btn btn-primary">Login</button>
如果你使用预处理程序,你可以用Sass的@extend功能,以减少一些在使用“单类名”模式时所涉及的维护工作。然而,即使有预处理程序的帮忙,我依然倾向于使用“多类名”模式,并在HTML中修改类名。
我发现这是一种更具扩展性的模式。比如,要实现一个基本的btn组件,并增加5种类型的按钮与3种额外的尺寸。用“多类名”模式的话只要9个class就可以搞定,用“单类名”模式则需要24个class。
如果需要的话,它也更容易让上下文环境适应组件。你可能想对出现在其它组件中的任一btn做一些细节调整。
/* “多类名”样式调整 */ .thing .btn { /* 相应的样式调整 */ } /* “单类名”样式调整 */ .thing .btn, .thing .btn-primary, .thing .btn-danger, .thing .btn-etc { /* 相应的样式调整 */ }
“多类名”模式意味着,你只需要用一个单独的组件内部选择器,便可以改变所有类型的btn元素的样式。“单类名”模式意味着,你必须顾及所有可能的按钮类型,并在创造一个新的按钮变体时调整这个选择器。
结构化的类名
当创建一个组件时——并为之添加了“主题”——其中一些class被用来区分各个组件,一些class被当做组件的修改器,其它的class则被用来关联DOM节点,它们一起被包含在一个较大的抽象组件中。
很难去判断btn(组件)、btn-primary(修改器)、brn-group(组件)和btn-group-item(组件子对象)之间的关系,这是因为这些名字不能清晰地表现class的目的。没有一致的模式。
在过去的一年中,我一直在尝试命名模式,目的是能帮助我快速理解在一个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</p> <p>component-name component-name--modifier-name component-name__sub-object component-name__sub-object--modifier-name</p> <p>is-state-type</p> <p>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屬性。這樣處理之後,檔案大小會減少到25KB。當原始檔案與扒下來的檔案都經過gzip壓縮後,它們的大小分別變為7.6KB和6KB—只相差1.6KB。自由使用class所導致的實際檔案大小的結果已經不值得再去強調了。
更多HTML的語意和與其相關的前端框架相關文章請關注PHP中文網!