【问题标题】:Why is animation speed increasing each time the target element is deleted and recreated?为什么每次删除和重新创建目标元素时动画速度都会增加?
【发布时间】:2013-08-30 20:33:07
【问题描述】:

我创建了一个基于其他人的 Chrome 实验的 jQuery 插件,该插件将一个画布元素插入到您的目标元素中,并在画布中绘制一个交互式星空。

每次调整窗口大小时,canvas 元素都会被移除然后恢复,以使其大小与其父元素匹配,并且所有内容都可以正常动画;它反应灵敏。

但是,每当它恢复时,动画的速度就会增加。为什么这样做?我认为所有变量(包括速度)都使用 this.start() 方法重置为默认值。

您可以在CodePen 上查看代码(和演示);你也可以在 Github 上 fork 它,虽然我认为 Github 版本是我自己的几个提交。

(另外,这是我的第一个真正的 jQuery 插件,所以如果您发现任何问题,请务必告诉我。)

有什么线索吗?

【问题讨论】:

  • 也许老动画还在。在激活新的animate 函数之前尝试使用.stop()
  • 没有 .animate() 到 .stop()。
  • 思路是一样的。您是否一次又一次地打电话给您的anim(),让一切都发生多次?
  • 可能是当窗口从 100 - 120 增加时,即增加了 20%,因此星速增加了 20%,但是当它从 120 - 100 增加时。它被计算为窗口大小减少了 16.67%,因此您回到了相同的窗口大小,但您的星速并没有因为它计算事物的角度而按比例减少?
  • 嗯。 @Itay 是的,使用 requestAnimationFrame 和 setTimeout 后备。但是,当我在 reset() 方法中调用 cancelAnimationFrame 时,没有区别...

标签: javascript jquery html animation canvas


【解决方案1】:

使用cancelAnimationFrame单独不需要停止动画循环(事实证明)。

绝对确定您还需要使用 条件检查 - 一个通用示例:

var isPlaying; /// our condition

function loop() {

    /* funky stuff here */

    If (isPlaying === true)
        requestId = requestAnimationFrame(loop);
}

然后启动它:

functiom start() {
    isPlaying = true;
    loop();
}

现在当你想停止动画时你需要做的:

function stop() {

    isPlaying = false;

    /// kill any request in progress
    if (requestId) cancelAnimationFrame(requestId);
}

如果您不这样做,您可能会冒着堆叠循环调用的风险 - 例如:

如果您调整大小并且 cAF 没有阻止 rAF 重新触发循环,则旧循环仍将在后台运行,您将在此基础上启动一个新循环。

这就是为什么您会看到速度增加,因为旧循环和新循环都会在星星被绘制到屏幕之前增加位置。

在第三次调整大小时,又开始了另一个循环,最终整个事情都会阻塞浏览器。

然而

我建议您使用以下方法,而不是利用循环的开始和停止:

  • 创建一次画布
  • 只启动一次循环
  • 在这种情况下,重构整个大小调整机制可能是有益的(例如,将所需的初始化(元素的宽度和高度)与以后可以重用的第一次初始化分开)。李>
  • 无需为每次调整大小重新初始化星星,因为您将使用宽度和高度来检查它们的边界(画布将进行剪裁)。

在调整大小时可以考虑使用条件标志来防止在调整大小时渲染

虽然通常情况下,由于 JavaScript 的单线程特性,实际上不需要在画布更改大小时阻止渲染的条件,在这种情况下,您需要对当前元素大小进行边界检查。 Canvas 自己会为您处理剪裁。

话虽如此:应该没有必要每次都重新创建画布元素。

这会产生不必要的开销。如果画布已经创建,则简单地在其属性上设置新的宽度和高度:

if (typeof canvas === 'undefined')
    canvas = /* only create if it doesn't exist */

canvas.width = width;
canvas.height = height;

PS:上述I "hampered" a version with some brute-force-ish implementations。它远非完整或高质量,但为了举例给你一些建议,它消除了一些痛苦。

请采用适合您的需求。

更新

要包含来自其他 cmets 的更多信息:

当在画布元素上设置新大小时,其 上下文 被重置为默认值(fillStyle 变为透明,strokeStyle 黑色,变换被重置等等)。

这意味着在设置每个新尺寸后,必须重新设置所有非默认设置。

设置一个新的大小可能(并且通常会)清除画布的内容,以便所有像素都变成透明的黑色。

【讨论】:

  • 这是一种让 SO 很棒的答案。感谢您帮助我学习。
  • 我更新了我的笔以反映您建议的一些更改(尤其是重构)。不过,我发现您的代码实际上并没有调整画布的大小(尽管出于某种原因,它似乎在 codepen 中这样做了),这是不行的。调整画布大小会重置画布,所以每当我更新它的尺寸时,我都会丢失所有的星星。新代码在星星更新时按比例重置星星的位置。
【解决方案2】:

对于手动更新canvas 元素尺寸的人来说:

调整canvas 元素的大小会导致它丢弃在调整大小之前绘制到它的所有内容。

这个脚本的动画应该在调整大小后继续绘制到画布上,但唯一会更新的是背景的fillRect;星星消失了。

在更改canvas 元素的尺寸后恢复星星的唯一方法是:额外调用context.strokeStyle。我不知道为什么;如果有人能对此事有所了解,我将不胜感激。

编辑: 根据下面的 cmets,画布中的所有内容都会重置,包括笔触和填充样式(显然,两者都默认为黑色) .所以当调整大小触发时,我不得不重新定义笔触和填充样式。

【讨论】:

  • 抱歉不清楚这一点 - 画布 do 清除所有新尺寸(它变成带有重置/新上下文的全新位图),包括。样式(中风 -> 黑色,这就是设置新尺寸后看不到星星的原因)。作为重构的一部分,您应该将样式设置等放在调整大小时调用的部分中。
  • @Ken-AbdiasSoftware 啊,这是有道理的。谢谢(你的)信息。经过几个小时想知道为什么要重置矩形而不是星星,我确实将样式规则放入resizer(),即使循环仍在进行;事实证明,你看不到黑场上的黑星。同时,作为重构的一部分,我将一堆公共函数移入init()(因为它们在逻辑上是初始化器),并将rAF 定义和一些变量移入全局空间;我不确定这是否违反了 Javascript 的任何神圣规则,但代码有效!再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2016-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多