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">
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来创建合用表单(还是前面提到的例子)。您可以先提供一个没有可选项的表单,让用户提交到服务器,根据用户的选择让服务器返回其他的选项。技术上,这是可行的。

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

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