diff --git a/src/runtime/motion/spring.js b/src/runtime/motion/spring.js index 96631044ab..31d8b660f5 100644 --- a/src/runtime/motion/spring.js +++ b/src/runtime/motion/spring.js @@ -8,126 +8,121 @@ import { is_date } from './utils'; * @returns {T} */ function tick_spring(ctx, last_value, current_value, target_value) { - if (typeof current_value === 'number' || is_date(current_value)) { - // @ts-ignore - const delta = target_value - current_value; - // @ts-ignore - const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0 - const spring = ctx.opts.stiffness * delta; - const damper = ctx.opts.damping * velocity; - const acceleration = (spring - damper) * ctx.inv_mass; - const d = (velocity + acceleration) * ctx.dt; - if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) { - return target_value; // settled - } - else { - ctx.settled = false; // signal loop to keep ticking - // @ts-ignore - return is_date(current_value) ? new Date(current_value.getTime() + d) : current_value + d; - } - } - else if (Array.isArray(current_value)) { - // @ts-ignore - return current_value.map((_, i) => tick_spring(ctx, last_value[i], current_value[i], target_value[i])); - } - else if (typeof current_value === 'object') { - const next_value = {}; - for (const k in current_value) { - // @ts-ignore - next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]); - } - // @ts-ignore - return next_value; - } - else { - throw new Error(`Cannot spring ${typeof current_value} values`); - } + if (typeof current_value === 'number' || is_date(current_value)) { + // @ts-ignore + const delta = target_value - current_value; + // @ts-ignore + const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0 + const spring = ctx.opts.stiffness * delta; + const damper = ctx.opts.damping * velocity; + const acceleration = (spring - damper) * ctx.inv_mass; + const d = (velocity + acceleration) * ctx.dt; + if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) { + return target_value; // settled + } else { + ctx.settled = false; // signal loop to keep ticking + // @ts-ignore + return is_date(current_value) ? new Date(current_value.getTime() + d) : current_value + d; + } + } else if (Array.isArray(current_value)) { + // @ts-ignore + return current_value.map((_, i) => + tick_spring(ctx, last_value[i], current_value[i], target_value[i]) + ); + } else if (typeof current_value === 'object') { + const next_value = {}; + for (const k in current_value) { + // @ts-ignore + next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]); + } + // @ts-ignore + return next_value; + } else { + throw new Error(`Cannot spring ${typeof current_value} values`); + } } /** @param {T} value * @param {SpringOpts} opts * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/spring.ts-to-jsdoc").Spring} */ export function spring(value, opts = {}) { - const store = writable(value); - const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; - /** @type {number} */ - let last_time; - /** @type {Task} */ - let task; - /** @type {object} */ - let current_token; - /** @type {T} */ - let last_value = value; - /** @type {T} */ - let target_value = value; - let inv_mass = 1; - let inv_mass_recovery_rate = 0; - let cancel_task = false; - /** @param {T} new_value - * @param {SpringUpdateOpts} opts - * @returns {Promise} - */ - function set(new_value, opts = {}) { - target_value = new_value; - const token = (current_token = {}); - if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) { - cancel_task = true; // cancel any running animation - last_time = now(); - last_value = new_value; - store.set((value = target_value)); - return Promise.resolve(); - } - else if (opts.soft) { - const rate = opts.soft === true ? 0.5 : +opts.soft; - inv_mass_recovery_rate = 1 / (rate * 60); - inv_mass = 0; // infinite mass, unaffected by spring forces - } - if (!task) { - last_time = now(); - cancel_task = false; - task = loop((now) => { - if (cancel_task) { - cancel_task = false; - task = null; - return false; - } - inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); - const ctx = { - inv_mass, - opts: spring, - settled: true, - dt: ((now - last_time) * 60) / 1000 - }; - const next_value = tick_spring(ctx, last_value, value, target_value); - last_time = now; - last_value = value; - store.set((value = next_value)); - if (ctx.settled) { - task = null; - } - return !ctx.settled; - }); - } - return new Promise((fulfil) => { - task.promise.then(() => { - if (token === current_token) - fulfil(); - }); - }); - } - /** @type {Spring} */ - const spring = { - set, - update: (fn, opts) => set(fn(target_value, value), opts), - subscribe: store.subscribe, - stiffness, - damping, - precision - }; - return spring; + const store = writable(value); + const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; + /** @type {number} */ + let last_time; + /** @type {Task} */ + let task; + /** @type {object} */ + let current_token; + /** @type {T} */ + let last_value = value; + /** @type {T} */ + let target_value = value; + let inv_mass = 1; + let inv_mass_recovery_rate = 0; + let cancel_task = false; + /** @param {T} new_value + * @param {SpringUpdateOpts} opts + * @returns {Promise} + */ + function set(new_value, opts = {}) { + target_value = new_value; + const token = (current_token = {}); + if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) { + cancel_task = true; // cancel any running animation + last_time = now(); + last_value = new_value; + store.set((value = target_value)); + return Promise.resolve(); + } else if (opts.soft) { + const rate = opts.soft === true ? 0.5 : +opts.soft; + inv_mass_recovery_rate = 1 / (rate * 60); + inv_mass = 0; // infinite mass, unaffected by spring forces + } + if (!task) { + last_time = now(); + cancel_task = false; + task = loop((now) => { + if (cancel_task) { + cancel_task = false; + task = null; + return false; + } + inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); + const ctx = { + inv_mass, + opts: spring, + settled: true, + dt: ((now - last_time) * 60) / 1000 + }; + const next_value = tick_spring(ctx, last_value, value, target_value); + last_time = now; + last_value = value; + store.set((value = next_value)); + if (ctx.settled) { + task = null; + } + return !ctx.settled; + }); + } + return new Promise((fulfil) => { + task.promise.then(() => { + if (token === current_token) fulfil(); + }); + }); + } + /** @type {Spring} */ + const spring = { + set, + update: (fn, opts) => set(fn(target_value, value), opts), + subscribe: store.subscribe, + stiffness, + damping, + precision + }; + return spring; } - /** * @typedef {(target_value: T, value: T) => T} Updater * @template T @@ -137,21 +132,21 @@ export function spring(value, opts = {}) { * @property {number} inv_mass * @property {number} dt * @property {Spring} opts - * @property {boolean} settled + * @property {boolean} settled */ /** @typedef {Object} SpringOpts * @property {number} [stiffness] * @property {number} [damping] - * @property {number} [precision] + * @property {number} [precision] */ /** @typedef {Object} SpringUpdateOpts * @property {any} [hard] - * @property {string|number|boolean} [soft] + * @property {string|number|boolean} [soft] */ /** @typedef {Object} Spring * @property {(new_value:T,opts?:SpringUpdateOpts)=>Promise} set * @property {(fn:Updater,opts?:SpringUpdateOpts)=>Promise} update * @property {number} precision * @property {number} damping - * @property {number} stiffness - */ \ No newline at end of file + * @property {number} stiffness + */