/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
2604150425000
src/motion.js
257 строк
7 KB
Kristofer Baxter
♻️ Remove Notice from JS and Markdown (#35717)
19 авг 2021, 21:21
Не верифицирован
19 авг 2021, 21:21
f564b7e
Код
Авторство
О чём код?
import {Deferred} from '#core/data-structures/promise'; import {Services} from '#service'; /** @const {function()} */ const NOOP_CALLBACK_ = function () {}; /** @const {number} */ const MIN_VELOCITY_ = 0.02; /** @const {number} */ const FRAME_CONST_ = 16.67; /** @const {number} */ const EXP_FRAME_CONST_ = Math.round(-FRAME_CONST_ / Math.log(0.95)); /** * Depreciation factor of 1/100 of a millisecond. This is how much previous * velocity is depreciated when calculating the new velocity. * @const {number} */ const VELOCITY_DEPR_FACTOR_ = FRAME_CONST_ * 2; /** * Calculates velocity for an object traveling the distance deltaV in the * time deltaTime given the previous velocity prevVelocity. The calculation * assumes a basic informational depreciation of previous velocity. * @param {number} deltaV * @param {time} deltaTime * @param {number} prevVelocity * @return {number} */ export function calcVelocity(deltaV, deltaTime, prevVelocity) { if (deltaTime < 1) { deltaTime = 1; } // Calculate speed and speed depreciation. const speed = deltaV / deltaTime; // Depreciation is simply an informational quality. It basically means: // we can't ignore the velocity we knew recently, but we'd only consider // it proportionally to how long ago we've seen it. Currently, this // depreciation factor is 1/100 of a millisecond. New average velocity is // calculated by weighing toward the new velocity and away from old // velocity based on the depreciation. const depr = 0.5 + Math.min(deltaTime / VELOCITY_DEPR_FACTOR_, 0.5); return speed * depr + prevVelocity * (1 - depr); } /** * Returns a motion process that will yield when the velocity has run down to * zerp. For each iteration, the velocity is depreciated and the coordinates * are advanced from start X/Y to the destination according to velocity * vectors. For each such iteration the callback is called with the new x and y. * @param {!Node} contextNode * @param {number} startX Start X coordinate. * @param {number} startY Start Y coordinate. * @param {number} veloX Starting X velocity. * @param {number} veloY Starting Y velocity. * @param {function(number, number):boolean} callback The callback for each * step of the deceleration motion. * @param {!./service/vsync-impl.Vsync=} opt_vsync Mostly for testing only. * @return {!Motion} */ export function continueMotion( contextNode, startX, startY, veloX, veloY, callback, opt_vsync ) { return new Motion( contextNode, startX, startY, veloX, veloY, callback, opt_vsync ).start(); } /** * Motion process that allows tracking and monitoring of the running motion. * Most importantly it exposes methods "then" and "thenAlways" that have the * semantics of a Promise and signal when the motion has completed or failed. * Additionally, it exposes the method "halt" which allows to stop/reset the * motion. * @implements {IThenable} */ export class Motion { /** * @param {!Node} contextNode Context node. * @param {number} startX Start X coordinate. * @param {number} startY Start Y coordinate. * @param {number} veloX Starting X velocity. * @param {number} veloY Starting Y velocity. * @param {function(number, number):boolean} callback The callback for each * step of the deceleration motion. * @param {!./service/vsync-impl.Vsync=} opt_vsync */ constructor(contextNode, startX, startY, veloX, veloY, callback, opt_vsync) { /** @private @const {!./service/vsync-impl.Vsync} */ this.vsync_ = opt_vsync || Services.vsyncFor(self); /** @private @const {!Node} */ this.contextNode_ = contextNode; /** @private @const */ this.callback_ = callback; /** @private {number} */ this.lastX_ = startX; /** @private {number} */ this.lastY_ = startY; /** @private {number} */ this.maxVelocityX_ = veloX; /** @private {number} */ this.maxVelocityY_ = veloY; /** @private {number} */ this.velocityX_ = 0; /** @private {number} */ this.velocityY_ = 0; const deferred = new Deferred(); /** @private {!Promise} */ this.promise_ = deferred.promise; /** @private {!Function} */ this.resolve_ = deferred.resolve; /** @private {!Function} */ this.reject_ = deferred.reject; /** @private {boolean} */ this.continuing_ = false; } /** */ start() { this.continuing_ = true; if ( Math.abs(this.maxVelocityX_) <= MIN_VELOCITY_ && Math.abs(this.maxVelocityY_) <= MIN_VELOCITY_ ) { this.fireMove_(); this.completeContinue_(true); } else { this.runContinuing_(); } return this; } /** * Halts the motion. The motion promise will be rejected since the motion * has been interrupted. */ halt() { if (this.continuing_) { this.completeContinue_(false); } } /** * Chains to the motion's promise that will resolve when the motion has * completed or will reject if motion has failed or was interrupted. * @override */ then(opt_resolve, opt_reject) { if (!opt_resolve && !opt_reject) { return this.promise_; } return this.promise_.then(opt_resolve, opt_reject); } /** * Callback for regardless whether the motion succeeds or fails. * @param {function()=} opt_callback * @return {!Promise} */ thenAlways(opt_callback) { const callback = opt_callback || NOOP_CALLBACK_; return /** @type {!Promise} */ (this.then(callback, callback)); } /** * @return {!Promise} * @private */ runContinuing_() { this.velocityX_ = this.maxVelocityX_; this.velocityY_ = this.maxVelocityY_; const boundStep = this.stepContinue_.bind(this); const boundComplete = this.completeContinue_.bind(this, true); return this.vsync_ .runAnimMutateSeries(this.contextNode_, boundStep, 5000) .then(boundComplete, boundComplete); } /** * Returns "true" to continue and "false" to stop motion process. * @param {time} timeSinceStart * @param {time} timeSincePrev * @return {boolean} * @private */ stepContinue_(timeSinceStart, timeSincePrev) { if (!this.continuing_) { return false; } this.lastX_ += timeSincePrev * this.velocityX_; this.lastY_ += timeSincePrev * this.velocityY_; if (!this.fireMove_()) { return false; } const decel = Math.exp(-timeSinceStart / EXP_FRAME_CONST_); this.velocityX_ = this.maxVelocityX_ * decel; this.velocityY_ = this.maxVelocityY_ * decel; return ( Math.abs(this.velocityX_) > MIN_VELOCITY_ || Math.abs(this.velocityY_) > MIN_VELOCITY_ ); } /** * @param {boolean} success * @private */ completeContinue_(success) { if (!this.continuing_) { return; } this.continuing_ = false; this.fireMove_(); if (success) { this.resolve_(); } else { this.reject_(); } } /** @private */ fireMove_() { return this.callback_(this.lastX_, this.lastY_); } }