07月08, 2016

【译】那些差一点成为了 CSS 的语言

原文:http://www.zcfy.cc/article/709

事实上,对我来说在过去的一年里,一直有一个快乐源泉是,不断告诉人们,如果你们想同 TeX、Microsoft Word 以及其他常见的文本处理环境那样地控制你们的网页文档:“对不起,你搞砸了”。

—— Marc Andreessen(Netscape 公司创始人、硅谷风投家,译者注)1994

当 1991 年 Tim Berners-Lee 宣布 HTML 问世时,没有给网页写样式的办法。如何对 HTML 标签进行渲染是由浏览器根据用户的喜好设置来决定的。然而,似乎创建一个标准方法让网页被“建议”如何渲染比按照设置的风格进行渲染要更好。

然而 CSS 直到五年之后才出现,十年之后才完全实现。这段时期是充满着创新和紧锣密鼓地工作的时期,这导致不只一两种相互竞争的样式方法出现,其中一些简单到差一点成为标准。

由于这些语言显然今天不常用,我发现想象这个世界可能出现的另一种样子很令人着迷。甚至更令人惊讶的是,碰巧许多其他的选择包含着一些开发者会希望在现在的 CSS 中看到的特性。

第一个提案

早在 1993 年,Mosaic 浏览器还没有发布 1.0 版本,那些已存在的浏览器单独处理 HTML。没有一种方法能够为 HTML 指定样式,意味着一旦浏览器决定了一个 <h1> 看起来应该是什么样的,你看到的就是那个样子。

在那一年的六月份,Robert Raisch 向 www-talk 邮件组提出了一个提案,希望创建“一个易于解析的格式来为 Web 文档提供一个样式信息”,这后来被叫做 RRP。

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

你不知道上面的代码在做什么情有可原。在 gzipping 之前的时代,网络连接的速度在 14.4kb 左右,让这个新格式的内容尽可能简洁是有意义的。这个特别的规则将 font famliy (fa)设置为 helvetica (he),并将 font size (si) 设为 18 points。

这个提案里面缺失一些有趣的内容,没有提到数值单位,所有的数值基于它们的上下文来解释(例如 font size 的单位总是 points)。这可以归因于 RRP 被设计为更多地作为一组“对浏览器渲染的提示或者建议”,而不是一个规范。这被认为是必要的,因为同样的样式表需要支持命令行模式的浏览器(比如 Lynx),以及正变得越来越受欢迎的图形化浏览器。

Lynx browser screenshot

逐渐地,RRP 包含了一个方法来指定一个分纵栏的布局,而这个特性直到 2011 年 CSS 才支持了。例如,分三列,每一列宽度为“80 单位”看起来如下:

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

这个解析起来有点难,但可能比起 white-space:nowarp 来也糟不到哪里去。

值得注意的是,RRP 不支持任何我们现在样式表中所拥有的“层级”。给定的文档在一个时间点只能有一个有效的样式,这对于指定文档的样式来说是一个符合逻辑的思考方式,尽管今天我们有了 CSS,已经不习惯那样思考了。

Macro Andressen (Mosaic 的创建者,Mosaic 本来有望成为最流行的浏览器)注意到了 RRP 提案,然而 Mosaic 从未实现它。相反地,Mosaic 快速转向(某种意义上来说,悲剧地)使用 HTML 标签来定义样式的路线上,引入了一些标签例如<FONT>以及<CENTER>

Viola 以及早期浏览器之争

那么为什么你不仅仅实现众多样式表提案其中的一个。如果选择对了,问题基本就解决了。

因此我要告诉人们,“好吧,你得学习这个语言来写你的文档,然后你得学习那个语言来实际决定你的文档如何展现。”噢,他们会喜欢的。

— Marc Andreessen 1994

与流行的看法相反,Mosaic 不是第一个图形浏览器,ViolaWWW 才是。ViolaWWW 是一个图形浏览器,由魏培源在四天之内写出来的。

Viola browser screenshot

魏培源创建了一个样式表语言,它支持一种格式的嵌套结构,而嵌套结构在今天的 CSS 里我们已经习惯了:

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

在这个例子里,我们应用一个 color 样式到 body 然后特别地为出现在 body 中的 H1 指定新的样式。PWP 使用一个括号系统如同今天一些开发者喜欢使用的 Stylus 和 SASS 语言使用一个缩进系统那样来处理嵌套,而不是用重复的选择器来处理嵌套。这令 PWP 的语法潜力至少在一方面比最后成为了网页通用语的 CSS 更好。

PWP 也因为引入了外部样式表机制而得到关注,这个机制我们今天依然在使用:

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

不幸地是,ViolaWWW 主要工作在 X Windowing 系统 上,这个系统只在 Unix 系统上流行。当 Mosaic 被移植到 Windows 上,它很快将 Viola 远远甩在身后。

