50. JavaScript 动画

Posted 03/10/2009 - 23:12 by yuanc

50: JavaScript 动画

作者 stuartlangridge · 2009年2月3日

引言

在本文中,我们将学习通过 JavaScript 创建动画效果——动画效果常常用来提高那些浏览器支持该功能的用户的体验。最常见的动画效果是可平滑伸缩面板、进度条、以及表单中的视觉反馈等。

看过卡通片或翻书动画的人都知道,动画其实是通过许多时间间隔很小的帧来实现的,这些帧连续播放使它看起来就像在动一样。动画是一种强大的技术,在吸引用户注意力这方面非常出色。但动画的缺点就是它总是会吸引人们的注意力,不管你想不想要它这么做。动画效果可以提高用户的 Web 应用程序使用体验,但就像辣椒一样:不要加太多哦。

本文目录如下:

一个简单的示例:黄色淡出技术

一种常见的动画应用就是黄色淡出技术:页面上被改动的区域的背景颜色会被设为黄色,然后会逐渐消褪,直到还回原来的背景。这是一个既美观又非干扰性的强调改动过的内容(比如显示了更多内容,或某些表单反馈信息)的方式,而不会干扰到用户正在进行的事情。点击此处查看黄色淡出技术实例。

黄色淡出技术背后的原理就是,将元素的背景颜色设置为黄色,然后通过一系列的步骤,将其逐渐变回为原来的颜色。因此如果原来的颜色是红色的话,背景颜色就会先设成黄色,然后是橙黄色,然后是橙色,然后是桔红色,然后成为红色。你所采用的步骤的数量决定了颜色变化的平滑度,而步骤之间的时间间隔决定了整个颜色变化的过程有多长。进行颜色变化时,我们可以利用一个很实用的CSS特性:颜色可以由三个一组的普通数字来定义,也可以用十六进制的字符串来定义。因此 #FF0000(红色)也可以写成 rgb(255,0,0)。经过五个步骤从 rgb(255,255,0) (黄色)改变成 rgb(255,0,0)(红色)的可以写成到 :

rgb(255,255,0)
rgb(255,192,0)
rgb(255,128,0)
rgb(255,64,0)
rgb(255,0,0)

我们先将元素的背景颜色设置为 rgb(255,255,0),然后过一段时间(假设是100毫秒),将背景颜色改成 rgb(255,192,0),然后再过100毫秒,又设置成 rgb(255,128,0),依此类推:

颜色 时间
rgb(255,255,0) 0
rgb(255,192,0) 100ms
rgb(255,128,0) 200ms
rgb(255,64,0) 300ms
rgb(255,0,0) 400ms

整个过程耗时400毫秒(还不到半秒钟),从黄色到红色的褪色过程很平滑。很方便的是,此处我们只需对颜色的一个通道进行改变(就是绿色的那个通道;rgb色彩的三个通道是红色,绿色,和蓝色通道);当然也可以同时改变多个颜色通道。在本例中,我们经四个步骤将绿色通道从255变到了0,也就是说每一步将其值改变64。

在 JavaScript 中,要在一段特定的时间之后触发某项操作,是通过 setTimeout setInterval 函数来实现的。setTimeout 函数会在某个时间延迟之后执行你规定的操作;而 setInterval 则会一遍又一遍地执行该操作,保持每两次执行之间等待一段延时;此函数很合适实现动画效果。从本质上讲,实现这个褪色效果的方法就是计算出每个步骤,然后用 setInterval 来依次地调用它们。setInterval 函数有两个参数:一个是作为要执行的操作而调用的函数,还有一个以毫秒为单位的时间延迟。

显然地,你并不是每次都要从黄色变到红色的,因此这个函数的通用性应该更好一些。如果你知道起始颜色和最终颜色,以及所经过的步骤的数量的话,这个问题就成了计算出每一步要对每个颜色改变多少的数学问题了。如果你将 startcolour 数组定义为一张三个数字的数组([255,255,0]),并将 endcolour 定义为类似的数组([255,0,0])的话,每一步对每个颜色的改变量就是:

var red_change = (startcolour[0] - endcolour[0]) / steps;
var green_change = (startcolour[1] - endcolour[1]) / steps;
var blue_change = (startcolour[2] - endcolour[2]) / steps;

setInterval 来逐步地改变该元素的背景颜色:

var currentcolour = [255,255,0];
var timer = setInterval(function(){
    currentcolour[0] = parseInt(currentcolour[0] - red_change);
    currentcolour[1] = parseInt(currentcolour[1] - green_change);
    currentcolour[2] = parseInt(currentcolour[2] - blue_change);
    element.style.backgroundColor = 'rgb(' + currentcolour.toString() + ')';
}, 50);

