DHTML、标准和浏览器兼容性
本文描述了开发交互式 HTML 界面时遇到的一些常见挑战,以及解决这些挑战的一些想法。我们还将探讨开发高度交互式 Web 界面时遇到的一些基本问题。
本文由 James Shimada 撰写,最初发表于 2005 年 8 月的《软件开发者期刊》。您可以在 SDJ 网站上找到更多文章。
引言
作为一名 Web 开发者,通过整合使用 JavaScript、层叠样式表 (CSS) 和 HTML,可以在很大程度上丰富您 Web 应用程序的交互性。尽管这些技术已经迅速成熟,并且所有 Web 浏览器供应商现在都非常重视标准兼容性,但在许多不同类型的浏览器上无缝运行的丰富、交互式 Web 应用程序的构建仍然可能非常具有挑战性。本文描述了开发交互式 HTML 界面时遇到的一些常见挑战,以及解决这些挑战的一些想法。我们将探讨开发高度交互式 Web 界面时遇到的一些基本问题,然后重点介绍如何利用面向标准的代码来最大化浏览器兼容性。我们还将讨论如何尝试编写与新兴标准兼容的代码。
DHTML 的挑战
术语动态 HTML (Dynamic HTML) 或 DHTML 通常用于描述 HTML、JavaScript 和 CSS 技术的集合。它也可以指使用 JavaScript 操作 HTML 内容或 CSS 样式而产生的网页的特性。事实上,DHTML 有几种定义(请参阅 Google)。事实上,准确定义 DHTML 的问题可以让我们对成功实现 DHTML 的挑战有所了解。当您浏览 DHTML 的每个定义时,可以看出它们的意思大致相同,但有些定义比其他定义更详细地介绍了它的某个特定方面。可以清楚地看到,对 DHTML 的概念存在细微的不同解释。也许很难得出一个能满足每个人对 DHTML 看法的单一描述,同样,也很难编写出一个在所有浏览器上都运行相同的 DHTML 解决方案。在本文中,我们将重点介绍如何通过关注标准兼容性来解决这个问题,但首先让我们提及实现 DHTML 时遇到的其他两个基本问题。
将客户端 DHTML 与服务器端系统集成也存在挑战,而服务器端系统通常是应用程序逻辑的实现之处。对 DHTML 的承诺会促使您重新审视服务器端内容交付系统和脚本语言(如 PHP、Perl/Mason 或 ASP)的作用,而这些通常被认为是表示层。使用 DHTML,客户端 JavaScript 在交付表示方面发挥着更积极的作用,并且必须谨慎定义服务器端前端与您的客户端表示层之间的角色和接口。为此,您会发现典型的 Web 开发者工具包最大的缺点之一是,在 JavaScript 函数中没有简单、明显的方法可以在不重新加载整个网页的情况下从服务器检索数据。事实上,当您的客户端 JavaScript UI 变得越来越高级时,重新加载页面可能会造成严重的中断。确实有几种方法可以实现远程脚本(JavaScript 远程过程调用),但目前还没有基于标准的方法。从 UI 设计的角度来看,DHTML 也可能非常具有挑战性。DHTML 的目标之一是提供开发能够模仿桌面应用程序丰富性的基于 Web 的应用程序的手段。但是,Web UI 设计与桌面软件设计之间存在根本性差异:基于 Web 的软件可以利用的设计约定和指南几乎没有。人们依赖熟悉 UI 约定来了解其使用的软件的表单预期。例如,即使您以前从未用过某个桌面程序,您仍然可能期望保存选项在文件菜单下,您可能会尝试右键单击(或在 Mac 上按住 Control 键单击)图标以发现与之相关的更多上下文命令。不幸的是,这些约定在 Web 上并不常见。Web 上没有通用的文件菜单概念,也没有任何菜单。相反,我们只剩下与应用程序交互的不同概念。面对复杂的 Web 应用程序,用户通常需要在对其感到满意之前投入更多的初始学习成本。无谓的 DHTML 有时只会加剧这个问题。
现在,我们已经对实现 DHTML 的相关问题有了一个很好的了解。首先,Web 浏览器支持不一致。其次,它需要与应用程序的表示层进行更复杂的交互(即,您的 PHP 脚本过去只是打印简单的 HTML,但现在它必须管理各种 JavaScript 和其他 DHTML 内容)。第三,由于缺乏 UI 指南,很难设计出成功的 DHTML 密集型 UI。但不要气馁!通过了解要避免什么,以及一些合理的設計考量,您可以创建丰富、令人满意的基于 DHTML 的解决方案,最大限度地减少这些问题,同时满足用户的需求。
了解您的受众
在解决浏览器兼容性问题时,首先要考虑谁将使用您的应用程序。这是一个公司内部网应用程序吗?还是您的应用程序将在互联网上广泛可用?是否有 Web 服务器日志报告可以按浏览器和版本细分您的网站流量?如果您知道将使用哪些浏览器,那么您可以根据用户的环境来设计您的应用程序。
但是,您甚至可能无法预测您的用户将拥有哪些浏览器。在这种情况下,您可以通过查看 Web 浏览器总体使用率和市场份额的估计值来粗略了解您的用户环境。截至本文撰写之时,像阿姆斯特丹的 OneStat.com 和圣地亚哥的 WebSiteStory 这样的 Web 分析公司报告称,微软的 Internet Explorer 的使用率为 88.9%,市场份额分别为 92.9%。这听起来似乎 IE 是浏览器中最受欢迎的选择,但请考虑 OneStat 的调查显示,IE 的使用率在六个月内下降了 5 个百分点,而 WebSideStory 报告称其市场份额在五个月内下降了 2.6%。IE 主导地位下降的主要原因是 Mozilla Firefox 的日益普及。虽然 Firefox 因其有用的插件功能和安全改进而广受欢迎,但它在实现 HTML、DOM 和 CSS 的 W3C 标准方面也做得非常出色,使其成为 DHTML 程序员的理想目标平台。
坚持使用标准
一方面,Web 是一个独立于平台的乐园。您可以构建一个应用程序,它会立即分发给您的用户,并且可以在任何操作系统上运行,而无需任何特殊软件(除了 Web 浏览器)。至少这是它的承诺,最终我们会实现的。但为了实现这一承诺,Web 浏览器需要实现标准化版本的 DHTML。引领标准化方向的是全球互操作性委员会——万维网联盟 (W3C)。该组织负责为 DHTML 相关技术制定标准规范。尽管 W3C 礼貌地承认DHTML这个词,但他们在任何技术文献中都不会正式使用该词。相反,他们维护 HTML(包括 XHTML,HTML 的 XML 等效项)、CSS 和文档对象模型 (DOM) 的几项规范和技术报告。对于 Web 程序员来说,DOM 技术报告可能是最有趣的。W3C 笼统地将 DOM 描述为“...一个允许程序和脚本动态访问和更新文档的内容、结构和样式的接口...”。在 Web 编程方面,DOM 描述了标准的 JavaScript API,用于操作网页的元素,如 DOM Core 和 DOM HTML 建议中所述。DOM 的一个关键特性是,当您操作网页的任何底层数据时,它会立即生效。这与传统的 Web 编程不同,在传统 Web 编程中,为了更新页面上的某些数据,您需要向服务器发出请求,进行一些处理,然后重新生成 HTML。使用 DOM,可以使用 JavaScript 即时更新网页上的数据。您仍然可能需要将用户操作的最终结果发送到服务器进行处理,但在此期间,您可以创建更即时的令人满意的体验,同时节省不必要的服务器往返。列表 1 包含了一个使用 DOM 和通用的 getElementById()
函数的非常简单的示例。
列表 1. 使用 DOM 的简单示例
<span id="myId">Hi</span>
<br />
<a href="javascript:updateText( 'Hello World' )">Hello World</a>
<script language="javascript" type="text/javascript">
function updateText( newText ) {
var elem = document.getElementById( "myId" );
elem.innerHTML = newText;
}
</script>
此示例通过更新 innerHTML
属性来简单地更新 <span>
块的内容为“Hello World”。innerHTML
属性已被 W3C 弃用,但它提供了一个非常直接的 DOM 使用示例。innerHTML
是一个名为DOM Level 0的东西的一部分。在 W3C 开始提出 DOM 建议之前,Web 浏览器供应商已经在定义 HTML 文档的标准化 JavaScript API 方面取得了一些进展,其中大部分(但肯定不是全部)符合行业标准的 JavaScript 规范,即 ECMAScript。在 W3C 正式建议之前,几乎所有 Web 浏览器都支持的 JavaScript 函数和属性被“祖父条款”纳入了Level 0 DOM。Level 0 DOM 函数通常可以安全地用于跨浏览器兼容脚本,但与 innerHTML
一样,可能不会出现在进一步的 W3C 建议中,因此在新浏览器中的支持不一定得到保证。
标准化的烦恼
HTML、CSS 和 DOM 标准应该是由于各种 Web 浏览器供应商推出自己的浏览器工作方式而引起的兼容性问题的救星。事实上,在大多数情况下它们确实如此,但这并不意味着实现它们总是容易的。它也不意味着 Web 浏览器统一支持所有标准。
DHTML 程序员经常面临一个问题,即需要实现一个在所有目标浏览器平台上都不一致,甚至根本不支持的特性。您的用户环境中可能存在必须支持的旧浏览器,或者您的用户环境中的现代浏览器可能无法统一实现 W3C DOM 建议中的 API。但有志者事竟成。让我们举一个例子,说明如何解决一个 W3C 推荐的机制,该机制并未被所有最新浏览器统一支持。
不透明度:弥补不一致的 API
您的网页的一个非常巧妙的效果是通过操作页面元素的透明度来实现的。通过为图像或文本块等页面元素分配小于 100% 的透明度值,您可以实现透明效果。如果您随时间操纵透明度,甚至可以实现淡入效果。更好的是,各种浏览器(如 Firefox、IE 和 Safari)都支持操纵透明度。缺点是这些浏览器都以不同的方式实现透明度。但是,我们仍然可以编写一些相对简洁的 JavaScript 代码,在各种不同的浏览器上实现透明度。在此示例中,我们将编写一个 JavaScript 函数,该函数将文本块逐渐淡入视图。
我们将从要淡入视图的文本块的 HTML 开始。请注意,外部 <div>
容器的 display 样式设置为 none
,这将使其默认不可见。我们将包含几个链接,当单击这些链接时,它们将执行我们编写的 JavaScript 函数来显示或隐藏 <div>
块(参见列表 2)。
列表 2. 显示和隐藏 <div>
块示例
<a href="javascript:show( 'fadeInBlock' )">Show</a>
<a href="javascript:hide( 'fadeInBlock' )">Hide</a>
<div id="fadeInBlock"
style="display:none; border=1px solid #666666;
background-color:#DADADA; width:95%; height=12">
<p style="margin:5; font-weight:bold">
This text is fading fading fading in.
</p>
</div>
现在,让我们看看这些 show()
和 hide()
函数是如何实现的。您首先会注意到的是,这两个函数都将 id
属性作为参数。每个函数都将使用我们最喜欢的 getElementById
方法来访问 <div>
块的对象表示。hide()
函数非常简单。它只需确保 display 样式被重置为 none
。show()
函数仍然保持一个相当高的级别,但它为我们处理问题提供了一个很好的框架。它首先确保浏览器支持操纵透明度。如果支持,我们将最初将透明度设置为零,使其完全透明,然后我们可以安全地将 display 样式设置为 block
。一旦 display 处于 block
模式,我们就可以调用一个函数,该函数逐渐增加透明度,直到它变得实心(列表 3)。
列表 3. 逐渐增加透明度
<script language="javascript" type="text/javascript">
function show( id ) {
var elem = document.getElementById( id );
if( supportsOpacity( elem )) {
// first set 0% opacity to make it completely transparent
setOpacity( elem, 0 );
// when we set display = "block" it's still invisible
elem.style.display = "block";
// call the function to gradually increase opacity
fadeIn( id );
} else {
// can't modify opacity, so just make it visible...
elem.style.display = "block";
}
}
function hide( id ) {
var elem = document.getElementById( id );
elem.style.display="none";
}
</script>
show()
函数调用了三个我们需要实现的函数:supportsOpacity()
、setOpacity()
和 fadeIn()
。现在,我们将看到三个不同的浏览器如何以三种不同的方式支持透明度(列表 4)。从 supportsOpacity()
函数开始,我们看到了一种用于确定 encodeURIComponent
是否已实现的技术,通过检查各种透明度样式访问器的定义性。Mozilla 和 Firefox 支持 MozOpacity
样式,而 IE 使用 filters 来实现透明度。此外,Safari 支持 opacity
样式。后者是透明度的 W3C 标准,但只有 Safari 和 Opera 在支持它方面做得比较好。这里还值得注意的是,IE 只能通过使用 filters 来支持透明度,前提是目标元素(在此示例中为 <div>
块)具有 height
和 width
样式属性。这与 Microsoft 定义 filters 的方式有关。
列表 4. 三种不同的透明度支持方式
<script language="javascript" type="text/javascript">
function supportsOpacity( el ) {
if ( el.style.opacity != undefined )
return true;
if( el.style.MozOpacity != undefined )
return true;
if ( el.style.filter != undefined )
return true;
return false;
}
function setOpacity( el, opaciLevel ) {
if ( el.style.opacity != undefined ) {
el.style.opacity = opaciLevel;
} else if( el.style.MozOpacity != undefined ) {
el.style.MozOpacity = opaciLevel;
} else if ( el.style.filter != undefined ) {
var oplvl = Math.round(opaciLevel*100);
el.style.filter="alpha(opacity=" + oplvl + ")";
}
}
function fadeIn( id, currentOpacity ) {
var counterLimit = 20;
var el = document.getElementById( id );
if( !currentOpacity ) {
currentOpacity = 1;
}
if( currentOpacity > counterLimit ) {
return;
}
setOpacity( el, ( currentOpacity/counterLimit ) );
currentOpacity++;
var func = "fadeIn( '" + id + "', " + currentOpacity + ")";
window.setTimeout( func, 50);
}
</script>
以我们 supportsOpacity
函数的风格评估访问器可以告诉我们该样式是否存在,即使相应的透明度样式未定义。如果三种透明度样式中的任何一种存在,我们就可以得出结论,该浏览器支持它。接下来,setOpacity()
使用相同的逻辑,但该函数实际上为 opacity
属性设置了一个值。opacity
和 MozOpacity
样式属性接受相同的值,即介于 0 和 1 之间的浮点数,但 IE 的 filter 需要更繁琐的语法,并且需要介于 0 和 100 之间的整数值作为 opacity
“filter”的值。最后,fadeIn()
函数使用 setTimeout()
方法以 50 毫秒的间隔调用自身 20 次,每次将透明度增加 1/20。
希望您的大部分 JavaScript 代码都可以这样编写,这样您就可以利用不同浏览器之间的共性。但正如本例所示,有时您需要考虑浏览器实现等效函数方式的差异。如果您必须编写条件性地依赖不同浏览器实现的 JavaScript 代码,那么将代码条件化为特定方法或属性的定义性,而不是条件化为实际浏览器版本,通常是明智的。请考虑我们 setOpacity()
函数的以下替代实现(参见列表 5)。
虽然像这样的代码可能与其它实现具有相同的结果,但它存在可能导致其在浏览器成熟时出错的问题。首先,它将 IE 和 Mozilla 锁定在它们特有的透明度支持方式中。一旦 Firefox 或 IE 发布了支持标准 opacity
属性的版本,您难道不想改用该机制吗?因为谁能说非标准化的透明度支持方式不会被弃用并从未来的版本中删除?此外,由于同一浏览器的不同版本在支持的功能上可能差异很大,您必须考虑版本号才能使浏览器检查代码真正起作用。而且,特别是对于 IE,它在不同平台上的行为可能差异很大。例如,IE for Mac 的最新版本 5.2,几乎没有 DOM 支持。可以想象,在代码中跟踪所有这些内容会非常笨拙且难以维护。而且您永远不希望您的代码在新浏览器中停止正常工作。最直接的技术是检查您想要使用的方法或属性,然后建立优先级,以便在调用非标准方法之前调用基于标准的 方法和属性。另外,请考虑使用 setOpacity()
等更高级的包装函数,它们会找出特定浏览器 API 的细节。这样,至少使用包装函数 Thus,代码可以保持浏览器独立性。
列表 5. setOpacity()
函数的替代实现
function setOpacity( el, opaciLevel ) {
// assume getBrowserType returns "moz", "ie", "safari" or "opera"
thisBrowser = getBrowserType();
if( thisBrowser == "moz" ) {
el.style.MozOpacity = opaciLevel;
} else if (thisBrowser == "ie" ) {
var oplvl = Math.round(opaciLevel*100);
el.style.filter="alpha(opacity=" + oplvl + ")";
} else if ((thisBrowser == "safari")
|| (thisBrowser == "opera")) {
el.style.opacity = opaciLevel;
}
}
远程脚本:领先标准一步
令人遗憾的是,即使是流行的 Web 浏览器最新版本似乎也落后于 W3C 标准 DOM 一步。比这更糟糕的是,当 W3C 标准落后于您想在 Web 应用程序中实现的功能时。远程脚本,或使用客户端 JavaScript 在不重新加载页面的情况下访问服务器上的数据,可能是高需求功能而无标准支持的最佳示例。
在它成为标准的一部分之前,Web 浏览器供应商会通过他们自己的远程脚本DOM 扩展来响应需求,公开类似于 XMLHTTP 请求 API 的东西。您甚至可以在 Emmanuil Batsis 编写的Sarissa 库中找到一个实现 XMLHTTP Request
对象通用接口的开源 JavaScript 库。就像我们的 setOpacity
示例一样,Sarissa 负责所有浏览器特定的细节,并为程序员提供了一个用于远程脚本以及可在各种不同浏览器上运行的 XSLT 和 XPath 任务的高级 API。
但在任何浏览器实现 XMLHTTP 请求 API 之前,纯 JavaScript 技术就已经用于执行远程脚本。虽然仅使用 JavaScript 在不重新加载页面的情况下访问服务器数据似乎不可能,但通过巧妙地利用 DOM,您可以模拟 JavaScript RPC。用于执行远程脚本的最广泛使用的 JavaScript 库之一是 JavaScript Remote Scripting Library (JSRS),由 Brett Ashely 编写,并在开源许可证下分发。简而言之,它通过使用 DOM 创建一个内联框架来构建 RPC。该内联框架具有指向服务器端脚本的 src
属性。当内联框架加载时,您的客户端脚本就可以访问其中包含的数据。
Sarissa 和 JSRS 等项目表明,即使在某个功能没有标准支持,而您又想将其集成到 Web 应用程序中时,您仍然可以很好地实现该功能。通过利用他人的工作或实现自己的 DOM 变通方法接口,仍然可以实现跨浏览器兼容性的目标,同时节省大部分代码去理解如何在每个不同的浏览器上完成。
结论
Web 浏览器在实现标准化 DHTML 相关技术方面取得了巨大进步,但正如我们所见,在实现跨不同浏览器工作的 DHTML 解决方案方面仍然存在许多挑战。但情况只会越来越好,因为标准兼容性受到越来越重视,并且像 Firefox 这样的新浏览器开始搅动浏览器市场。最重要的是,如果您一开始就考虑标准来设计您的 DHTML 应用程序,就可以节省大量时间和精力。尽量利用目标浏览器之间的共性。但如果您面临着协调不同的浏览器 API,则应致力于更高级别的功能抽象,隐藏浏览器特定的细节。
资源
请务必查看以下资源,以获取有关浏览器兼容 DHTML 的深入信息
- Dynamic HTML: The Definitive Reference, by Danny Goodman, O’Reilly。
- W3C DOM
- DOM in Mozilla