真・懒

订阅 Twitter GitHub 联系

ppk on JavaScript 第二章:背景(二)

这一章讲的太多是开发观念,为其他书所不或很少提及的。所以我基本上全译而不是精简笔记。以后的技术章节我可能只记录一些需要注意的问题,否则我就是在翻译这本书而不是作读书笔记了。因为是全译,所以有点长,故这一章还未完结,请耐心等待第三部分,如果您真的在等的话。以下是正文:

分离行为和表现

第三层分离,行为和表现,恐怕要比前两种分离更复杂。别期待我会给您清晰的答案,该分离还属于当代 JavaScript 编程领域中尚未能够明确化到容易学习和理解规则的范围内。现在我们的问题比答案还多。

最基本的问题是:哪种效果您应该定义到 CSS 中,哪种应该定义到 JavaScript 中?尽管表现应定义在 CSS 中而行为应定义在 JavaScript 中的问题很明显,但是它们存在一个交错的灰色地带,某些效果是属于表现还是行为并不清晰。

下拉菜单: hover 还是 mouseover/out

下拉菜单的目的是,当用户鼠标悬停时显示次级菜单,移开时隐藏,从远古时代(精确地算的话,1997 年)起我们就用 JavaScript 获得这种效果。那时我们没有其他选择,如果您要下拉菜单,您只能用 JavaScript。

尽管如此,时代的进步让您现在可以使用 CSS 来实现,无须任何 JavaScript:

<li>
  <a href="#">News</a>
  <ul>
    <li><a href="#">Press Releases</a></li>
    <li><a href="#">News Articles</a></li>
    ... ...
  </ul>
</li>
// in .css file
li ul {
  display: none;
}
li:hover ul {
  display: block;
}

li 内的 ul 次级菜单,初始隐藏(display: none),当鼠标悬停 li 上,通过 li:hover,次级菜单即可展示出来(display:block)。

哪种方案更可取?大部分人将选择 JavaScript 因为 IE6 及更早版本并不支持 li:hover

但是,这个现实的答案并未能回答到根本。我们来假设下当所有浏览器都完美支持 :hover 时(别怀疑,IE7 已经实现),CSS 和 JavaScript 方案都兼容浏览器了。我们选择谁?

CSS 代码比 JavaScript 简单多了,理由当然有其优越性。我们会在后面的章节看到,JavaScript 鼠标移开事件,特别令人憎恶,要玩得起它,必须有一手过硬的技术。相比之下,CSS :hover 选择器……真的跑起来了。

此外,CSS 可以在 JavaScript「残掉」的时候继续工作。不过,这并不意味着 CSS 必定比 JavaScript 更具无障碍。

有些人用键盘代替鼠标。他们使用按键(一般是 Tab 键)捕获 HTML 元素(通常是连接),然后按回车来激活已捕获元素。键盘用户没法使用 CSS 下拉菜单,因为 li:hover 仅仅是一个鼠标选择器,没有对应的键盘。此外,没法捕获 li 因为键盘仅能捕获连接,按钮和表单域。

反之,JavaScript 能通融键盘用户。因此 CSS 下拉菜单未必本来就比 JavaScript 版更无障碍。键盘用户更喜欢 JavaScript 版本下拉菜单。

实际上,它们都有各自的问题。下拉菜单是表现还是行为?该效果只在用户行为下才触发,这表明它是行为。另一方面,该效果是关于展现次级菜单的,这表明它是表现。

尽管个人倾向于行为答案,但问题允许有多个答案,每个 web 开发者都应招到自己的解决方案,忽略这些理论假设吧。

总之,CSS :hover 方案比起 JavaScript mouseover/out 的来明显的得分点只在于更少的代码和维护。但这是 CSS 方案比 JavaScript 方案好的充分理由吗?还是要取决于个人的选择。

相同效果 vs. 相似效果

在下拉菜单中,您需要给每个被选中的 li 部署相同的效果。「如果 li 内有一个嵌套的 ul,当鼠标悬停 li 时显示它」。如我们所见,这很容易折合归纳成以下两行 CSS:

