位置与动画
position
样式属性是一种强大的布局方法。默认情况下,该属性值为static
,表示元素处于文档中的默认位置。若该属性设置为relative
,该元素在文档中依然占据空间,但此时其top
和left
样式属性则是相对于常规位置的偏移。若position
设置为absolute
,会将元素从默认文档流中移除,该元素将不再占据空间,而会与其他元素重叠。其top
和left
属性则是相对其最近的闭合元素的偏移,其中position
属性的值不是static
。如果没有任何闭合元素存在,则是相对于整个文档的偏移。
我们可以使用该属性创建一个动画。下面的文档用于显示一幅猫的图片,该图片会沿着椭圆轨迹移动。
<p style="text-align: center">
<img src="img/cat.png" style="position: relative">
</p>
<script>
let cat = document.querySelector("img");
let angle = Math.PI / 2;
function animate(time, lastTime) {
if (lastTime != null) {
angle += (time - lastTime) * 0.001;
}
lastTime = time;
cat.style.top = (Math.sin(angle) * 20) + "px";
cat.style.left = (Math.cos(angle) * 200) + "px";
requestAnimationFrame(newTime => animate(newTime, time));
}
requestAnimationFrame(animate);
</script>
我们的图像在页面中央,position
为relative
。为了移动这只猫,我们需要不断更新图像的top
和left
样式。
脚本使用requestAnimationFrame
在每次浏览器准备重绘屏幕时调用animate
函数。animate
函数再次调用requestAnimationFrame
以准备下一次更新。当浏览器窗口(或标签)激活时,更新频率大概为 60 次每秒,这种频率可以生成美观的动画。
若我们只是在循环中更新 DOM,页面会静止不动,页面上也不会显示任何东西。浏览器不会在执行 JavaScript 程序时刷新显示内容,也不允许页面上的任何交互。这就是我们需要requestAnimationFrame
的原因,该函数用于告知浏览器 JavaScript 程序目前已经完成工作,因此浏览器可以继续执行其他任务,比如刷新屏幕,响应用户动作。
我们将动画生成函数作为参数传递给requestAnimationFrame
。为了确保每一毫秒猫的移动是稳定的,而且动画是圆滑的,它基于一个速度,角度以这个速度改变这一次与上一次函数运行的差。如果仅仅每次走几步,猫的动作可能略显迟钝,例如,另一个在相同电脑上的繁重任务可能使得该函数零点几秒之后才会运行一次。
我们使用三角函数Math.cos
和Math.sin
来使猫沿着圆弧移动。你可能不太熟悉这些计算,我在这里简要介绍它们,因为你会在这本书中偶尔遇到。
Math.cos
和Math.sin
非常实用,我们可以利用一个 1 个弧度,计算出以点(0,0
为圆心的圆上特定点的位置。两个函数都将参数解释为圆上的一个位置,0 表示圆上最右侧那个点,一直逆时针递增到2π
(大概是 6.28),正好走过整个圆。Math.cos
可以计算出圆上某一点对应的x
坐标,而Math.sin
则计算出y
坐标。超过2π
或小于 0 的位置(或角度)都是合法的。因为弧度是循环重复的,a+2π
与a
的角度相同。
用于测量角度的单位称为弧度 - 一个完整的圆弧是2π
个弧度,类似于以角度度量时的 360 度。 常量π
在 JavaScript 中为Math.PI
。
猫的动画代码保存了一个名为angle
的计数器,该绑定记录猫在圆上的角度,而且每当调用animate
函数时,增加该计数器的值。我们接着使用这个角度来计算图像元素的当前位置。top
样式是Math.sin
的结果乘以 20,表示圆中的垂直弧度。left
样式是 Math.cos 的结果乘以200
,因此圆的宽度大于其高度,导致最后猫会沿着椭圆轨迹移动。
这里需要注意的是样式的值一般需要指定单位。本例中,我们在数字后添加px
来告知浏览器以像素为计算单位(而非厘米,ems
,或其他单位)。我们很容易遗漏这个单位。如果我们没有为样式中的数字加上单位,浏览器最后会忽略掉该样式,除非数字是 0,在这种情况下使用什么单位,其结果都是一样的。