扒一扒 CSS 語言的誕生史

寸志發表於2016-07-28

  實話說,在過去這一年,這已經成為我好心情的固定來源。即不斷地告訴一波波想要像在 TeX、Microsoft Word 等常見的文件處理工具中那樣方便地控制 HTML 文件的樣式的人們說——安全帶繫好,受傷別怪我:“很不好意思,你完蛋了!” —— 馬克·安德森 1994年

  在1991年,蒂姆·伯納斯·李首次提出 HTML 的時候,並沒有給頁面新增樣式的方法。給定的 HTML 該如何渲染決定於瀏覽器本身,常常還受到使用者偏好輸入的影響。然而,這看起來確實是一個不錯的想法,為網頁建立標準,使用者可以提供“建議”該以什麼樣的風格渲染頁面。

  但要知道五年後才有 CSS,十年後 CSS 才獲得全面的實現。因此這是一個群雄逐鹿的時代,很多多熱心的工作和變革,產生了多個互相競爭的樣式方案,看上去很有可能成為標準。

  儘管這些語言在今天並沒有用起來,但是我發現思考彼時的未來會變成什麼樣子真的很有奇妙。更讓人驚訝的是,碰巧這些可能成為 CSS 的語言包含的一些特性正是如今開發者希望出現在 CSS 中的。

 第一個提案

  1993年年初,Mosaic 還沒有釋出 1.0,當時其他已有的瀏覽器還在搞怎麼處理 HTML。並沒有什麼方法可以來給 HTML 新增樣式。總之就是,<h1>的樣式完全取決於瀏覽器。

  在這年的6月,Robert Raisch 在www-talk的郵件列表中給出了一個提案,建立了“一個解析容易與 Web 文件一起釋出的樣式資訊的格式”,賜名 RRP。