li ul {
  display: none;
}
li:hover ul {
  display: block;
}

太简单了,因为所有的 li 效果都一样。

现在我们来个不一样的例子:a 鼠标悬停。当用户鼠标停在图片上,图片改变,鼠标移开,图片变回原来的。

之前,所有的鼠标悬停效果都用 JavaScript 来实现,这是传统。JavaScript 鼠标悬停效果是最受欢迎的宠物自从 1996 年诞生以来,并且是 WWW 中被拷贝最多的脚本。因为已经存在银河沙数的脚本例子,没人移植到 CSS 上。

即使如此,理论上,创建 CSS 鼠标悬停效果是可以的:

<a href="somewhere.html" id="somewhere">Somewhere</a>
<a href="somewhere_else.html" id="somewhere_else">Somewhere else</a>
a#somewhere {
  background-image: url(pix/somewhere.gif);
}
a#somewhere_else {
  background-image: url(pix/somewhere_else.gif);
}
a:hover#somewhere,
a:focus#somewhere,
a:active#somewhere {
  background-image: url(pix/somewhere_hover.gif);
}

a:hover#somewhere_else,
a:focus#somewhere_else,
a:active#somewhere_else {
  background-image: url(pix/somewhere_else_hover.gif);
}

您会注意到每个鼠标悬停效果都需要两条 CSS 语句,一条定义普通状态,另一条定义鼠标悬停状态。原因很简单:每个连接都有起独自的普通状态和鼠标悬停状态图片,这些不同的图片需要在 CSS 中定义。

在这个案例中我们不能为所有的连接部署相同的效果,但它们效果很相似。所有的连接都会在鼠标悬停时改变图片,但每个连接都需要属于其自身的图片集合。每添加一条新连接我们都必须增加两条 CSS 语句,而且必须是手工的。无法改变的事实是,一个单纯的 CSS 鼠标悬停很快就会变成维护地狱,尤其是在几打连接上使用时。

这只不过是一条常规罢了。当您需要给一些元素部署相同的效果时 CSS 特别高效,就像上面的下拉菜单例子,然而在一些类似的效果上,CSS 显得低效,就像刚才那个鼠标悬停例子。

而 JavaScript 允许您写一个管理您不愿意计算有多少的连接的脚本:

<a href="somewhere.html" id="somewhere"><img src="pix/somewhere.gif" /></a>
<a href="somewhere_else.html" id="somewhere_else"
  ><img src="pix/somewhere_else.gif"
/></a>
function initMouseOvers() {
  var links = document.getElementsByTagName("img");
  for (var i = 0; i < links.length; i++) {
    var moSrc = links[i].src.substring(0, links[i].src.lastIndexOf("."));
    moSrc += "_hover.gif";
    links[i].moSrc = moSrc;
    links[i].origSrc = links[i].src;
    links[i].onmouseover = function () {
      this.src = this.moSrc;
    };
    links[i].onmouseout = function () {
      this.src = this.origSrc;
    };
  }
}

起初,这个方案需要比 CSS 更多的代码行,但它的强大远远抵消这个坏处。如果您需要另一个悬停,您只需把连接加上,ok,它自己自动工作了。

因此,当创建这些类似但不相同的效果时,JavaScript 方案更高效。当您面临选择 CSS 还是 JavaScript 时请记得这点。

现在还不可能得出一个关于表现和行为分离绝对的结论,有待更多的调研,现在我更希望您花些时间考虑一下这个问题,无论您是否遭遇类似问题,一旦您发现有更好的规则,分享出来吧!说不定您就是第三种分离的规则领导者。

无障碍概览

我已经不止一次提及无障碍了,而且是通常伴随着讽评普遍缺乏它的 JavaScript 开发中。是时候来个概览了,同时给我们的例子来个无障碍测试。

什么是无障碍

无障碍是指在大部分情况下,尤其在用户不可改变的某些条件,比如视力衰减,或者使用一个不支持(足够)JavaScript 的浏览器,您的网页依然可以使用。

在 JavaScript 情景中「依然可以使用」是什么意思?它表示,用户必须可读站点内容,使用导航,并且能进行一般的操作比如提交表单。少不了,也不多。

