標籤:

扒一扒 CSS 語言的誕生史

原文:eager.io/blog/the-langu

實話說,在過去這一年,這已經成為我好心情的固定來源。即不斷地告訴一波波想要像在 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)n

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

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

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

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

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

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

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

Viola 以及原始瀏覽器之戰

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

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

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

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

(BODY fontSize=normaln BGColor=whiten FGColor=blackn (H1 fontSize=largestn BGColor=redn FGColor=white)n)n

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

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

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

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

Web 之前的樣式表

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

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

你要知道 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>n <docdesc>n <charlist>n <font size="12pt" bckcol="white" fontcol="black">n </charlist>n </docdesc>n <e-i-c gi="h1"><font size="24pt" bckcol="red", fontcol="white"></e-i-c>n <e-i-c gi="h2"><font size="20pt" bckcol="red", fgcol="white"></e-i-c>n <e-i-c gi="a"><font fgcol="red"></e-i-c>n <e-i-c gi="cmd kbd screen listing example"><font style="monoser"></e-i-c>n</outspec>n

你搞不清楚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 H1n (make paragraphn font-size: 14ptn font-weight: bold))n

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

(define (create-heading heading-font-size)n (make paragraphn font-size: heading-font-sizen font-weight: bold))nn(element h1 (create-heading 24pt))n(element h2 (create-heading 18pt))n

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

(element TRn (if (= (modulo (child-number) 2)n 0)n ;even-rown )) ;odd-rown

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

(element H1n (make paragraphn font-size: (+ 4pt (inherited-font-size))))n

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

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

為什麼樣式表達到了終點

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

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

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

這枚語言一定程度上來說是「面向對象的」:

*LI.prebreak: 0.5n*LI.postbreak: 0.5n*OL.LI.label: 1n*OL*OL.LI.label: An

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

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

*A.anchor: !HREFn

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

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

(style an (block #f) ; format as inline phrasen (color blue) ; in blue if youve got itn (click (follow (attval href))) ; and on click, follow urln

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

最大的可能

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

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

H1 {n fontSize: 20;n}n

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

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

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

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

A {n if (getAttribute(self, "href") != "") thenn fgColor = "blue";n underlineNumber = 1;n endifn}n

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

LI {n if (ChildNum(Self) == round(NumChildren(Parent) / 2 + 1)) thenn VertPos: Top = Parent.Top;n HorizPos: Left = LeftSib.Left + Self.Width;n elsen VertPos: Top = LeftSib.Actual Bottom;n HorizPos: Left = LeftSib.Left;n endifn}n

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

CSS 的元魂

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

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

h1.font.size = 24pt 100%nh2.font.size = 20pt 40%n

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

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

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

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

User AuthornFont o——x———————o 64%nColor o-x—————————o 90%nMargin o——————x———o 37%nVolume o————x—————o 50%n

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

AGE > 3d ? background.color = pale_yellow : background.color = whitenDISPLAY_HEIGHT > 30cm ? http://NYT.com/style : http://LeMonde.fr/stylen

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

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

接下來就是你所知的 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 忽略在 上的 CSS 規則,然後在每個結構化的元素之前添加隨機數量的空格;如果 IE4 正確處理了 ,但 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";ntags.p.fontSize = "14pt";nwith (tags.H3) {n color = "green";n}nnclasses.punk.all.color = "#00FF00"nids.z098y.letterSpacing = "0.3em"n

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

evaluate_style() {n if (color == "red"){n fontStyle = "italic";n } else {n fontWeight = "bold";n }n}nntag.UL.apply = evaluate_style();n

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

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

最大的可能

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

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

微信搜索「前端外刊評論」關注我們!

推薦閱讀:

面試系列之四:你真的了解React嗎(下)Flux與Vuex的差異以及Webpack的工作原理
關於Date對象的前端面試題
如何成為一個卓越的前端開發工程師
監控平台前端SDK開發實踐
怎麼修改 HTML 中上傳選擇框(input file)的樣式,以達到同時解決 FireFox 下遮蓋其他元素問題,以及中英文等不同語言下右邊按鈕寬度不同導致影響布局問題?

TAG:CSS | 前端开发 |