在AJAX如火如荼的今天,相信大家对Prototype这个Javascript类库应该都有所耳闻,它也的确使编写Javascript变得更简单。关于Prototype的文章,《Prototype简介》、《Prototype源码》诸如此类数不胜数;所以本文不会再做这几方面的介绍,并假设读者对Prototype有一定了解。
网页动画与原理
提到网页动画,大家首先想起应该Flash。不知道大家没有开发过Flash动画,故我想对此作一个简单的介绍(在我读大学的时候,对Flash也曾有过痴迷,所以也略懂一二)。Flash的动画主要分两类:渐变动画和逐帧动画。
- 渐类动画——用户在时间轴上创建开始的关键帧和结束的关键帧,开发环境(Macromedia Profassional Flash 8等)会根据以上所创建的关键帧的颜色、位置和形状等,在计算出中间的过渡帧并添加到相应的时间轴上。这适用于创建简单的动画。
- 逐帧动画——用户在时间轴的每帧上创建关键帧,并在其中绘制相应的图按。这适用于创建复杂的动画。
在Javascript中由于没有绘图API(应用程序接口),故只可以使用DOM+CSS改变元素的外观。而通过每隔一段时间调用一次改变元素外观的函数,实现类似Flash的渐类动画。
具体实现
因为不同的Javascript动画实现的基本原理都相同,所以可以创建一个基类将其抽象出来。代码如下:
var Animation
= Class.create();
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
Animation.prototype =
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 构造函数
|
| 参数:
| element 将要实现动画效果的元素
| fps 每秒播放帧数
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
initialize: function(element, fps)
{
this.element
= $(element);
this.interval
= Math.round(1000
/ fps);
this.isPlaying
=
false;
this.currentFrame
=
1;
//创建一个用于存储中间状态的临时对象
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
this.temp
=
{ };
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 子类覆盖该方法,实现自定义的动画补间
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_createTweens:
function(original,
transformed, frames)
{ },
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 创建动画补间
|
| 参数:
| original 开始状态
| transformed 结束状态
| frames 动画帧数
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
createTweens: function(original, transformed, frames)
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.isPlaying)
{
this.stop();
}
this._createTweens(original,
transformed, frames);
this.original
= original;
this.transformed
= transformed;
this.frames
= frames;
//将开始状态拷贝到临时对象
Object.extend(this.temp, original);
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 判断临时对象状态是否超出结束状态
|
| 参数:
| prop 状态属性名称
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_isOverstep:
function(prop)
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.original[prop]
< this.transformed[prop])
{
return this.temp[prop]
>
this.transformed[prop];
}
return this.temp[prop]
<
this.transformed[prop];
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_prepare: function()
{ },
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_draw: function(frame)
{ },
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_drawFrame: function()
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.isPlaying)
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.currentFrame
<
this.frames)
{
this._prepare();
this._draw(this.temp);
this.currentFrame ++;
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
}
else
{
//最后一帧绘制结束状态
this._draw(this.transformed);
this.stop();
}
}
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_play:
function()
{ },
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
play: function()
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(!this.isPlaying)
{
this._play();
this.isPlaying = true;
this.timer =
setInterval(this._drawFrame.bind(this),
this.interval);
}
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_stop: function()
{ },
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
stop:
function()
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.isPlaying)
{
this._stop();
//回到开始状态
this.isPlaying = false;
this.currentFrame = 1;
Object.extend(this.temp,
this.original);
clearInterval(this.timer);
}
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_pause: function()
{ },
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
pause: function()
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if(this.isPlaying)
{
this._pause();
this.isPlaying = false;
clearInterval(this.timer);
}
}
}
清单1 Animation.js
Animation类实现了一些公用的管理内部状态的操作,如播放动画、停止动画和暂停动画等。接下来,创建特定的动画变得相当容易了,下面让我们来看一个形状和位置渐变的动画实现,代码如下:
var ShapeAnimation
= Class.create();
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
ShapeAnimation.prototype
= Object.extend(new Animation(),
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 覆盖父类的空白实现,计算每帧的变化量
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_createTweens: function(original, transformed, frames)
{
this.xSpan
= Math.round((transformed.x
- original.x)
/ frames);
this.ySpan
= Math.round((transformed.y
- original.y)
/ frames);
this.wSpan
= Math.round((transformed.w
- original.w)
/ frames);
this.hSpan
= Math.round((transformed.h
- original.h)
/ frames);
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 覆盖父类的空白实现,计算当前的状态。如果超出结束状态,保持结束状态不变
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_prepare: function()
{
this.temp.x
=
this._isOverstep('x')
?
this.transformed.x
: this.temp.x
+
this.xSpan;
this.temp.y
=
this._isOverstep('r')
?
this.transformed.y
: this.temp.y
+
this.ySpan;
this.temp.w
=
this._isOverstep('w')
?
this.transformed.w
: this.temp.w
+
this.wSpan;
this.temp.h
=
this._isOverstep('h')
?
this.transformed.h
: this.temp.h
+
this.hSpan;
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//*------------------------------------------------------------------------
| 用途:
| 覆盖父类的空白实现,刷新元素外观
------------------------------------------------------------------------*/
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_draw:
function(frame)
{
var
x = frame.x
+ 'px';
var
y = frame.y
+ 'px';
var
w = frame.w
+ 'px';
var
h = frame.h
+ 'px';
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Element.setStyle(this.element,
{ left: x, top: y, w, height: h });
}
});
清单2 ShapeAnimation.js
ShapeAnimation类继承Animation类,并覆盖了其中的某些方法。最后,让我们创建HTML文件,测试一下这个ShapeAnimation是否可以正确工作。代码如下:
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ShapeAnimation Test</title>
<script
type="text/javascript" src="prototype-1.4.0.js"></script>
<script
type="text/javascript" src="Animation.js"></script>
<script
type="text/javascript" src="ShapeAnimation.js"></script>
<script
type="text/javascript">
var animation;
Event.observe(window, 'load', init,
false);
function init() {
var clip
= $('clip');
var pos
= Position.cumulativeOffset(clip);
animation =
new ShapeAnimation(clip, 12);
animation.createTweens( { x: pos[0], y: pos[1], w:
100, h:
75 },
{ x: 100,
y: 100, w:
200, h:
200 },
24 );
}
function play() {
animation.play();
}
function stop() {
animation.stop();
}
function pause() {
animation.pause();
}
</script>
</head>
<body>
<input
type="button" onclick="play()" value="Play" />
<input
type="button" onclick="stop()" value="Stop" />
<input
type="button" onclick="pause()" value="Pause" /><br
/>
<br
/>
<img
src="thumb.jpg" alt="Thumb" id="clip" style="left: 13px; position:
absolute; top: 52px;"
/>
</body>
</html>
清单3 ShapeAnimationTest.htm
分别在IE或Firefox中打开ShapeAnimationTest.htm,播击“Play”、“Stop”和“Pause”按钮工作正常。
举一反三
上述例子,我创建了形状动画类。有了Animation类作为基类,当然我可以容易地创建更多的动画类。下面我再举一个裁剪动画示例。代码如下:
var ClipAnimation
= Class.create();
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockStart.gif)
ClipAnimation.prototype
= Object.extend(new Animation(),
{
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_createTweens: function(original, transformed, frames)
{
this.tSpan
= Math.round((transformed.t
- original.t)
/ frames);
this.rSpan
= Math.round((transformed.r
- original.r)
/ frames);
this.bSpan
= Math.round((transformed.b
- original.b)
/ frames);
this.lSpan
= Math.round((transformed.l
- original.l)
/ frames);
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_prepare: function()
{
this.temp.t
=
this._isOverstep('t')
?
this.transformed.t
: this.temp.t
+
this.tSpan;
this.temp.r
=
this._isOverstep('r')
?
this.transformed.r
: this.temp.r
+
this.rSpan;
this.temp.b
=
this._isOverstep('b')
?
this.transformed.b
: this.temp.b
+
this.bSpan;
this.temp.l
=
this._isOverstep('l')
?
this.transformed.l
: this.temp.l
+
this.lSpan;
},
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
_draw: function(frame)
{
var
clipStyle =
'rect(' + frame.t
+ 'px ';
clipStyle = clipStyle + frame.r + 'px ';
clipStyle = clipStyle + frame.b + 'px ';
clipStyle
= clipStyle
+ frame.l
+ 'px)';
![](http://www.blogjava.net/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
Element.setStyle(this.element,
{ clip: clipStyle });
}
});
清单4 ClipAnimation.js
测试文件代码如下:
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Untitled Page</title>
<script
type="text/javascript" src="prototype-1.4.0.js"></script>
<script
type="text/javascript" src="Animation.js"></script>
<script
type="text/javascript" src="ClipAnimation.js"></script>
<script
type="text/javascript">
var animation;
Event.observe(window, 'load', init,
false);
function init() {
var clip
= $('clip');
var pos
= Position.cumulativeOffset(clip);
var dimensions
= Element.getDimensions(clip);
animation =
new ClipAnimation(clip, 12);
animation.createTweens( { t:
0, r: dimensions.width, b: Element.getHeight(clip), l:
0 },
{ t: 0,
r: dimensions.width, b:
0, l:
0 },
24);
}
function play() {
animation.play();
}
function stop() {
animation.stop();
}
function pause() {
animation.pause();
}
</script>
</head>
<body>
<input
type="button" onclick="play()" value="Play"
/>
<input
type="button" onclick="stop()" value="Stop" />
<input
type="button" onclick="pause()" value="Pause" /><br />
<br
/>
<img
src="thumb.jpg" alt="Thumb" id="clip" style="left: 13px; position:
absolute; top: 52px;"
/>
</body>
</html>
清单5 ClipAnimationTest.htm
总结
Prototype实现了部分的面向对象,对常用的操作提供了方便的封装。这样我们可以编写具有更高可重性的Javascript代码,将实现重HTML文件中分离出来,使程序结构更清晰可读。
点击以下链接下载示例代码