在 Web 出现之前的样式表

HTML 是一种只在计算机科学家中受欢迎的东西。是的,它表达了一个文档的底层结构,但是文档不仅仅只是结构化的文本数据,它们有视觉上的影响。HTML 完全消除了一切视觉创造力,而文档设计本应该包含视觉创造力。

— Roy Smith 1993

一个语言对表达文档样式的需求远早于互联网存在。

正如你可能知道的,HTML 源自于在互联网之前出现的语言,这个语言叫 SGML。在 1987 年,美国国防部决定研究 SGML 是否能够用来方便地存储和传输他们需要处理的大量文档。如同其他“好”的政府工程,他们没有把时间浪费在起名字上。那个团队最初被叫做计算机辅助后勤支援队,后来改名计算机辅助采集与后勤支援队,然后最终叫做连续采集与生命周期支持计划。不管叫哪一个,简写都是 CALS。

CALS 团队创建了一个语言来给 SGML 文档添加样式,这个语言叫做 FOSI,这是一个缩写,无疑是四个单词的组合(只是年代久远不知道是哪四个单词了——译者注)。他们为这个语言发布了一个规范,这一规范汇集了一切不可理解的内容。它包含一个在网络上永远存在的我最喜欢的无意义的信息图表

互联网的一个不可侵犯的原则是:如果你能证明过程中有人错了,就能进一步完善。在 1993 年,魏培源的提案仅仅提出四天之后,Steven Heaney 提出(http://1997.webhistory.org/www.lists/www-talk.1993q4/0295.html) 何必“重复发明轮子”,我们最好使用 FOSI 的一个变种来为 web 添加样式。

一个 FOSI 文档自身用 SGML 编写,这实际上是一个有点逻辑的设计,因为 web 开发者已经熟悉 SGML 的变体 HTML。一个 FOSI 文档的例子如下:

<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 风格的语言和更多其他声明式语言之间爆发了战争。魏培源将自己的语法描述成 LISP 风格,但不久以后,一个真正的 LISP 语言变体进入了历史舞台。

图灵完备的样式表

由于它的复杂性,FOSI 实际上被认为只是格式化文档的一个临时方案。长远的计划是创建一个基于函数式编程语言 Scheme 的语言,这可以实现你所能想象到的最强大的文档转换。这个语言被叫做 DSSSL。它的作者 Jon Bosak 说:

把 DSSSL 与脚本语言放在一起是一个错误。是的,DSSSL 是图灵完备的。是的,它是一个编程语言。但是一个脚本语言(至少我使用这个词的方式)是过程式的,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))))

但不幸的是,如同所有 Scheme 类的语言一样,DSSSL 有致命的缺陷:太多的括号。此外,当它最后发布的时候,它可以说是太完备了,以至于吓到了浏览器开发者。DSSSL 规范包含超过 210 个独立的可以定制样式的属性。

DSSSL 团队接下来创建了 XSL,一个用来转换文档的语言,它也很令人困惑,但确实要受欢迎一些。

为什么一些样式表脱颖而出?

CSS 没有包含父级选择器(基于子元素包含的样式给父元素设置样式的一种方法)。这个事实长期让很多人在 Stack Overflow 发表文章表示不满,然而,事实证明,它缺少这一特性是有一个很好的理由的。特别是在互联网的早期,让网页在完全加载完成之前就能够被渲染,是非常重要的。换句话说,我们想要在浏览器在下载完页面底部的 HTML 之前就能够渲染前面已经下载好的 HTML。

实现父类选择器将意味着样式得在 HTML 文档一边加载的同时一边更新。类似 DSSSL 这样的语言更惨,因为他们可以在文档本身上执行操作,这样,如果在文档加载完之前就开始渲染,它们的很多操作将不可用。

Bert Bos 在 1995 年 3 月第一个提出父类选择器问题,并提交了一个可行的语言提案。他的提案还包括一个早期版本的微笑表情符 :-)。

这个语言自身在语法上有点“面向对象”:

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

使用 . 来指定直系后代,使用 * 来指定祖先。

他的语言也包含一个很酷的属性来通过样式表定义链接元素的特性如何作用:

`*A.anchor: !HREF`

在上面的例子里,我们指定链接元素的目标是它自己的 HREF 属性值。链接元素的行为应该能够被控制,这个想法在许多提案中流行。在 JavaScript 出现之前的年代,没有别的办法控制这类行为,因此这一想法在这些新提案中出现是合乎逻辑的。

一个函数式提案,在 1994 年被一个叫做 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 年的版本,按照时间规范命名为 PSL96, 它的全称是“表述规范语言(Presentation Specification Language)”。在它的核心,PSL 看起来像 CSS:

H1 {
  fontSize: 20;
}

