要制作一个用 1 分钟画一个圆圈的动画,不需要使用 rAF,因为这只会产生额外的负载,尽管我个人建议在大多数情况下使用 rAF。
然而,在这种情况下,监视器同步不是那么重要setInterval
(并且setTimeout
)在负载方面可能是更好的选择。
这是修改后的代码,每分钟画一个圆圈。它基于实际时间戳,因此时间非常准确。这里的间隔设置为 120 毫秒,但这应该与圆的周长有关,因为这将确定在该时间范围内要绘制多少像素,因为重叠像素不会那么可见(此处忽略子像素)。根据需要随意调整超时。
在这里修改小提琴
现在的设置如下(小提琴不需要window.onload,所以我删除了它,但是如果你在最后一页的标题中加载脚本,当然你需要把它放回去)。var 名称可能会更好,但我保留了一些原始名称:
start_angle = 1.5 * Math.PI, /// common offset (north)
end_angle = 2 * Math.PI, /// ends full circle in radians
increase_end_angle = 0, /// current angle incl. offset
radius = 50,
startTime = (new Date()).getTime(), /// get current timestamp
diff; /// used for timestamp diff
我们还将静态设置移到循环之外以节省一些 CPU 周期(实际上设置笔触样式等确实会产生效果,如果一直设置,因此这是更优化的)。没有任何必要使用save
/restore
因为我们没有在其他地方需要的 out 循环期间更改很多变量:
context.lineWidth = 6;
context.lineCap = "round";
主要功能是根据实际时间重置圆圈:
setInterval(anim, 120); /// 120 for demo, use Ø and time to find optimal timeout
function anim() {
/// calc difference between initial and current timestamp
diff = (new Date()).getTime() - startTime;
diff = diff / 60000; /// 60000ms = 60s, now we have [0, 1] fractions
/// final angle
increase_end_angle = start_angle + end_angle * diff;
/// draw circle
context.beginPath();
context.arc(x, y, radius, start_angle, increase_end_angle);
context.stroke();
/// check diff fraction
if (diff >= 1) { /// if diff >= 1 we have passed 1 minute
/// update time and new radius
startTime = (new Date()).getTime();
radius += 10; /// add to current radius
};
}
理想情况下,您将清除每次绘制的当前圆圈以保持抗锯齿像素以获得更平滑的外观,因为在顶部重绘最终会由于 alpha 通道而消除此问题。
当然,这意味着您需要在半径增加时执行一些额外的步骤,例如将当前内容绘制到后面的画布以保留已经绘制的圆圈。
更新:您还可以通过设置画布使画布“高分辨率”以减少 arc 方法的粗糙度:
canvas.width = wantedWidth * 2;
canvas.height = wantedHeight * 2;
canvas.style.width = wantedWidth + 'px'
canvas.style.height = wantedHeight + 'px';
请记住相应地缩放所有坐标和大小(x2)。
更新了使用高分辨率画布运行的小提琴
更新:解决其他问题:
1)如何将所有内容乘以 2(即比例)使一切变得更清晰。
在“高分辨率模式”中发生的情况是,我们使用的画布大小是初始画布的两倍,但是通过应用额外样式 (CSS),我们将整个画布缩小到第一个大小。
但是,现在同一区域中有两倍多的像素,并且由于子像素化,我们可以利用它来获得更好的“分辨率”。但与此同时,我们还需要缩放所有内容以使其回到与使用双倍尺寸画布之前相同的位置。
这就像一张 400x400 的图像。如果以 400x400 显示,则像素比率为 1:1。如果您改为使用 800x800 的图像,但将其尺寸强制缩小到 400x400,图像中仍然会有 800x800 像素,但那些无法显示的像素(因为监视器无法显示半像素)会被插值以使其出现在那里那里是半个像素。
但是,对于形状,您可以获得更详细的形状版本,因为它首先经过抗锯齿处理,然后对其周围的内容进行插值,使其看起来更平滑(如您在演示中所见)。
2)我理解setInterval(anim,120);//这部分..ratio..这就是为什么循环在60秒完成?再次,我在使用 setInterval.Reason 时总是怀疑,因为它确实会在一段时间后提供混蛋。但在这种情况下不是。我不想让我的动画停止,这在使用 RAF 时总是发生。但是 raf 再次对优化非常有用。所以那里有点混乱,但我想我会采用 setInterval 的方式。
首先采取的混蛋是由于setInterval
并且setTimeout
无法同步到显示器的 VBLANK。VBLANK 来自旧的 CRT 电视,当扫描监视器屏幕内部的光束时,开始了一个新的框架。要正确同步,请同步到 VBLANK 间隙。这适用于所有与视频相关的设备,包括。电脑。
然而,由于这需要定时器的浮点分辨率(在 60Hz 的情况下为 16.7ms),因此这些定时器是不可能的。在此期间,您会不时遇到舍入错误,导致在循环中跳过一帧,从而导致抽搐。引入了 rAF ( requestAnimationFrame
),它能够与显示器的刷新率同步,但不仅仅是因为这个原因。它更加低级和高效,因此也可以降低功耗。
如果您不需要监视器同步(最多)每秒 60 次,那么使用不准确的计时器并没有错——因为在这种情况下,您更依赖于在一整分钟内绘制的圆的半径。如果您使用 rAF,您可能会在此期间多次在同一个像素上绘制,但看不到任何变化(尽管子像素化会启动并且仍然允许其中一些像素发生小的视觉变化)。所以这里的 rAF 没有任何作用,因为你会做许多不会在屏幕上产生影响的不必要的绘制。
setInterval
每个 SE 还不错,但对于动画来说,在需要不断更新的地方通常太不准确了。它的作用是创建一个事件并将其放入浏览器的事件队列中。如果可能,事件大约在它超时的时间执行(队列包含多种事件,例如重绘、函数调用等)。这里不是超级准确但足够准确,因为我们不依赖于它何时超时,而是使用我们更新弧的实际时间。因此,为此目的,当我们以非常小的增量绘制时,这将掩盖细微的不准确性。
这并不意味着其他因素可能会引发更新中的问题,但这也适用于 rAF。没有什么是完美的。
至于时间 120 毫秒 - 这只是我开始的一个数字。这是一个没有太大改变平滑度的数字。然而,这并不是完成这个圈子的东西。
完成循环的是时间上的差异"Now time" - "Start time"
。在这种情况下,这将产生 60,000 毫秒的差异,因为我们每轮使用 60 秒。所以为了得到一个有用的数字,我们除以 60000,所以我们得到的数字在 0.0 和 1.0 之间,我们可以直接乘以角度,当 diff 为 1 时得到 100% 的角度。
理想的计时器是使用与圆周相关的时间。这将决定每个新像素之间有多少时间,因此您可以例如将其再次除以 10 以考虑子像素化。这将是最佳的,因为更新的终点不会有重叠,但每次触发循环时都会绘制一个新像素。
3)这个问题有点难,我现在正在研究它。如果我做不到,我会接受你的建议。它处理一些 json,创建多个实例并在数据停止时停止动画.我明天试试,现在太累了。
为此,我建议提出一个新问题。似乎元答案超过了主要答案.. :-o :-)