@BODY fo(fa=he,si=18)

  看不懂這段程式碼也很正常。在沒有 gzip,網路傳輸速度只有 14.4k 的時代,盡力壓縮新格式的大小是非常合理的。這段規則的實際上是設定字型(font family -> fa)為helvetica(he),字號(font size -> si)為 18 畫素。

  這個提案缺少一個有意思的東西就是單位,所有數字對應的單位決定於他們使用的上下文(例如字型的大小都是以畫素為單位的)。這可以說明 RRP 設計的目的是作為“一系列指導渲染的指示或者建議的集合”,而不是作為標準。這是值得考慮的,因為同一份樣式表必須在 common line mode 瀏覽器和圖形瀏覽器(例如 [Lynx](https://en.wikipedia.org/wiki/Lynx_(web_browser))都能正常工作,後一種瀏覽器變得越來越流行。

Lynx browser screenshot

  有趣的是,RRP 包含設定列布局的方式,這個特性直到2011年才引入到 CSS 中。例如,3列每列80單位就是下面這樣子:

@P co(nu=3,wi=80)

  這解析起來有點困難,但應該沒有white-space: nowrap難。

  值得一提的是,RRP 並不支援如今所用的“層疊”樣式表。一個文件同一時刻只能啟用一個樣式表,這從邏輯上來說是合理的,但是今天看來就有點奇怪了。

  馬克·安德森(一個曾經最流行的瀏覽器 Mosaic 的創造者)知道 RRP 提案,但是並沒有在 Mosaic 中實現它。Mosaic 很快(同時也是遺憾地)就採用了通過 HTML 來定義樣式的方案,引入像<FONT>和<CENTER>這樣的標籤。

 Viola 以及原始瀏覽器之戰

現在臺面上已經有多個樣式表的提案,為什麼你不選其中一個實現之?只要正確地實現了問題就將得到解決。

因此,我必須告訴大家,“好了,你需要學習這種語言來撰寫你的文件,然後學習另外種語言來來把你的文件定義成你想要的樣子。”噢,他們會喜歡這樣的。 —— 馬克·安德森 1994

  反直覺的是,Mosaic 並不是第一個圖形化的瀏覽器。ViolaWWW 要比它還早,Pei-Yuan Wei 起初花了四天時間寫出的圖形化的瀏覽器。

Viola browser screenshot

  Pei-Yuan Wei 建立了一個樣式表語言,支援某種巢狀式的結構,這已經被我們用在了今天的 CSS 之中:

(BODY fontSize=normal
	  BGColor=white
	  FGColor=black
  (H1   fontSize=largest
	    BGColor=red
	    FGColor=white)
)

  在上例中,為 body 設定顏色,並給出現在 body 中的 h1 設定樣式。PWP 並沒有採用重複的選擇器來表示層級,而是使用圓括號系統。這讓我聯想到了想 Stylus 和 SASS 使用的縮排系統,如今這在某些 CSS 開發者中很流行。從這方面來講,PWP 的語法比 CSS 更好,不過後者已經成為了 Web 的通用語言。

  值得一提的是 PWP 還是引用外部樣式表方法的提出者,到今天也一直在用:

<LINK REL="STYLE" HREF="URL_to_a_stylesheet">

  遺憾的是,ViolaWWW 只能在 X Window System 下工作,後者只在 Unix 系統上受歡迎。當 Mosaic 移植到到 Windows 後,Viola 就消失不見了。

 Web 之前的樣式表

只有電腦科學家才能接受 HTML 這種東西。它確實可以表達一個文件的內在結構,但一個文件不只包含文字資料的結構。它們需要有視覺的衝擊力。HTML 完全抹殺了文件設計者本應該有的視覺創造力。—— Roy Smith 1993

  實時上,對定義文件樣式語言的需求早在網際網路出現之前就有了。

  你要知道 HTML 也是基於一種網際網路之前就存在的語言 SGML 制定的。早在1987年,美國國防部就決定調研 SGML 是否可以簡化文件的儲存和傳輸,他們有大量的文件需要處理。與其他好的政府專案一樣,沒有時間浪費在起一個好聽的名字上。這個小組名字一開始叫做 Computer-Aided Logistics Support(計算機輔助後勤支援),後來更名為 Computer-aided Acquisition and Logistics Support(計算機輔助採集和後勤支援),最後是 Continuous Acquisition and Life-cycle Support(持續採辦與全壽命支援計劃)。總之首字母縮寫就是 CALS。

  CALS 團隊創造了一門語言來定義 SGML 文件的樣式,名為 FOSI,毫無疑問也是某四個單詞的縮寫。他們釋出了一份語言規範,儘管全面,但理解不能。

  網際網路一個不變的鐵律就是:在你能證明某人錯誤之前必須做更多的事情。1993年,在 Pei-Yuan 給出提案的第四天,Steven Heaney 提出使用 FOSI 一個變體來定義 Web 的樣式,他並沒有“重新發明輪子”。

  FOSI 文件直接就用 SGML 寫成,這對於熟悉 SGML 變體 HTML 的 Web 開發者來說有一種邏輯上的轉換。文件示例:

<outspec>
  <docdesc>
	<charlist>
	  <font size="12pt" bckcol="white" fontcol="black">
	</charlist>
  </docdesc>
  <e-i-c gi="h1">\<font size="24pt" bckcol="red", fontcol="white"\></e-i-c>
  <e-i-c gi="h2">\<font size="20pt" bckcol="red", fgcol="white"\></e-i-c>
  <e-i-c gi="a"><font fgcol="red"></e-i-c>
  <e-i-c gi="cmd kbd screen listing example"><font style="monoser"></e-i-c>
</outspec>

  你搞不清楚docdesc和charlist是什麼意思對吧?www-talk成員也搞不清楚。唯一可以給出上下文資訊的只有e-i-c,即“element in context”。FOSI 值得傲嬌的是引入了 em 作為字型的單位,現在已經作為熟悉 CSS 的開發者的首選方式。

  語言之間的戰爭就如語言本身一樣古老。當時正值“lisp-style”函式式語言與申明式語言的戰爭。Pei-Yuan 把自己的語法描述為是“LISP式的”,但這只是時間的問題,LISP 真正的變種語言馬上就要出現了。

 圖靈完備的樣式表

  受累於複雜性,FOSI 被看做是解決文件格式問題的臨時過度方案。長遠的方案是基於函式式語言 Scheme 建立一門新的語言,基於強大的能力,能對文件進行任何你可以想到的轉換。這門語言叫做 DSSSL。用貢獻者 Jon Bosak 的話來講:

把 DSSSL 和指令碼語言放在一起是一種錯誤。沒錯,DSSSL 是圖靈完備的,它確實是一枚程式語言。但是指令碼語言(至少我是這麼叫的)是程式化的;DSSSL 則完全不一樣。它完全就是函式式的,完全沒有副作用。在 DSSSL 樣式表沒有任何影響,樣式表是一個巨大的函式,它的價值是一個抽象的,與裝置無關的,非過程化的,對文件格式的描述,作為一種規範(也可稱其為申明)送到顯示區域逐級渲染過程中。

  得益於它的簡潔,DSSSL 實際上是一種很容易理解的樣式語言:

(element H1
  (make paragraph
	font-size: 14pt
	font-weight: 'bold))

  同時作為程式語言,你甚至可以定義函式:

(define (create-heading heading-font-size)
  (make paragraph
	font-size: heading-font-size
	font-weight: 'bold))

(element h1 (create-heading 24pt))
(element h2 (create-heading 18pt))

  還可以在樣式中使用計算,比如定義一個黑白相間的表格:

(element TR
  (if (= (modulo (child-number) 2)
	    0)
	…   ;even-row
	…)) ;odd-row

  最後還有讓你嫉妒心爆棚的特性,DSSSL 甚至可以把繼承的屬性值作為變數,在上面進行計算。

(element H1
  (make paragraph
	font-size: (+ 4pt (inherited-font-size))))

  不幸的是,DSSSL 同時具備了所有 Scheme 類語言的致命弱點:括號太多了。更糟糕的是,規範最終釋出時,認為其太過複雜的聲音不絕於耳,這讓瀏覽器開發者感到膽怯。DSSSL 標準包含了超過210項獨立的樣式屬性。

  這個團隊繼續建立了 XSL,用來定義文件的轉化,雖然依然讓人困惑,但是確實更流行一些。

 為什麼樣式表達到了終點

  CSS 並沒有包含父選擇符(一種用來定義包含特定子節點的節點樣式定義方法)。這個問題在 Stack Overflow 上頻繁地被問道,但事實證明這個特性的缺失事出有因。尤其在網際網路的早期,有一個重要的觀點被普遍認可,在文件被完全載入之前,頁面必須是可渲染的。換句話說,大家希望在構成頁面底部的 HTML 全部載入完成之前,就可以渲染頁面起始的 HTML。

  一個父選擇器意味著隨著 HTML 文件的載入樣式可能會有變化。像 DSSSL 這樣的語言,如果被完全實現,因為它們自己可以操作文件,所以在開始渲染時,頁面很可能是不可用的。

  第一個貢獻者 Bert Bos,在1995年3月提出了這個問題,並給出了一個工作良好的語言,他的提議中包含了“smiley”表情 :-) 的一個早期版本。

  這枚語言一定程度上來說是“物件導向的”:

*LI.prebreak: 0.5
*LI.postbreak: 0.5
*OL.LI.label: 1
*OL*OL.LI.label: A

  使用.來指定直接子節點,使用*來指定祖先節點。

  他的語言裡還有很酷的屬性,可以在樣式表中定義像超連結這樣的特性:

*A.anchor: !HREF

  在上例中,把超連結的HREF屬性設定為它的目的地址。像這種可以在樣式表中控制元素的行為的想法在多個提案中非常流行。在還沒有 JavaScript 出現的日子裡,並沒有什麼可以控制元素的方法,因此這些新的提案看上去是合理的。

  其中一個函式式的提案,也同樣包含類似的行為。這個提案由名為 C.M. Sperberg-McQueen 的紳士提出:

(style a
  (block #f)     ; format as inline phrase
  (color blue)   ; in blue if you’ve got it
  (click (follow (attval 'href)))  ; and on click, follow url

  他的語言同時還引入了content關鍵字,提供了一種在樣式表中控制 HTML 元素內容的方法。在 CSS 2.1 中也引入了這個概念。

 最大的可能

  在我開講 CSS 語言前身之前,還有另外一個語言值得一提,全都是因為它從某種程度上來說,是早期 Web 開發者夢寐以求的東西。

  它就是 PSL96,按照當年的命名約定,1996年版的“Presentation Specification Language”,從核心上看,PSL 與 CSS 很像。

H1 {
  fontSize: 20;
}

  而且它馬上變得更有趣了。例如,你不但可以基於元素所設定的尺寸(Width)來設定其位置,也可以基於瀏覽器渲染後的真實尺寸(Actual Width):

LI {
  VertPos: Top = LeftSib . Actual Bottom;
}

  注意你甚至可以使用元素的左鄰右舍作為約束條件。

  你還可以在樣式中使用邏輯表示式。例如,只對有href屬性的超連結應用樣式:

A {
  if (getAttribute(self, "href") != "") then
	fgColor = "blue";
	underlineNumber = 1;
  endif
}

  這種樣式可以擴充套件實現全部今天我們用樣式類做的事情。

LI {
  if (ChildNum(Self) == round(NumChildren(Parent) / 2 + 1)) then
	VertPos: Top = Parent.Top;
	HorizPos: Left = LeftSib.Left + Self.Width;
  else
	VertPos: Top = LeftSib.Actual Bottom;
	HorizPos: Left = LeftSib.Left;
  endif
}

  支援如此的功能或許真的可以實現完美拆分內容和樣式的程式碼。遺憾的是這門語言讓人望而生畏,畢竟太易於擴充套件了。這意味著對於不同的瀏覽器很可能實現會很不一樣。而且,它以學術界中的數篇論文發表出現,並沒有 www-talk 郵件列表上進行研討,要知道大部分的工作都是在這郵件列表裡確定的。因此這門語言並沒有被整合到主流的瀏覽器中。

 CSS 的元魂

  有一門語言,直接引出了 CSS,至少從名字上是這樣,它的名字是 CHSS(Cascading HTML Style Sheets),於1994年由 Håkon W Lie 提出

  與很多優秀的點子一樣,這個提案的原始版本看上很瘋狂。

h1.font.size = 24pt 100%
h2.font.size = 20pt 40%

  注意在行尾的百分比,表示當前這個樣式表對這個值有多大的權重。例如,如果之前的樣式表定義h2的字型大小為30pt,有60%的權重,而加上這個樣式表h2 20px的40%,根據權重將這兩個值合到一起大概就是26pt。

  在基於文件的 HTML 頁面的年代裡,可以想象該提案的結果了,畢竟妥協的設計是沒法在我們面向應用的世界裡工作的。不過,它已經具備了最根基的思想——樣式表是可以疊加的。換句話說,在它的眼裡同一個頁面可以同時應用多個樣式表。

  這初步的構想被認為是很重要的,是因為為使用者提供了一種可以控制文件展現的方法。頁面自己有一個樣式表,Web 使用者也可能有自己的樣式表,這兩個樣式表一起影響頁面的渲染。支援多個樣式表被看做是維護了 Web 的個人自由,並不是提供的一種方式給開發者(他們仍然手工地編寫單獨的 HTML)。

  使用者可以控制給到頁面作者建議樣式多少權重,就如提案中的一個 ASCII 圖表表示的那樣:

	   User                   Author
Font   o——x———————o 64%
Color  o-x—————————o 90%
Margin o——————x———o 37%
Volume o————x—————o 50%

  和其他提案相似的,它所包含的一些特性並沒有帶到 CSS 中,至少十多年來都沒有。例如,這個提案中允許基於使用者的環境編寫表示式:

AGE \> 3d ? background.color = pale\_yellow : background.color = white
DISPLAY\_HEIGHT \> 30cm ? http://NYT.com/style : http://LeMonde.fr/style

  如未來科幻描述的那樣,瀏覽器很可能知道內容的中的哪些部分與你更相關,於是可以針對你顯示更大的字型:

RELEVANCE \> 80 ? h1.font.size \*= 1.5

 接下來就是你所知的 CSS

Microsoft 對開源標準的貢獻是絕對的,尤其是在網際網路領域。—— John Ludeman 1994

  Håkon Lie 簡化他的提案,並與 Bert Bos 合作,在1996年11月釋出了 CSS 規範的第一版。最終他把 CSS 的誕生寫入到了自己的博士論文中,也就是這個文件幫助我寫下了這篇文章。

  與其他提案相比,CSS 最值的一提的就是它的簡單。它易於解析、編寫和閱讀。這種例子在網際網路的歷史裡屢見不鮮。最終取勝的技術是那些初學者容易入門的,而不是那些給專家用的。

  這波變革具有很大的偶然性,CSS 就可以作為一個活生生的例子。例如,只有上下文選擇器(body ol li)得以支援,因為 Netscape 已經有方法可以移除超連結內圖片的邊框,而且看上去實現有所主流瀏覽器的功能更重要些。這個功能本身就大大拖延了 CSS 的實現,畢竟那個時候大部分瀏覽器在解析 HTML 的時候都不會把 tag 儲存成一個棧。因此解析器需要重新設計才能完整的支援 CSS。

  有如此的挑戰(外加非標準標籤定義樣式被大量使用)導致1997年以前 CSS 都沒法用。直到2000年3月才有瀏覽器完整支援它。每個工程師都會告訴你,直到最近幾年瀏覽器的實現才真正與標準保持一致,這裡 CSS 首次釋出已經過去超過15年。

 終極大 Boss

如果 Netscape 4 忽略在 <body> 上的 CSS 規則,然後在每個結構化的元素之前新增隨機數量的空格;如果 IE4 正確處理了 <body>,但 padding 上卻全是問題。那寫什麼樣的 CSS 才是安全的?有的開發者直接選擇完全不用 CSS,還有的可能就為 IE4 寫一個樣式表,再為 Netscape 4 寫一個,以彌補後者犯的錯。 — Jeffrey Zeldman

  Internet Explorer 3 以釋出時帶著對 CSS 的支援(有可能有點糟糕)而聞名遐邇。為了與之競爭,網景公司決定 Netscape 4 也應該支援這門語言。與其把寶壓在第三方語言上,考慮到 HTML 和 JavaScript,決定實現方案應該是將 CSS 轉化為 JavaScript 執行。而且,確定 Web 開發者也可以訪問這個“JavaScript 樣式表“中間語言。

  語法直接使用 JavaScript,然後增加一些樣式相關的 API:

tags.H1.color = "blue";
tags.p.fontSize = "14pt";
with (tags.H3) {
  color = "green";
}

classes.punk.all.color = "#00FF00"
ids.z098y.letterSpacing = "0.3em"

  你甚至可以定義函式,每次解析到對應的標籤時就會執行一次該函式:

evaluate\_style() {
  if (color == "red"){
	fontStyle = "italic";
  } else {
	fontWeight = "bold";
  }
}

tag.UL.apply = evaluate\_style();

  我們應該簡化樣式和指令碼之間的分界線無疑是合理的,在如今的 React 社群這種觀點又開始復現了。

  當時,JavaScript 自己雖然是一門比較新的語言,但通過一些反向工程,Internet Explorer 已經在 IE3 中以 JScript 的方式支援它了。更大的問題在於社群已經團結在 CSS 周圍了,而且,彼時的 Netscape 已經被標準社群視作小霸王,當它向標準委員會提交 JSSS 時,委員會充耳不聞。三年後,Netscape 6 也放棄了對 JSSS 的支援,而且自己也差不多要安樂死了。

 最大的可能

  鑑於 W3C 引起的一些輿論壓力,2000年 Internet Explorer 5.5 釋出,幾乎完全支援 CSS1。當然,如大家所知,瀏覽器 CSS 的實現 Bug 無限多,十年以來都很難用。今天現狀已經有了戲劇性的改善,真正實現了開發者的夢想,編寫一次程式碼,有信心程式碼可以在瀏覽器之間一樣地工作。

  看了這麼多,我個人的結論就是,無論這些決定是武斷還是有理有據的,都決定著我們目前的工具是什麼樣子的。如果 CSS 當時的設計只是為了滿足1996年的需求,那可以肯定的是,20年後的今天我們做事情的方式至少是有些不一樣的。

  原文:https://eager.io/blog/the-languages-which-almost-were-css/

相關文章