尽管如此,它很快变得更有趣。例如,你可以不仅仅基于 Width 指定的大小表达元素的位置,,还可以通过浏览器渲染的实际(Actual Width)大小来表达:

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

注意到你也可以使用元素的左邻来作为约束。

你还可以加逻辑表达式到你的样式中。例如只为拥有 href 属性的 a 元素指定样式:

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),它被 Håkon W Lie 提出于 1994 年。

如大多数好主意,原方案是相当疯狂的:

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

注意到在规则末尾的百分比,这个百分比指明当前样式设置的值有多少“权重”。例如:如果前一个样式定义了 h2 的 font size 为 30pt,权重 60%,然后这个样式定义了 h220px 40%,这两个值将被基于他们的权重结合在一起,得到大约 26pt

现在相当清楚这个提案是如何在基于文档的 HTML 网页时代被提出的,可是我们应用导向的世界容不下基于妥协的设计。然而,它确实包含了样式表应当级联的基本理念。换句话说,它能够允许多个样式表被应用到同一个网页。

它那个计算公式被认为是重要的,因为它让最终用户能控制他们所能看到的结果。原始网页将有用一个样式表,而 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`

你知道接下来发生了什么

微软绝对致力于开放标准,尤其是在互联网上。

— John Ludeman 1994

Håkon Lie 继续简化他的提案,然后,在 1996 年 12 月与 Bert Bos 一起发布了 CSS 的第一版规范。最终他将创立 CSS 写进他的博士论文,这篇论文对我写成这篇文章超有帮助。

与其他许多提案相比较,一个值得注意的事实是 CSS 的简单性。它容易解析,容易编写也容易阅读。和互联网的历史上的许多其他的例子一样,让新手最容易上手的技术,往往会最终战胜那些更强大的给专业人员使用的技术。

它本身给我们一个提醒,提醒我们创新多少是一种偶然。例如,支持上下文选择器(body ol li)仅仅是因为 Netscape 浏览器已经有一个方法来移除超链接图片的 border,而其他流行的浏览器必须要能做到。这个功能本身的实现经过了比较长的时间,是因为在那个时候大多数浏览器都没有在解析 HTML 的时候维持一个标签栈,这意味着要实现这个功能,解析器得重新设计。

像这样的挑战(以及广泛传播的用非标准 HTML 标签来定义样式)意味着 CSS 不可用直到 1997 年,而在 2000 年 3 月之前,都没有任何一个浏览器完全支持 CSS 所有特性。正如任何开发人员可以告诉你,浏览器支持没有任何地方接近标准兼容直到几年之前,在 CSS 发布了 15 年之后。

最终 Boss

如果 Netscape 4 忽略应用到 <body> 元素的 CSS 规则并添加随机数量的空白到你网页的每一个结构元素,而如果 IE 4 处理 <body> 正确但是搞砸了 padding 属性,什么样的 CSS 可以安全编写?一些开发者选择完全不写 CSS。另一些开发者写一份样式表来补 IE 4 的缺,另一份不同的样式表来填 Netscape 4 的坑。

— Jeffrey Zeldman

IE 3 高调地宣称支持 CSS(某种程度上有点可怕)。为了竞争,Netscape 4 也考虑了 CSS。但它决定通过将 CSS 转换为 JavaScript 来执行它,而不是也支持这个第三(考虑到 HTML 和 JavaScript)语言。甚至更进一步,它决定让这个“JavaScript Style Sheet”中介语言能够被 web 开发者访问(https://web.archive.org/web/19970709133056/http://home.netscape.com/comprod/products/communicator/guide.html )。

这个语法是直接的 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 自身也还是一个非常新的语言,但是通过一些逆向工程,IE 已经添加 JavaScript 支持到 IE3(作为“JScript”)。更大的问题是社区已经团结起来支持 CSS,而 Netscape,在那个时间,被许多标准社区视为恶霸。当 Netscape 提交 JSSS 到标准委员会,没有人理会它。三年后,Netscape 6 放弃支持 JSSS,这项技术(基本上)悄无声息地消亡了。

可能是什么

多亏了 W3C 组织的一些公开羞辱,在 2000 年 IE 5.5 几乎完全支持了 CSS 1。当然,如我们所知道的,至少在接下来的十年里浏览器的 CSS 实现非常粗糙且难以使用。幸运地是,今天这一状况大幅度改善了,让开发者写一份代码在不同的浏览器下得到(几乎)同样的结果的梦想成为现实。

我个人从这些中得出的结论是领悟到影响我们现有工具的许多决定是既有偶然性也有必然性的。如果 CSS 被设计出来时只是为了满足 1996 年的约束,那么可能那时它已经给了我们一个承诺 20 年后会做一些有点不同的事情。

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

本文链接:https://www.h5jun.com/post/the-languages-which-almost-became-css.html

-- EOF --

Comments