在每一步中,该函数都会读取 currentcolour,并将红色通道改动 red_change,将绿色通道改动 green_change,并将蓝色通道改动 blue_change。首先将该元素的背景颜色设置为新颜色:[255,255,0].toString()也就是"255,255,0",因此为了得到颜色 rgb(255,255,0),我们用 toString()来创建 rgb(255,255,0) ,并将其设置为该元素的背景颜色。

然而, setInterval 会无限次地调用你的函数;即使目标颜色已经达到了,它也不会停下来。为了使 setInterval 停下来,我们可以使用 clearInterval();下面的代码会对该操作的调用次数进行计数,并在适当的步骤数之后停止动画:

var currentcolour = startcolour;
var stepcount = 0;
var timer = setInterval(function(){
    currentcolour[0] = parseInt(currentcolour[0] - red_change);
    currentcolour[1] = parseInt(currentcolour[1] - green_change);
    currentcolour[2] = parseInt(currentcolour[2] - blue_change);
    element.style.backgroundColor = 'rgb(' + currentcolour.toString() + ')';
    stepcount += 1;
    if (stepcount >= steps) {
        element.style.backgroundColor = 'rgb(' + endcolour.toString() + ')';
        clearInterval(timer);
    }
}, 50);

这就是实现动画的方法:每次执行一个步骤。

怎样设置 startcolourendcolour 呢?一个简单的方法就是将上面的代码封装到一个 fade 函数中:

fade: function(element, startcolour, endcolour, time_elapsed) {
   ...code from above...
}

然后就可以通过一个函数调来以实现某个元素的黄色淡出效果了:

fade(document.getElementById("yft"), [255,255,60], [0,0,255], 750);

或者也可以是“红色淡出”,也就是将该元素设为红色,然后逐渐褪化为蓝色(该元素的背景颜色),就像这样:

fade(document.getElementById("yft"), [255,0,0], [0,0,255], 750);

这个例子是用来改变背景颜色的,但它也适用于其它任何东西的改变:前景颜色(用于制作1960年代那种风格的令人眼花缭乱的迷幻文字特效),不透明度(用来使某些事物淡出或淡入),位置(用于使某个元素在页面上移来移去),高度和宽度(使某个元素变大,或将其缩小至无物以实现消失效果)。

用 JavaScript 程序库实现动画

动画是一种常用的效果,因此大多数 JavaScript 程序库都具有某些动画效果支持,支持常见动画的。举例来说,jQuery 就有一种内置支持能使元素逐渐淡化直至透明:

$("#myelement").fadeOut();

还有一个 animate()函数,可以用来实现更复杂的自定义效果:

$("#block").animate({ 
    width: "70%",
}, 1500 );

这段代码非常直观——它将会读取该元素,并在1500毫秒时间内将 CSS 的 width 属性改为原来的70%——此处是 animate 函数的文档

Prototype 的 scriptaculous 框架也提供了类似的功能,比如 Effect.Fade('id_of_element'),以及许许多多其它的效果。Yahoo UI 库也可以实现类似效果

new Y.Anim({ node: '#demo', to: { width: 70%, }}).run();

如果你在自己的代码中已经用到过某个 JavaScript 程序库,你就该知道它们所提供的动画功能比你自己用 setInterval来做动画要容易得多。不过,我觉得了解藏在表面下的机制是很重要的——从长远来看,这会使你的脚本编写能力越来越强。这就是为什么我要在讨论程序库之前以例子的形式介绍如何创建简单动画。

你可以在 dev.opera.com 的 JavaScript 工具包简介一文中找到更多关于如何使用各种 JavaScript 程序库的资源。

一个更复杂的示例:位置的移动和大小的改变

虽然黄色渐变技术的示例阐释了动画基本原理,但它有点太乏味。大多数人在想到动画的时候,他们所想到的是运动。如果 Wile E. Coyote 所做的东西都只是改变改变颜色的话,他可能就没那么有趣了。

有一个很不错的技巧可以用来提醒用户页面中所发生的事情,而又不会打断他们的工作流程,这就是非模态消息。与弹出一个 alert() 对话框不同(然后只有等用户点击了 K 他们才能继续操作),非模态消息会直接将消息放在页面上的一个浮动的div上,该div会一直不显眼地呆在那里,直到用户确认已收到信息。另一个优点是允许用户再次阅读自己已确认的消息。因此,让我们来实现一个浮动信息,点击该信息后,它就会“缩到”屏幕的一角去,然后再点击一下还可以还回来。你可以点击此处查看 “缩放信息”效果

如果你要做任何正式的动画效果,或任何正式的 JavaScript 工作的话, JavaScript 程序库总是值得一用的。这就使得你可以为用户提供你所希望提供的用户体验,而不用操心实现该动画所需的数学细节了。(读完前面的第一个例子后,现在你知道该怎样进行计算,以及如何使用 setInterval 了,不过,让别人帮你做那些重活儿可以为你节省不少时间,还有脑细胞。)

