位置与动画

position样式属性是一种强大的布局方法。默认情况下,该属性值为static,表示元素处于文档中的默认位置。若该属性设置为relative,该元素在文档中依然占据空间,但此时其topleft样式属性则是相对于常规位置的偏移。若position设置为absolute,会将元素从默认文档流中移除,该元素将不再占据空间,而会与其他元素重叠。其topleft属性则是相对其最近的闭合元素的偏移,其中position属性的值不是static。如果没有任何闭合元素存在,则是相对于整个文档的偏移。

我们可以使用该属性创建一个动画。下面的文档用于显示一幅猫的图片,该图片会沿着椭圆轨迹移动。

  1. <p style="text-align: center">
  2. <img src="img/cat.png" style="position: relative">
  3. </p>
  4. <script>
  5. let cat = document.querySelector("img");
  6. let angle = Math.PI / 2;
  7. function animate(time, lastTime) {
  8. if (lastTime != null) {
  9. angle += (time - lastTime) * 0.001;
  10. }
  11. lastTime = time;
  12. cat.style.top = (Math.sin(angle) * 20) + "px";
  13. cat.style.left = (Math.cos(angle) * 200) + "px";
  14. requestAnimationFrame(newTime => animate(newTime, time));
  15. }
  16. requestAnimationFrame(animate);
  17. </script>

我们的图像在页面中央,positionrelative。为了移动这只猫,我们需要不断更新图像的topleft样式。

脚本使用requestAnimationFrame在每次浏览器准备重绘屏幕时调用animate函数。animate函数再次调用requestAnimationFrame以准备下一次更新。当浏览器窗口(或标签)激活时,更新频率大概为 60 次每秒,这种频率可以生成美观的动画。

若我们只是在循环中更新 DOM,页面会静止不动,页面上也不会显示任何东西。浏览器不会在执行 JavaScript 程序时刷新显示内容,也不允许页面上的任何交互。这就是我们需要requestAnimationFrame的原因,该函数用于告知浏览器 JavaScript 程序目前已经完成工作,因此浏览器可以继续执行其他任务,比如刷新屏幕,响应用户动作。

我们将动画生成函数作为参数传递给requestAnimationFrame。为了确保每一毫秒猫的移动是稳定的,而且动画是圆滑的,它基于一个速度,角度以这个速度改变这一次与上一次函数运行的差。如果仅仅每次走几步,猫的动作可能略显迟钝,例如,另一个在相同电脑上的繁重任务可能使得该函数零点几秒之后才会运行一次。

我们使用三角函数Math.cosMath.sin来使猫沿着圆弧移动。你可能不太熟悉这些计算,我在这里简要介绍它们,因为你会在这本书中偶尔遇到。

Math.cosMath.sin非常实用,我们可以利用一个 1 个弧度,计算出以点(0,0为圆心的圆上特定点的位置。两个函数都将参数解释为圆上的一个位置,0 表示圆上最右侧那个点,一直逆时针递增到(大概是 6.28),正好走过整个圆。Math.cos可以计算出圆上某一点对应的x坐标,而Math.sin则计算出y坐标。超过或小于 0 的位置(或角度)都是合法的。因为弧度是循环重复的,a+2πa的角度相同。

用于测量角度的单位称为弧度 - 一个完整的圆弧是个弧度,类似于以角度度量时的 360 度。 常量π在 JavaScript 中为Math.PI

位置与动画 - 图1

猫的动画代码保存了一个名为angle的计数器,该绑定记录猫在圆上的角度,而且每当调用animate函数时,增加该计数器的值。我们接着使用这个角度来计算图像元素的当前位置。top样式是Math.sin的结果乘以 20,表示圆中的垂直弧度。left样式是 Math.cos 的结果乘以200,因此圆的宽度大于其高度,导致最后猫会沿着椭圆轨迹移动。

这里需要注意的是样式的值一般需要指定单位。本例中,我们在数字后添加px来告知浏览器以像素为计算单位(而非厘米,ems,或其他单位)。我们很容易遗漏这个单位。如果我们没有为样式中的数字加上单位,浏览器最后会忽略掉该样式,除非数字是 0,在这种情况下使用什么单位,其结果都是一样的。