Javascript 选项卡处于非活动状态时动画出错

Javascript animation goes wrong when tab is inactive

样本: http://codepen.io/anon/pen/xgZMVd/

html 文件:

<div class="game-page">
</div>

css 文件:

.game-page {
    width: 1024px;
    height: 768px;
    position: absolute;
    background-color: grey;
}

.stick {
    position: absolute;
    left: 50%;
    margin-left: -94px;
    top: -60px;
    width: 188px;
    height: 50px;
    background-color: black;
}

js文件:

$(document).ready(function() {
  init();
});

var CSS_CLASS = { STICK: "stick" },
    changeSpeedTime = 1000, //in milliseconds
    gameTime = 60, //in seconds
    timer = 1, //in seconds
    windowWidth = 1024,
    windowHeight = 768,
    stickTop = -60,
    restStickTime = 0, //in milliseconds
    initialStickInterval = 1000, //in milliseconds
    stickInterval = null, //in milliseconds
    initialStickDuration = 7, //in seconds
    stickDuration = null, //in seconds
    stickTweensArray = [],
    changeSpeedInterval = null,
    countdownTimerInterval = null,
    generateSticksInterval = null,
    generateSticksTimeout = null,
    $gamePage = null;

function init() {
  initVariables();
  initGamePage();
}

function changingSpeedFunction(x){
  var y = Math.pow(2, (x / 20));
  return y;
}

function initVariables() {
  $gamePage = $(".game-page");
  stickDuration = initialStickDuration;
  stickInterval = initialStickInterval;
}

function initGamePage() {
  TweenMax.ticker.useRAF(false);
  TweenMax.lagSmoothing(0);
  initGamePageAnimation();
}

function initGamePageAnimation () {
  generateSticks();

  changeSpeedInterval = setInterval(function () {
    changeSpeed();
  }, changeSpeedTime);

  countdownTimerInterval = setInterval(function () {
    updateCountdown();
  }, 1000);
}

function changeSpeed () {
  var x = timer;
  var y = changingSpeedFunction(x);  //change speed function
  stickDuration = initialStickDuration / y;
  stickInterval = initialStickInterval / y;

  changeCurrentSticksSpeed();
  generateSticks();
}

function changeCurrentSticksSpeed () {
  stickTweensArray.forEach(function(item, i, arr) {
    var tween = item.tween;
    var $stick = item.$stick;

    var oldTime = tween._time;
    var oldDuration = tween._duration;
    var newDuration = stickDuration;
    var oldPosition = stickTop;
    var newPosition = $stick.position().top;
    var oldStartTime = tween._startTime;

    var distance = newPosition - oldPosition;
    var oldSpeed = distance / oldTime;
    var newSpeed = oldSpeed * oldDuration / newDuration;
    var newTime = distance / newSpeed;
    var currentTime = oldStartTime + oldTime;
    var newStartTime = currentTime - newTime;

    item.tween._duration = newDuration;
    item.tween._startTime = newStartTime;
  });
}

function generateSticks () {
  if (restStickTime >= changeSpeedTime) {
    restStickTime -= changeSpeedTime;
    restStickTime = Math.abs(restStickTime);
  } else {
    generateSticksTimeout = setTimeout(function () {
      generateSticksInterval = setInterval(function () {
        generateStick();
        restStickTime -= stickInterval;
        if (restStickTime <= 0) {
          clearInterval(generateSticksInterval);
          restStickTime = Math.abs(restStickTime);
        }
      }, stickInterval);
      generateStick();
      restStickTime = changeSpeedTime - Math.abs(restStickTime) - stickInterval;
      if (restStickTime <= 0) {
        clearInterval(generateSticksInterval);
        restStickTime = Math.abs(restStickTime);
      }
    }, restStickTime);
  }
}

function generateStick () {
  var $stick = $("<div class='" + CSS_CLASS.STICK + "'></div>").appendTo($gamePage);

  animateStick($stick);
}

function animateStick ($stick) {
  var translateYValue = windowHeight + -stickTop;

  var tween = new TweenMax($stick, stickDuration, {
    y: translateYValue, ease: Power0.easeNone, onComplete: function () {
      $stick.remove();
      stickTweensArray.shift();
    }
  });

  stickTweensArray.push({tween:tween, $stick:$stick});
}

function updateCountdown () {
  timer++;

  if (timer >= gameTime) {
    onGameEnd();
    clearInterval(changeSpeedInterval);
    clearInterval(countdownTimerInterval);
    clearInterval(generateSticksInterval);
    clearTimeout(generateSticksTimeout);
  }
}

function onGameEnd () {
  var $sticks = $gamePage.find(".stick");
  TweenMax.killTweensOf($sticks);
}

所以,根据我的研究,我有下一个情况:

  1. TweenMax(因为它使用 requestAnimationFrame)在选项卡处于非活动状态时冻结。
  2. setInterval keep going when tab is inactive (also it's delay may change when tab is inactive, depends on browser)
  3. 是否有任何其他 javascript 选项卡处于非活动状态时会发生变化的功能?

那么我有2个解决方案:

  1. 冻结整个游戏,当标签不活动时。
  2. 保持不活动时继续前进。

对于第一个解决方案,我遇到了下一个问题:由于 TweenMax 使用 requestAnimationFrame,它可以根据此解决方案正常工作(冻结动画),但是如何在选项卡处于非活动状态时冻结间隔和超时,然后恢复间隔和超时?

对于第二种解决方案,我可以使用 TweenMax.lagSmoothing(0) 和 TweenMax.ticker.useRAF(false) 来制作动画并且它有效,但是无论如何间隔 and/or 超时出了点问题。 I expected that animation goes wrong because of change of interval delay to 1000+ ms when tab is inactive (according to http://Whosebug...w-is-not-active), but I disabled acceleration and set delays to 2000ms and it didn't help.

请至少帮我解决一个问题。最好两者都有一些变化。

第一个解决方案解决了问题:冻结整个游戏。只是使用 TweenMax.delayedCall() 函数而不是 setTimeout 和 setInterval 并且整个动画同步得很好。