前面的演示中用到了 jQuery 程序库来实现效果,但就如我们提到过的那样,大多数程序库都提供了非常相似的动画支持,因此你应该能用自己喜欢的程序库来实现动画。大体上来讲,我们应该这么做:

  1. 在屏幕中央显示出一个浮动信息
  2. 当该信息被点击时:
    1. 将其在水平方向上的位置移至右侧
    2. 将其在垂直方向上的位置移至顶部
    3. 将其宽度改为20 px
    4. 将其高度改为20 px
    5. 将其不透明度改为20%,这样该信息就接近透明了,并将其中的文字隐藏
  3. 点击这个“迷你”版的信息时,使其回到屏幕中央(跟我们刚才使其缩小的操作刚好相反)

这样,用户就能清楚地了解自己的信息到底怎么了,从完整大小的信息到迷你信息的转化过程应当是平滑的动画(这样用户就能目睹自己的信息“缩到”窗口的一角)。

用 jQuery 来实现这个动画是很简单的:只需使用 .animate() 函数,并告诉它你想要的该动画的最终效果就行了(以及你希望整个过程持续多长时间):

$(ourObject).animate({
    width: "20px", height: "20px", top: "20px",
    right: "20px", marginRight: "0px", opacity: "0.2"
  }, 300);

上面这段代码会获取 ourObject ,并在300毫秒的时间内,将其宽度和高度变成20px,将其顶部和右侧的位置变为20px,将其 margin-right 样式属性变成0px,并将其不透明度(在支持不透明度的浏览器中)变成20%。然后只需使用 jQuery 风格编程,以实现在点击该信息时产生动画效果的问题了:

$(ourObject.click, function(){
  $(this).animate({
    width: "20px", height: "20px", top: "20px",
    right: "20px", marginRight: "0px", opacity: "0.2"
  }, 300)
});

再次点击时恢复该信息,要实现这个效果只需再次调用 .animate() 就可以了

$(ourObject).animate({
    width: "400px", height: "75px", top: "50px",
    right: "50%", marginRight: "-200px", opacity: "0.9"
  }, 300);

此外还需要用一点逻辑结构来确定当前该信息是处于显示状态还是收缩状态(这样你才能知道该执行哪个动画),以及需要一些 CSS,用来描述该信息的初始样式(大小,颜色为绿色,位置为水平居中),这就是所需的全部东西。仅仅三十行代码的脚本就可以实现这个效果。这就是为什么说脚本库真是个好东西的原因!

CSS 过渡

最后要说明的是,有些(但不是所有)动画实际上不需要任何 JavaScript 就可以实现!Safari 和其它基于 Webkit 的浏览器,还有马上就要发行的 Firefox3.1,都可以实现从一个 CSS 值到另一个值的平滑过渡,而不需要任何的 JavaScript 。下面这段代码:

div { opacity: 1; -webkit-transition: opacity 1s linear; }
div:hover{ opacity: 0; }

在支持此特性的浏览器中,上面代码在鼠标悬停的时在一秒钟的时间内将 div 平滑地淡出。不过,CSS 过渡是很新的技术,只有最新的浏览器才支持此特性,因此如果你想让自己的动画能为大多数人群所使用,那你就得通过 DOM 脚本来实现了。

总结

本文介绍了如何利用 JavaScript 实现 Web 页面动画功能——我们探讨了如何通过 setTimeout setInterval 函数创建动画示例,然后我们讨论了如何利用 JavaScript 脚本库来更快捷地创建动画。

练习题

  1. setTimeout setInterval 之间的差别是什么?
  2. 如果不存在 setInterval 这个函数,你可以怎样模拟出一个这样的函数?
  3. 你该如何使一个元素从完全可见褪化到完全不可见,整个过程分20步,耗时1.5秒?
  4. 你该如何使一个元素从完全可见褪化到完全不可见,然后又回到可见状态,整个过程分20步,耗时1.5秒?

作者简介

Picture of the article author Stuart Langridge

Stuart Langridge 很可能是世界上唯一一个同时拥有计算机科学和哲学学位的人了。当他没瞎捣鼓电脑时,他是 Canonical 上的一个 JavaScript ,Django 及 Python 骇客,SitePoint 的 DHTML Utopia 的作者,同时还是一个上等啤酒爱好者。他还是世界上第一个免费开源软件广播秀 LugRadio 的团队的四分之一。你可以在 kryogenix.org上面找到他关于 Web ,脚本,开源软件,以及所有飘过他窗前的东西的杂乱感想;你很可能在屋外找到正在寻找吸烟区的 Stuart 。