diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bae9361f..ed126ad3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,5 +19,5 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - run: npm clean-install + - run: npm install - run: npm test diff --git a/README.md b/README.md index 9211e396..48ef9633 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ JavaScript (TypeScript) tweening engine for easy animations, incorporating optim More languages: [English](./README.md), [简体中文](./README_zh-CN.md) -# Example +--- ```html @@ -48,10 +48,7 @@ More languages: [English](./README.md), [简体中文](./README_zh-CN.md) ``` -[Try the above example on CodePen](https://codepen.io/trusktr/pen/KKGaBVz?editors=1000) - -Animate numbers in any JavaScript object. For example, [rotate a 3D box made -with Three.js](https://codepen.io/trusktr/pen/ExJqvgZ): +[Try this example on CodePen](https://codepen.io/trusktr/pen/KKGaBVz?editors=1000) # Installation diff --git a/dist/tween.amd.js b/dist/tween.amd.js index 77100205..a0cfdfdd 100644 --- a/dist/tween.amd.js +++ b/dist/tween.amd.js @@ -678,11 +678,13 @@ define(['exports'], (function (exports) { 'use strict'; * it is still playing, just paused). */ Tween.prototype.update = function (time, autoStart) { + var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = true; } if (this._isPaused) return true; + var property; var endTime = this._startTime + this._duration; if (!this._goToEnd && !this._isPlaying) { if (time > endTime) @@ -709,85 +711,72 @@ define(['exports'], (function (exports) { 'use strict'; var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; - var elapsed = this._calculateElapsedPortion(elapsedTime, durationAndDelay, totalTime); + var calculateElapsedPortion = function () { + if (_this._duration === 0) + return 1; + if (elapsedTime > totalTime) { + return 1; + } + var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); + var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; + // TODO use %? + // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay + var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); + if (portion === 0 && elapsedTime === _this._duration) { + return 1; + } + return portion; + }; + var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); - var status = this._calculateCompletionStatus(elapsedTime, durationAndDelay); - if (status === 'repeat') { - // the current update is happening after the instant the tween repeated - this._processRepetition(elapsedTime, durationAndDelay); - } + // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); - if (status === 'about-to-repeat') { - // the current update is happening at the exact instant the tween is going to repeat - // the values should match the end of the tween, not the beginning, - // that's why _processRepetition happens after _updateProperties - this._processRepetition(elapsedTime, durationAndDelay); - } if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } - if (status === 'repeat' || status === 'about-to-repeat') { - if (this._onRepeatCallback) { - this._onRepeatCallback(this._object); - } - this._onEveryStartCallbackFired = false; - } - else if (status === 'completed') { - this._isPlaying = false; - if (this._onCompleteCallback) { - this._onCompleteCallback(this._object); - } - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration, false); - } - } - return status !== 'completed'; - }; - Tween.prototype._calculateElapsedPortion = function (elapsedTime, durationAndDelay, totalTime) { - if (this._duration === 0 || elapsedTime > totalTime) { - return 1; - } - var timeIntoCurrentRepeat = elapsedTime % durationAndDelay; - var portion = Math.min(timeIntoCurrentRepeat / this._duration, 1); - if (portion === 0 && elapsedTime !== 0 && elapsedTime % this._duration === 0) { - return 1; - } - return portion; - }; - Tween.prototype._calculateCompletionStatus = function (elapsedTime, durationAndDelay) { - if (this._duration !== 0 && elapsedTime < this._duration) { - return 'playing'; - } - if (this._repeat <= 0) { - return 'completed'; - } - if (elapsedTime === this._duration) { - return 'about-to-repeat'; - } - return 'repeat'; - }; - Tween.prototype._processRepetition = function (elapsedTime, durationAndDelay) { - var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); - if (isFinite(this._repeat)) { - this._repeat -= completeCount; - } - // Reassign starting values, restart by making startTime = now - for (var property in this._valuesStartRepeat) { - var valueEnd = this._valuesEnd[property]; - if (!this._yoyo && typeof valueEnd === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(valueEnd); + if (this._duration === 0 || elapsedTime >= this._duration) { + if (this._repeat > 0) { + var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); + if (isFinite(this._repeat)) { + this._repeat -= completeCount; + } + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { + this._valuesStartRepeat[property] = + // eslint-disable-next-line + // @ts-ignore FIXME? + this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + if (this._yoyo) { + this._swapEndStartRepeatValues(property); + } + this._valuesStart[property] = this._valuesStartRepeat[property]; + } + if (this._yoyo) { + this._reversed = !this._reversed; + } + this._startTime += durationAndDelay * completeCount; + if (this._onRepeatCallback) { + this._onRepeatCallback(this._object); + } + this._onEveryStartCallbackFired = false; + return true; } - if (this._yoyo) { - this._swapEndStartRepeatValues(property); + else { + if (this._onCompleteCallback) { + this._onCompleteCallback(this._object); + } + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration, false); + } + this._isPlaying = false; + return false; } - this._valuesStart[property] = this._valuesStartRepeat[property]; } - if (this._yoyo) { - this._reversed = !this._reversed; - } - this._startTime += durationAndDelay * completeCount; + return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { diff --git a/dist/tween.cjs b/dist/tween.cjs index 7de60d34..0e15887c 100644 --- a/dist/tween.cjs +++ b/dist/tween.cjs @@ -680,11 +680,13 @@ var Tween = /** @class */ (function () { * it is still playing, just paused). */ Tween.prototype.update = function (time, autoStart) { + var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = true; } if (this._isPaused) return true; + var property; var endTime = this._startTime + this._duration; if (!this._goToEnd && !this._isPlaying) { if (time > endTime) @@ -711,85 +713,72 @@ var Tween = /** @class */ (function () { var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; - var elapsed = this._calculateElapsedPortion(elapsedTime, durationAndDelay, totalTime); + var calculateElapsedPortion = function () { + if (_this._duration === 0) + return 1; + if (elapsedTime > totalTime) { + return 1; + } + var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); + var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; + // TODO use %? + // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay + var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); + if (portion === 0 && elapsedTime === _this._duration) { + return 1; + } + return portion; + }; + var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); - var status = this._calculateCompletionStatus(elapsedTime, durationAndDelay); - if (status === 'repeat') { - // the current update is happening after the instant the tween repeated - this._processRepetition(elapsedTime, durationAndDelay); - } + // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); - if (status === 'about-to-repeat') { - // the current update is happening at the exact instant the tween is going to repeat - // the values should match the end of the tween, not the beginning, - // that's why _processRepetition happens after _updateProperties - this._processRepetition(elapsedTime, durationAndDelay); - } if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } - if (status === 'repeat' || status === 'about-to-repeat') { - if (this._onRepeatCallback) { - this._onRepeatCallback(this._object); - } - this._onEveryStartCallbackFired = false; - } - else if (status === 'completed') { - this._isPlaying = false; - if (this._onCompleteCallback) { - this._onCompleteCallback(this._object); - } - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration, false); - } - } - return status !== 'completed'; - }; - Tween.prototype._calculateElapsedPortion = function (elapsedTime, durationAndDelay, totalTime) { - if (this._duration === 0 || elapsedTime > totalTime) { - return 1; - } - var timeIntoCurrentRepeat = elapsedTime % durationAndDelay; - var portion = Math.min(timeIntoCurrentRepeat / this._duration, 1); - if (portion === 0 && elapsedTime !== 0 && elapsedTime % this._duration === 0) { - return 1; - } - return portion; - }; - Tween.prototype._calculateCompletionStatus = function (elapsedTime, durationAndDelay) { - if (this._duration !== 0 && elapsedTime < this._duration) { - return 'playing'; - } - if (this._repeat <= 0) { - return 'completed'; - } - if (elapsedTime === this._duration) { - return 'about-to-repeat'; - } - return 'repeat'; - }; - Tween.prototype._processRepetition = function (elapsedTime, durationAndDelay) { - var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); - if (isFinite(this._repeat)) { - this._repeat -= completeCount; - } - // Reassign starting values, restart by making startTime = now - for (var property in this._valuesStartRepeat) { - var valueEnd = this._valuesEnd[property]; - if (!this._yoyo && typeof valueEnd === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(valueEnd); + if (this._duration === 0 || elapsedTime >= this._duration) { + if (this._repeat > 0) { + var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); + if (isFinite(this._repeat)) { + this._repeat -= completeCount; + } + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { + this._valuesStartRepeat[property] = + // eslint-disable-next-line + // @ts-ignore FIXME? + this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + if (this._yoyo) { + this._swapEndStartRepeatValues(property); + } + this._valuesStart[property] = this._valuesStartRepeat[property]; + } + if (this._yoyo) { + this._reversed = !this._reversed; + } + this._startTime += durationAndDelay * completeCount; + if (this._onRepeatCallback) { + this._onRepeatCallback(this._object); + } + this._onEveryStartCallbackFired = false; + return true; } - if (this._yoyo) { - this._swapEndStartRepeatValues(property); + else { + if (this._onCompleteCallback) { + this._onCompleteCallback(this._object); + } + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration, false); + } + this._isPlaying = false; + return false; } - this._valuesStart[property] = this._valuesStartRepeat[property]; } - if (this._yoyo) { - this._reversed = !this._reversed; - } - this._startTime += durationAndDelay * completeCount; + return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { diff --git a/dist/tween.d.ts b/dist/tween.d.ts index c6d7dbae..2a5ecb67 100644 --- a/dist/tween.d.ts +++ b/dist/tween.d.ts @@ -137,9 +137,6 @@ declare class Tween { * it is still playing, just paused). */ update(time?: number, autoStart?: boolean): boolean; - private _calculateElapsedPortion; - private _calculateCompletionStatus; - private _processRepetition; private _updateProperties; private _handleRelativeValue; private _swapEndStartRepeatValues; diff --git a/dist/tween.esm.js b/dist/tween.esm.js index 5fbb8699..4e6dc7d7 100644 --- a/dist/tween.esm.js +++ b/dist/tween.esm.js @@ -676,11 +676,13 @@ var Tween = /** @class */ (function () { * it is still playing, just paused). */ Tween.prototype.update = function (time, autoStart) { + var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = true; } if (this._isPaused) return true; + var property; var endTime = this._startTime + this._duration; if (!this._goToEnd && !this._isPlaying) { if (time > endTime) @@ -707,85 +709,72 @@ var Tween = /** @class */ (function () { var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; - var elapsed = this._calculateElapsedPortion(elapsedTime, durationAndDelay, totalTime); + var calculateElapsedPortion = function () { + if (_this._duration === 0) + return 1; + if (elapsedTime > totalTime) { + return 1; + } + var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); + var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; + // TODO use %? + // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay + var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); + if (portion === 0 && elapsedTime === _this._duration) { + return 1; + } + return portion; + }; + var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); - var status = this._calculateCompletionStatus(elapsedTime, durationAndDelay); - if (status === 'repeat') { - // the current update is happening after the instant the tween repeated - this._processRepetition(elapsedTime, durationAndDelay); - } + // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); - if (status === 'about-to-repeat') { - // the current update is happening at the exact instant the tween is going to repeat - // the values should match the end of the tween, not the beginning, - // that's why _processRepetition happens after _updateProperties - this._processRepetition(elapsedTime, durationAndDelay); - } if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } - if (status === 'repeat' || status === 'about-to-repeat') { - if (this._onRepeatCallback) { - this._onRepeatCallback(this._object); - } - this._onEveryStartCallbackFired = false; - } - else if (status === 'completed') { - this._isPlaying = false; - if (this._onCompleteCallback) { - this._onCompleteCallback(this._object); - } - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration, false); - } - } - return status !== 'completed'; - }; - Tween.prototype._calculateElapsedPortion = function (elapsedTime, durationAndDelay, totalTime) { - if (this._duration === 0 || elapsedTime > totalTime) { - return 1; - } - var timeIntoCurrentRepeat = elapsedTime % durationAndDelay; - var portion = Math.min(timeIntoCurrentRepeat / this._duration, 1); - if (portion === 0 && elapsedTime !== 0 && elapsedTime % this._duration === 0) { - return 1; - } - return portion; - }; - Tween.prototype._calculateCompletionStatus = function (elapsedTime, durationAndDelay) { - if (this._duration !== 0 && elapsedTime < this._duration) { - return 'playing'; - } - if (this._repeat <= 0) { - return 'completed'; - } - if (elapsedTime === this._duration) { - return 'about-to-repeat'; - } - return 'repeat'; - }; - Tween.prototype._processRepetition = function (elapsedTime, durationAndDelay) { - var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); - if (isFinite(this._repeat)) { - this._repeat -= completeCount; - } - // Reassign starting values, restart by making startTime = now - for (var property in this._valuesStartRepeat) { - var valueEnd = this._valuesEnd[property]; - if (!this._yoyo && typeof valueEnd === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(valueEnd); + if (this._duration === 0 || elapsedTime >= this._duration) { + if (this._repeat > 0) { + var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); + if (isFinite(this._repeat)) { + this._repeat -= completeCount; + } + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { + this._valuesStartRepeat[property] = + // eslint-disable-next-line + // @ts-ignore FIXME? + this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + if (this._yoyo) { + this._swapEndStartRepeatValues(property); + } + this._valuesStart[property] = this._valuesStartRepeat[property]; + } + if (this._yoyo) { + this._reversed = !this._reversed; + } + this._startTime += durationAndDelay * completeCount; + if (this._onRepeatCallback) { + this._onRepeatCallback(this._object); + } + this._onEveryStartCallbackFired = false; + return true; } - if (this._yoyo) { - this._swapEndStartRepeatValues(property); + else { + if (this._onCompleteCallback) { + this._onCompleteCallback(this._object); + } + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration, false); + } + this._isPlaying = false; + return false; } - this._valuesStart[property] = this._valuesStartRepeat[property]; } - if (this._yoyo) { - this._reversed = !this._reversed; - } - this._startTime += durationAndDelay * completeCount; + return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { diff --git a/dist/tween.umd.js b/dist/tween.umd.js index 9b032f38..b4ffc32b 100644 --- a/dist/tween.umd.js +++ b/dist/tween.umd.js @@ -682,11 +682,13 @@ * it is still playing, just paused). */ Tween.prototype.update = function (time, autoStart) { + var _this = this; var _a; if (time === void 0) { time = now(); } if (autoStart === void 0) { autoStart = true; } if (this._isPaused) return true; + var property; var endTime = this._startTime + this._duration; if (!this._goToEnd && !this._isPlaying) { if (time > endTime) @@ -713,85 +715,72 @@ var elapsedTime = time - this._startTime; var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); var totalTime = this._duration + this._repeat * durationAndDelay; - var elapsed = this._calculateElapsedPortion(elapsedTime, durationAndDelay, totalTime); + var calculateElapsedPortion = function () { + if (_this._duration === 0) + return 1; + if (elapsedTime > totalTime) { + return 1; + } + var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); + var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; + // TODO use %? + // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay + var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); + if (portion === 0 && elapsedTime === _this._duration) { + return 1; + } + return portion; + }; + var elapsed = calculateElapsedPortion(); var value = this._easingFunction(elapsed); - var status = this._calculateCompletionStatus(elapsedTime, durationAndDelay); - if (status === 'repeat') { - // the current update is happening after the instant the tween repeated - this._processRepetition(elapsedTime, durationAndDelay); - } + // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); - if (status === 'about-to-repeat') { - // the current update is happening at the exact instant the tween is going to repeat - // the values should match the end of the tween, not the beginning, - // that's why _processRepetition happens after _updateProperties - this._processRepetition(elapsedTime, durationAndDelay); - } if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed); } - if (status === 'repeat' || status === 'about-to-repeat') { - if (this._onRepeatCallback) { - this._onRepeatCallback(this._object); - } - this._onEveryStartCallbackFired = false; - } - else if (status === 'completed') { - this._isPlaying = false; - if (this._onCompleteCallback) { - this._onCompleteCallback(this._object); - } - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration, false); - } - } - return status !== 'completed'; - }; - Tween.prototype._calculateElapsedPortion = function (elapsedTime, durationAndDelay, totalTime) { - if (this._duration === 0 || elapsedTime > totalTime) { - return 1; - } - var timeIntoCurrentRepeat = elapsedTime % durationAndDelay; - var portion = Math.min(timeIntoCurrentRepeat / this._duration, 1); - if (portion === 0 && elapsedTime !== 0 && elapsedTime % this._duration === 0) { - return 1; - } - return portion; - }; - Tween.prototype._calculateCompletionStatus = function (elapsedTime, durationAndDelay) { - if (this._duration !== 0 && elapsedTime < this._duration) { - return 'playing'; - } - if (this._repeat <= 0) { - return 'completed'; - } - if (elapsedTime === this._duration) { - return 'about-to-repeat'; - } - return 'repeat'; - }; - Tween.prototype._processRepetition = function (elapsedTime, durationAndDelay) { - var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); - if (isFinite(this._repeat)) { - this._repeat -= completeCount; - } - // Reassign starting values, restart by making startTime = now - for (var property in this._valuesStartRepeat) { - var valueEnd = this._valuesEnd[property]; - if (!this._yoyo && typeof valueEnd === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(valueEnd); + if (this._duration === 0 || elapsedTime >= this._duration) { + if (this._repeat > 0) { + var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); + if (isFinite(this._repeat)) { + this._repeat -= completeCount; + } + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { + this._valuesStartRepeat[property] = + // eslint-disable-next-line + // @ts-ignore FIXME? + this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + if (this._yoyo) { + this._swapEndStartRepeatValues(property); + } + this._valuesStart[property] = this._valuesStartRepeat[property]; + } + if (this._yoyo) { + this._reversed = !this._reversed; + } + this._startTime += durationAndDelay * completeCount; + if (this._onRepeatCallback) { + this._onRepeatCallback(this._object); + } + this._onEveryStartCallbackFired = false; + return true; } - if (this._yoyo) { - this._swapEndStartRepeatValues(property); + else { + if (this._onCompleteCallback) { + this._onCompleteCallback(this._object); + } + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration, false); + } + this._isPlaying = false; + return false; } - this._valuesStart[property] = this._valuesStartRepeat[property]; } - if (this._yoyo) { - this._reversed = !this._reversed; - } - this._startTime += durationAndDelay * completeCount; + return true; }; Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { for (var property in _valuesEnd) { diff --git a/package.json b/package.json index 31072b6c..c5d9bd8b 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,10 @@ "tsc": "tsc", "tsc-watch": "tsc --watch", "examples": "npx serve .", - "test": "npm run build && npm run format-check && npm run test-unit", + "test": "npm run build && npm run test-lint && npm run test-unit", "test-unit": "nodeunit test/unit/nodeunitheadless.cjs", - "format-check": "npm run prettier -- --check", - "format": "npm run prettier -- --write", + "test-lint": "npm run prettier -- --check", + "lint": "npm run prettier -- --write", "prettier": "prettier .", "prepare": "npm run build", "version": "npm test && git add .", diff --git a/src/Tween.ts b/src/Tween.ts index 0c73854a..2cc49bb0 100644 --- a/src/Tween.ts +++ b/src/Tween.ts @@ -400,6 +400,8 @@ export class Tween { update(time = now(), autoStart = true): boolean { if (this._isPaused) return true + let property + const endTime = this._startTime + this._duration if (!this._goToEnd && !this._isPlaying) { @@ -433,106 +435,87 @@ export class Tween { const durationAndDelay = this._duration + (this._repeatDelayTime ?? this._delayTime) const totalTime = this._duration + this._repeat * durationAndDelay - const elapsed = this._calculateElapsedPortion(elapsedTime, durationAndDelay, totalTime) - const value = this._easingFunction(elapsed) + const calculateElapsedPortion = () => { + if (this._duration === 0) return 1 + if (elapsedTime > totalTime) { + return 1 + } - const status = this._calculateCompletionStatus(elapsedTime, durationAndDelay) + const timesRepeated = Math.trunc(elapsedTime / durationAndDelay) + const timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay + // TODO use %? + // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay - if (status === 'repeat') { - // the current update is happening after the instant the tween repeated - this._processRepetition(elapsedTime, durationAndDelay) + const portion = Math.min(timeIntoCurrentRepeat / this._duration, 1) + if (portion === 0 && elapsedTime === this._duration) { + return 1 + } + return portion } + const elapsed = calculateElapsedPortion() + const value = this._easingFunction(elapsed) + // properties transformations this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value) - if (status === 'about-to-repeat') { - // the current update is happening at the exact instant the tween is going to repeat - // the values should match the end of the tween, not the beginning, - // that's why _processRepetition happens after _updateProperties - this._processRepetition(elapsedTime, durationAndDelay) - } - if (this._onUpdateCallback) { this._onUpdateCallback(this._object, elapsed) } - if (status === 'repeat' || status === 'about-to-repeat') { - if (this._onRepeatCallback) { - this._onRepeatCallback(this._object) - } - - this._onEveryStartCallbackFired = false - } else if (status === 'completed') { - this._isPlaying = false - - if (this._onCompleteCallback) { - this._onCompleteCallback(this._object) - } + if (this._duration === 0 || elapsedTime >= this._duration) { + if (this._repeat > 0) { + const completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat) + if (isFinite(this._repeat)) { + this._repeat -= completeCount + } - for (let i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration, false) - } - } - return status !== 'completed' - } + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { + this._valuesStartRepeat[property] = + // eslint-disable-next-line + // @ts-ignore FIXME? + this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]) + } - private _calculateElapsedPortion(elapsedTime: number, durationAndDelay: number, totalTime: number) { - if (this._duration === 0 || elapsedTime > totalTime) { - return 1 - } + if (this._yoyo) { + this._swapEndStartRepeatValues(property) + } - const timeIntoCurrentRepeat = elapsedTime % durationAndDelay + this._valuesStart[property] = this._valuesStartRepeat[property] + } - const portion = Math.min(timeIntoCurrentRepeat / this._duration, 1) - if (portion === 0 && elapsedTime !== 0 && elapsedTime % this._duration === 0) { - return 1 - } - return portion - } + if (this._yoyo) { + this._reversed = !this._reversed + } - private _calculateCompletionStatus(elapsedTime: number, durationAndDelay: number) { - if (this._duration !== 0 && elapsedTime < this._duration) { - return 'playing' - } + this._startTime += durationAndDelay * completeCount - if (this._repeat <= 0) { - return 'completed' - } + if (this._onRepeatCallback) { + this._onRepeatCallback(this._object) + } - if (elapsedTime === this._duration) { - return 'about-to-repeat' - } + this._onEveryStartCallbackFired = false - return 'repeat' - } + return true + } else { + if (this._onCompleteCallback) { + this._onCompleteCallback(this._object) + } - private _processRepetition(elapsedTime: number, durationAndDelay: number) { - const completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat) - if (isFinite(this._repeat)) { - this._repeat -= completeCount - } + for (let i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration, false) + } - // Reassign starting values, restart by making startTime = now - for (const property in this._valuesStartRepeat) { - const valueEnd = this._valuesEnd[property] - if (!this._yoyo && typeof valueEnd === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(valueEnd) - } + this._isPlaying = false - if (this._yoyo) { - this._swapEndStartRepeatValues(property) + return false } - - this._valuesStart[property] = this._valuesStartRepeat[property] - } - - if (this._yoyo) { - this._reversed = !this._reversed } - this._startTime += durationAndDelay * completeCount + return true } private _updateProperties( diff --git a/src/tests.ts b/src/tests.ts index 4ce30c71..6fbcd744 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -1283,46 +1283,6 @@ export const tests = { test.done() }, - 'Test repeat behaves the same with quick and slow updates'(test: Test): void { - TWEEN.removeAll() - - const makeTween = (obj: {x: number}) => new TWEEN.Tween(obj).to({x: 100}, 100).repeat(20).start(0) - - const obj1 = {x: 0} - const tween1 = makeTween(obj1) - - for (let t = 0; t <= 300; t += 25) { - tween1.update(t) - - const obj2 = {x: 0} - const tween2 = makeTween(obj2) - tween2.update(t) - test.equal(obj1.x, obj2.x, `t=${t}: ${obj1.x} === ${obj2.x}`) - } - - test.done() - }, - - 'Test repeat+delay behaves the same with quick and slow updates'(test: Test): void { - TWEEN.removeAll() - - const makeTween = (obj: {x: number}) => new TWEEN.Tween(obj).to({x: 100}, 100).delay(50).repeat(20).start(0) - - const obj1 = {x: 0} - const tween1 = makeTween(obj1) - - for (let t = 0; t <= 300; t += 25) { - tween1.update(t) - - const obj2 = {x: 0} - const tween2 = makeTween(obj2) - tween2.update(t) - test.equal(obj1.x, obj2.x, `t=${t}: ${obj1.x} === ${obj2.x}`) - } - - test.done() - }, - 'Test yoyo with repeat Infinity happens forever'(test: Test): void { TWEEN.removeAll() @@ -1449,69 +1409,6 @@ export const tests = { test.done() }, - 'Test yoyo reverses at right instant'(test: Test): void { - TWEEN.removeAll() - - const obj = {x: 0} - new TWEEN.Tween(obj).to({x: 100}, 100).repeat(1).yoyo(true).start(0) - - TWEEN.update(98) - test.equal(obj.x, 98) - - TWEEN.update(99) - test.equal(obj.x, 99) - - // Previously this would fail, the first update after 100 would happen as if yoyo=false - TWEEN.update(101) - test.equal(obj.x, 99) - - TWEEN.update(101) - test.equal(obj.x, 99) - - TWEEN.update(102) - test.equal(obj.x, 98) - - test.done() - }, - - 'Test yoyo callbacks happen on right order'(test: Test): void { - TWEEN.removeAll() - - let events: string[] = [] - const obj = {x: 0} - - new TWEEN.Tween(obj) - .to({x: 100}, 100) - .repeat(1) - .yoyo(true) - .easing(TWEEN.Easing.Linear.None) - .onUpdate(() => events.push('update')) - .onStart(() => events.push('start')) - .onEveryStart(() => events.push('everystart')) - .onRepeat(() => events.push('repeat')) - .onComplete(() => events.push('complete')) - .start(0) - - function testAndReset(expected: string[]) { - test.deepEqual(events, expected) - events = [] - } - - testAndReset([]) - TWEEN.update(99) - testAndReset(['start', 'everystart', 'update']) - TWEEN.update(101) - testAndReset(['update', 'repeat']) - TWEEN.update(150) - testAndReset(['everystart', 'update']) - TWEEN.update(199) - testAndReset(['update']) - TWEEN.update(201) - testAndReset(['update', 'complete']) - - test.done() - }, - 'Test TWEEN.Tween.stopChainedTweens()'(test: Test): void { const t = new TWEEN.Tween({}), t2 = new TWEEN.Tween({}) diff --git a/test/unit/nodeunit.html b/test/unit/nodeunit.html index 130bf914..69c7595b 100644 --- a/test/unit/nodeunit.html +++ b/test/unit/nodeunit.html @@ -7,13 +7,6 @@ - -