在任何情况下都完美无障碍是一宗大案,我们可以看到,在著书的这段时期,屏幕阅读器某些繁杂的技术问题更是让完美是难以企及。但是,我们应该从现在就开始,因为我们能够,所以我们就应该解决某些无障碍问题。

无脚本

最明显不过的无障碍问题,任何人都可指控的,就是某些浏览器不支持(足够)JavaScript. 您小心翼翼打造的脚本这些浏览器并不买帐,用户看到的是未经脚本处理过的页面。

编写无障碍脚本的天字第一号准则是,理所当然,是确保没有 JavaScript 页面功能依然。稍后我们会继续讨论这些骇人听闻的细节,但现在我更想先讨论两条不太为人所知但很严重的无障碍问题。

无鼠标

某些用户不用鼠标,使用击键来操纵页面。有的用键盘,有的用小物件比如屏幕触摸键盘,或者是其他模拟键盘的设备。

使用击键代替鼠标的原因可能有多种。我自己偶尔情况下会使用键盘来实现某些快速操作,显然这是我自己的选择。我可以用鼠标,但有时我很懒。

但,其他用户,可能只能一直使用键盘,有些条件是他们改变不了的。最可能的场景是,这些用户的手(部分)残障或者不能精确地移动鼠标。击键给他们提供了另外更好的选择,除非 JavaScript 开发者忽视了他们。

通常这些用户的浏览器能够执行先进的脚本,并且他们能够看到屏幕上的反馈结果。所以并没有什么大问题,除非脚本对键盘输入无反应。

为了让您的脚本兼容键盘,除了鼠标事件之外,您应该增加定义额外的事件。比如,如果您使用鼠标悬停事件就应该使用聚焦(focus)事件,因为没有鼠标就不会有鼠标悬停事件的发生。

屏幕阅读器

有些人不能使用普通的浏览器。比较典型的是盲人或者视力严重损害得阅读不了电脑屏幕的任何东西。反而,他们需要一个能够大声朗读页面内容的程序。这些程序就是屏幕阅读器。

JavaScript 开发者通常认为屏幕阅读器根本就是不支持脚本的浏览器,因此屏幕阅读器的无障碍只不过是无脚本用户的扩展而已。无 JavaScript 的页面只要工作,盲人用户就不会遭遇有关 JavaScript 的问题(尽管大量的其他问题像缺少 alt 依然存在)。

不幸的是,这是个神话。大部分屏幕阅读器都运行在已有的有时是 IE 有时是 Mozilla 的浏览器上。因为它们使用普通的浏览器来获取数据,它们也支持 JavaScript。当基础的浏览器碰到脚本,它一般会试着去执行。

似乎是个好消息。如果屏幕阅读器也支持 JavaScript,那就没有问题了,对不?很不幸,有两大严重的问题:屏幕阅读起的线性性质,及其混乱的事件支持。

屏幕阅读器只支持线性处理页面。当一个视力明朗的用户使用一个图形浏览器访问网站,她简单一瞥就能获得网站的总览。左边那些古怪颜色可能就是主导航,中间的文本就是主要内容,等等。因此用户能够迅速地决定哪些是她需要的,并于这些交互。此外,一旦脚本改变了文档结构或者表现,她的眼睛能被吸引过去并能揣测其含义。

但屏幕阅读器用户不会如此。屏幕阅读器从头到脚把页面读个遍,一般是按照源代码的顺序。这是一个严重的问题,对当代 JavaScript 来说。就算所有屏幕阅读器完美支持 JavaScript,我们怎样才能提示其用户页面部分已改变,尤其是屏幕阅读器已经读过这些部分?

拿表单验证(本书有几个例子贯穿始终,这是其中一个)的例子来说,假设它运行在一个完美支持 JavaScript 的屏幕阅读器中,允许用户填写和提交表单。因为 JavaScript 工作完美,一旦用户激活提交按钮,表单验证程序就会运行。如果有错,脚本就会提示「出错」,并停止提交。

问题在于,屏幕阅读器可能(但不总是)会听到提示并得到问题的某些暗示,但是阅读器已经读过表单域,他不会听得到错误。

为了帮助屏幕阅读器用户,表演验证回滚到表单的开头:

<form id="startOfForm"></form>
if (!validForm) {
  alert("Errors have been found");
  location.hash = "#startOfForm";
}

这对视力未损或者已损的用户都有好处,他们都会觉察到返回表单的开头,所以能够以其方式修正错误。但是,需要注意的是,这对视力良好的用户来说是一个额外的特色,但对视力有损的用户来说是一个绝对的必须!

屏幕阅读器和事件

不幸的是,屏幕阅读器的事件支持非常混乱与无序。理论上您希望它们能支持界面事件如聚焦和模糊,但不支持鼠标悬停与离开,因为屏幕阅读器用户使用键盘(或者等价的设备)来输入。

更不幸的是,有些屏幕阅读器厂商不管怎么还是把鼠标事件引入。理由是,大部分网站只使用鼠标事件因为制作者永远不考虑键盘的可访问性。厂商们需要他们的程序能够正确处理这些页面,因此增加鼠标事件。当然这会产生严重的问题,当一个关注无障碍的 web 开发者需要小心区分屏幕阅读器和图形浏览器来给予不同的处理。

我不想继续讨论细节了,它们让您迷惑(我也是),屏幕阅读器的事件支持在新版本发布时可能会改变。假如您不能预测屏幕阅读器的事件支持,那就罢了吧。

现实情形太差了,我担心目前屏幕阅读器的无障碍并不会有更新换代的革命性发展。Derek Featherstone无情但正确的结论是:「我们可以处理 JavaScript 的开或关,但不能处理中间状态」,因此他感到旧屏幕阅读器让用户选择完全禁止 JavaScript 的方式更好。如果这样,他们会回到一个没有脚本的页面,这个问题我们可以处理的。

此时,根据我们对屏幕阅读器的 JavaScript 支持的认知程度,我只能同意 Derek Featherstone,尽管是勉强的。无脚本屏幕阅读器比启动了脚本的屏幕阅读器更好照顾。

无障碍和可用性

回到起点:任何网页都无障碍在浏览器不支持(足够的)JavaScript 的情况下。我们来讨论一些通用的规则。

我们应该三思 JavaScript 的目的,就是增加网站额外的一层可用性。一旦 JavaScript 失效,网站的可用性必经会受损害,整层都消失了。但该层的缺席不应该损害页面最基本的无障碍。

拿合用表单(另一个例子)来说。当脚本运行时,毫无疑问用户会看到他所需要的特定表单字段。当脚本不能执行时,用户会看到他可能需要的所有表单字段。从可用性角度来看,这确实是令人讨厌的:用户看到的表单字段阅读,他可能越迷惑和恼怒,并且很有可能决定不去理会这些表单了。

但是,页面保持了完美的无障碍,我已经尽了我作为 web 开发者的责任。用户依然可以填写表单并提交到服务器,即使没有我便利的脚本来处理过的过程会变得耗时和困惑。用户可能不愿填表因为太长和困惑,但这是我们力不能及的地方了。当 JavaScript 不被支持,可用性受损。

别限制了可用性

无障碍不应该限制可用性。如果您有一个 JavaScript 必不可少的精彩绝妙的主意来增加网站的可用性,请使用它,但尽可能(但不是必须)确保页面能用。

完美的无障碍并不是由为有脚本和无脚本用户提供相同的功能组成的。有时就是直截了当的不可能。

比如,让我们来尝试不用 JavaScript 来创建合用表单(还是前面提到的例子)。您可以先提供一个没有可选项的表单,让用户提交到服务器,根据用户的选择让服务器返回其他的选项。技术上,这是可行的。

但是,从用户体验的观点来看,它比我们刚才研究的无脚本表单更糟糕。尽管那可能迷惑并且没有比脚本化的版本有用,但至少不需要额外的下载和字段,而用户也可能误认为自己已经成功提交了表单。

总而言之,最好接受可用性缩减了的无脚本页面,总比费心思补救好。