motion complete

pull/4742/head
pushkine 5 years ago
parent 0d9d3c53d1
commit a75fc80f4c

@ -72,7 +72,7 @@
"c8": "^7.1.1", "c8": "^7.1.1",
"code-red": "0.1.1", "code-red": "0.1.1",
"codecov": "^3.6.5", "codecov": "^3.6.5",
"css-tree": "1.0.0-alpha39", "css-tree": "1.0.0-alpha22",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",

@ -116,7 +116,7 @@ export function init(
if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = val))) { if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = val))) {
if (i in $$.bound) $$.bound[i](val); if (i in $$.bound) $$.bound[i](val);
if (ready) { if (ready) {
if (!~$$.dirty) { if (!~$$.dirty[0]) {
schedule_update(component); schedule_update(component);
$$.dirty.fill(0); $$.dirty.fill(0);
} }

@ -1,9 +1,9 @@
import { noop } from './utils'; import { noop } from './utils';
export const resolved_promise = Promise.resolve();
export const is_client = typeof window !== 'undefined'; export const is_client = typeof window !== 'undefined';
export const is_iframe = is_client && window.location !== window.parent.location; export const is_iframe = is_client && window.location !== window.parent.location;
export let now = is_client ?()=> performance.now() :()=> Date.now(); export let now = is_client ? performance.now.bind(performance) : Date.now.bind(Date);
export let raf = is_client ? window.requestAnimationFrame : noop; export let raf = is_client ? window.requestAnimationFrame : noop;

@ -6,103 +6,106 @@ type TaskCanceller = () => void;
let next_frame: Array<TaskCallback> = []; let next_frame: Array<TaskCallback> = [];
let running_frame: Array<TaskCallback> = []; let running_frame: Array<TaskCallback> = [];
function run(t: number) { let next_frame_length = 0;
const run = (t: number) => {
t = now();
[running_frame, next_frame] = [next_frame, running_frame]; [running_frame, next_frame] = [next_frame, running_frame];
let next_frame_tasks = 0; for (let i = (next_frame_length = 0), j = running_frame.length, v; i < j; i++) {
for (let i = 0, j = running_frame.length, v; i < j; i++) { if ((v = running_frame[i])(t)) {
if ((v = running_frame[i])(t)) next_frame[next_frame_tasks++] = v; next_frame[next_frame_length++] = v;
} }
running_frame.length = 0;
if (next_frame_tasks) raf(run);
} }
running_frame.length = 0;
if (next_frame_length) raf(run);
else console.log('ended loop');
};
type TimeoutTask = { t: number; c: (now: number) => void }; type TimeoutTask = { timestamp: number; callback: (now: number) => void };
const timed_tasks: Array<TimeoutTask> = []; const timed_tasks: Array<TimeoutTask> = [];
const pending_insert_timed: Array<TimeoutTask> = []; const pending_insert_timed: Array<TimeoutTask> = [];
let running_timed = false; let running_timed = false;
let pending_inserts = false; let pending_inserts = false;
function run_timed(now: number) { const run_timed = (now: number) => {
/* Runs every timed out task */ /* Runs every timed out task */
let last_index = timed_tasks.length - 1; let last_index = timed_tasks.length - 1;
while (last_index >= 0 && now >= timed_tasks[last_index].t) { while (last_index >= 0 && now >= timed_tasks[last_index].timestamp) timed_tasks[last_index--].callback(now);
timed_tasks[last_index--].c(now);
}
if (pending_inserts) { if (pending_inserts) {
for ( for (
let i = 0, j = last_index, this_task: TimeoutTask, that_task: TimeoutTask; let i = 0, j = last_index, this_task: TimeoutTask, that_task: TimeoutTask;
i < pending_insert_timed.length; i < pending_insert_timed.length;
i++ i++
) { )
if (now >= (this_task = pending_insert_timed[i]).t) { if (now >= (this_task = pending_insert_timed[i]).timestamp) this_task.callback(now);
this_task.c(now); else {
} else { /* moves each task up until this_task.timestamp > task.timestamp */
for (j = last_index; j > 0 && this_task.t > (that_task = timed_tasks[j]).t; j--) { for (j = last_index; j > 0 && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--)
/* move each task up until this_task.timestamp > task.timestamp */
timed_tasks[j + 1] = that_task; timed_tasks[j + 1] = that_task;
}
timed_tasks[j] = this_task; timed_tasks[j] = this_task;
last_index++; last_index++;
} }
}
pending_inserts = !!(pending_insert_timed.length = 0); pending_inserts = !!(pending_insert_timed.length = 0);
} }
return (running_timed = !!(timed_tasks.length = last_index + 1)); return (running_timed = !!(timed_tasks.length = last_index));
} };
export function setAnimationTimeout(callback: () => void, timestamp: number): TaskCanceller { const unsafe_loop = (fn) => {
const task: TimeoutTask = { c: callback, t: timestamp }; if (!next_frame_length) raf(run);
next_frame[next_frame_length++] = fn;
};
export const loop = (fn) => {
let running = true;
if (!next_frame_length) raf(run);
next_frame[next_frame_length++] = (t) => !running || fn(t);
return () => void (running = false);
};
export const setAnimationTimeout = (callback: () => void, timestamp: number): TaskCanceller => {
const task: TimeoutTask = { callback, timestamp };
if (running_timed) { if (running_timed) {
pending_inserts = !!pending_insert_timed.push(task); pending_inserts = !!pending_insert_timed.push(task);
} else { } else {
unsafe_loop(run_timed);
running_timed = true;
timed_tasks.push(task); timed_tasks.push(task);
if (1 === next_frame.push(run_timed)) raf(run);
}
return () => void (task.c = noop);
} }
return () => void (task.callback = noop);
};
/** /**
* Calls function every frame with a value going from 0 to 1 * Calls function every frame with a value going from 0 to 1
*/ */
export function useTween( export const useTween = (
run: (now: number) => void, run: (now: number) => void,
stop: () => void, stop: () => void,
end_time: number, end_time: number,
duration = end_time - now() duration = end_time - now()
): TaskCanceller { ): TaskCanceller => {
let running = true; let running = true;
if ( unsafe_loop((t) => {
1 ===
next_frame.push((t) => {
if (!running) return false; if (!running) return false;
t = (end_time - t) / duration; t = 1 - (end_time - t) / duration;
if (t >= 1) return run(1), stop(), (running = false); if (t >= 1) return run(1), stop(), false;
if (t >= 0) run(t); if (t >= 0) run(t);
return running; return running;
}) });
)
raf(run);
return () => void (running = false); return () => void (running = false);
} };
/** /**
* Calls function every frame with the amount of elapsed frames * Calls function every frame with the amount of elapsed frames
*/ */
export function onEachFrame( export const onEachFrame = (
each_frame: (seconds_elapsed: number) => boolean, each_frame: (seconds_elapsed: number) => boolean,
on_stop?, on_stop?,
max_skipped_frames = 4 max_skipped_frames = 4
): TaskCanceller { ): TaskCanceller => {
max_skipped_frames *= FRAME_RATE || calc_framerate(); max_skipped_frames *= FRAME_RATE || calc_framerate();
let lastTime = now(); let lastTime = now();
let running = true; let running = true;
if ( const cancel = (t) => (on_stop && on_stop(t), false);
1 === unsafe_loop((t: number) => {
next_frame.push((t: number) => { if (!running) return cancel(t);
if (!running) return on_stop && on_stop(t), false;
if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames; if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames;
return each_frame((-lastTime + (lastTime = t)) / 1000); return each_frame((-lastTime + (lastTime = t)) / 1000) ? true : cancel(t);
}) });
)
raf(run);
return () => void (running = false); return () => void (running = false);
} };
// tests // tests
export const clear_loops = () => export const clear_loops = () =>
void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = 0); void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = next_frame_length = +(running_timed = pending_inserts = false));

@ -1,9 +1,8 @@
import { set_current_component } from './lifecycle'; import { set_current_component } from './lifecycle';
import { resolved_promise } from './environment';
const dirty_components = []; const dirty_components = [];
const resolved_promise = Promise.resolve();
let update_scheduled = false; let update_scheduled = false;
export function schedule_update(component) { export function schedule_update(component) {
dirty_components.push(component); dirty_components.push(component);

@ -1,22 +1,33 @@
import { safe_not_equal, noop } from './utils'; import { safe_not_equal, noop, subscribe } from './utils';
import { onEachFrame, useTween, loop } from './loop';
import { now } from './environment';
type Setter<T> = (value: T) => void; type Setter<T> = (value: T) => void;
type StopCallback = () => void; type StopCallback = () => void;
export type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void; export type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void;
type Subscriber<T> = (value: T) => void; type Subscriber<T> = (value: T) => void;
type Unsubscriber = () => void; type Unsubscriber = () => void;
interface Observable<T> {
subscribe(callback: Subscriber<T>): Unsubscriber;
}
type Observable_s = Observable<unknown>[] | Observable<unknown>;
type Deriver<T> = (values: any, setter?: Setter<T>) => void | (() => void) | T;
/**
* Internal Svelte Observable
*/
export class Store<T> { export class Store<T> {
static update_queue: Store<any>[] = []; static value_queue: Store<any>['value'][] = [];
static update_queue: Store<any>['subscribers'][] = [];
static is_flushing = false; static is_flushing = false;
static flush(store: Store<any>) { static flush(store: Store<any>) {
this.update_queue.push(store); this.value_queue.push(store.value);
this.update_queue.push([...store.subscribers]);
if (this.is_flushing) return; if (this.is_flushing) return;
this.is_flushing = true; this.is_flushing = true;
for (let i = 0, j = 0, subscribers, value; i < this.update_queue.length; i++) { for (let i = 0, j = 0, subscribers, value; i < this.update_queue.length; i++)
for (j = 0, { subscribers, value } = this.update_queue[i]; j < subscribers.length; j++) { for (j = 0, subscribers = this.update_queue[i], value = this.value_queue[i]; j < subscribers.length; j++)
subscribers[j].r(value); subscribers[j].run(value);
} this.update_queue.length = this.value_queue.length = +(this.is_flushing = false);
}
this.update_queue.length = +(this.is_flushing = false);
} }
value: T; value: T;
has_subscribers = false; has_subscribers = false;
@ -27,21 +38,32 @@ export class Store<T> {
set(next_value: T) { set(next_value: T) {
this.value = next_value; this.value = next_value;
if (!this.has_subscribers) return; if (!this.has_subscribers) return;
for (let i = 0; i < this.subscribers.length; i++) this.subscribers[i].i(); for (let i = 0; i < this.subscribers.length; i++) this.subscribers[i].invalidate();
Store.flush(this); Store.flush(this);
} }
subscribe(run: Subscriber<T>, invalidate = noop) { subscribe(run: Subscriber<T>, invalidate = noop) {
const subscriber = { r: run, i: invalidate }; const subscriber = { run, invalidate };
this.subscribers.push(subscriber); this.subscribers.push(subscriber);
run(this.value), (this.has_subscribers = true); run(this.value), (this.has_subscribers = true);
return this.unsubscribe.bind(this, subscriber) as Unsubscriber; return this.unsubscribe.bind(this, subscriber) as Unsubscriber;
} }
unsubscribe(subscriber) { unsubscribe(subscriber) {
this.subscribers.splice(this.subscribers.indexOf(subscriber), 1); const index = this.subscribers.indexOf(subscriber);
if (~index) {
if (Store.is_flushing) subscriber.run = subscriber.invalidate = noop;
this.subscribers.splice(index, 1);
this.has_subscribers = !!this.subscribers.length; this.has_subscribers = !!this.subscribers.length;
return true;
}
return false;
} }
} }
export class Writable<T> extends Store<T> { /**
* like Store but
* + StartStopNotifier
* + update function
*/
class StartStopWritable<T> extends Store<T> {
start: StartStopNotifier<T>; start: StartStopNotifier<T>;
stop = noop; stop = noop;
constructor(initial: T, startStopNotifier: StartStopNotifier<T>) { constructor(initial: T, startStopNotifier: StartStopNotifier<T>) {
@ -49,17 +71,178 @@ export class Writable<T> extends Store<T> {
this.start = startStopNotifier || noop; this.start = startStopNotifier || noop;
} }
subscribe(run, invalidate) { subscribe(run, invalidate) {
// *must* run *after* first subscription ?
if (!super.has_subscribers) this.stop = this.start(this.set.bind(this)) || noop; if (!super.has_subscribers) this.stop = this.start(this.set.bind(this)) || noop;
return super.subscribe(run, invalidate); return super.subscribe(run, invalidate);
} }
set(next_value: T) { set(next_value: T) {
if (this.stop && safe_not_equal(super.value, next_value)) super.set(next_value); if (this.stop) super.set(next_value);
} }
update(fn) { update(fn) {
this.set(fn(this.value)); this.set(fn(this.value));
} }
unsubscribe(subscriber) { unsubscribe(subscriber) {
super.unsubscribe(subscriber); if (super.unsubscribe(subscriber)) {
if (!super.has_subscribers && this.stop) this.stop(), (this.stop = null); if (!this.has_subscribers) this.stop();
return true;
}
return false;
}
}
/**
* StartStopWritable but
* + safe_not_equal
*/
export class Writable<T> extends StartStopWritable<T> {
set(next_value: T) {
if (safe_not_equal(this.value, next_value)) super.set(next_value);
}
}
export class Derived<S extends Observable_s, D extends Deriver<T>, T> extends StartStopWritable<T> {
cleanup = noop;
target;
deriver;
set: (value_s: unknown | unknown[]) => void;
constructor(stores: S, deriver: D, initial_value?: T) {
super(
initial_value,
Array.isArray(stores)
? (_set) => {
let l = stores.length;
let pending = 1 << l;
const values = new Array(l);
const unsubs = stores.map((store, i) =>
subscribe(
store,
(v) => void ((values[i] = v), !(pending &= ~(1 << i)) && this.set(values)),
() => void (pending |= 1 << i)
)
);
if (!(pending &= ~(1 << l))) this.set(values);
return () => {
while (l--) unsubs[l]();
this.cleanup();
};
}
: (_set) => ((unsub) => void (unsub(), this.cleanup())).bind(this, subscribe(stores, this.set))
);
this.target = stores;
this.set =
// deriver defines < 2 arguments ?
deriver.length < 2
? // return value is store value
(v) => void super.set(deriver(v) as T)
: // return value is cleanup | void, store value is set manually
(v) =>
void (this.cleanup(),
typeof (this.cleanup = deriver(v, super.set.bind(this)) as () => void) !== 'function' &&
(this.cleanup = noop));
}
}
export type initCreateMotionTick<T> = (set: (value: T) => void) => createMotionTick<T>;
export type createMotionTick<T> = (prev_value: T, next_value: T) => SpringTick<T>;
export type SpringTick<T> = (current_value: T, elapsed: number, dt: number) => boolean;
export type TweenTick = (t: number) => boolean;
/** applies motion fn to every leaf of any object */
function parseStructure<T>(obj: T, schema: initCreateMotionTick<T>): initCreateMotionTick<T> {
const isArray = Array.isArray(obj);
if (typeof obj === 'object' && obj !== null && (isArray || Object.prototype === Object.getPrototypeOf(obj))) {
const keys = Object.keys(obj);
let i = 0,
l = keys.length,
k = '',
createTickers = keys.map((key) => parseStructure(obj[key], schema)((next_value) => (obj[key] = next_value))),
tickers = new Array(l),
pending = 0;
const target = { ...obj };
//@ts-ignore
obj = isArray ? [...obj] : { ...obj };
return (set) => (_from_value, to_value) => {
for (k in to_value) if (to_value[k] !== obj[k]) target[k] = to_value[k];
for (i = 0; i < l; i++) (pending |= 1 << i), (tickers[i] = createTickers[i](obj[keys[i]], target[keys[i]]));
return (_current, elapsed, dt) => {
for (i = 0; i < l; i++) if (pending & (1 << i) && !tickers[i](obj[keys[i]], elapsed, dt)) pending &= ~(1 << i);
//@ts-ignore
set(isArray ? [...obj] : { ...obj });
return !!pending;
};
};
}
return schema;
}
abstract class MotionStore<T> extends Store<T> {
running = false;
cancel = noop;
initCreateTicker: initCreateMotionTick<T>;
createTicker: createMotionTick<T>;
tick;
constructor(value: T, startSetTick: initCreateMotionTick<T>) {
super(value);
this.createTicker = parseStructure(value, (this.initCreateTicker = startSetTick))(super.set.bind(this));
}
set(next_value: T) {
const this_id = ++this.uidRunning;
this.clearStateSubscribers(false);
//@ts-ignore
if (!this.value && this.value !== 0) {
this.setImmediate(next_value);
} else {
this.tick = this.createTicker(this.value, next_value);
this.loop(() => this.clearStateSubscribers(true));
this.running = true;
}
return {
then: (resolve, reject) => {
const stop = (has_ended) => (this.uidRunning === this_id ? resolve : reject)(has_ended);
if (!this.running || this_id !== this.uidRunning) stop(true);
else this.onCompletionSubscribers.push(stop);
},
};
}
abstract loop(stop): void;
setImmediate(value) {
this.createTicker = parseStructure(value, this.initCreateTicker)(super.set.bind(this));
super.set((this.value = value));
if (this.running) this.cancel();
this.running = false;
}
onCompletionSubscribers = [];
onRestSubscribers = [];
uidRunning = 0;
onRest(callback: Subscriber<void>) {
this.onRestSubscribers.push(callback);
return () => {
const index = this.onRestSubscribers.indexOf(callback);
if (~index) this.onRestSubscribers[index] = noop;
};
}
clearStateSubscribers(has_ended: boolean) {
let i = 0,
l = this.onRestSubscribers.length;
if (has_ended) {
this.running = false;
if (l)
for (; i < this.onRestSubscribers.length; i++) {
this.onRestSubscribers[i]();
if (this.onRestSubscribers[i] === noop) this.onRestSubscribers.splice(i--, 1);
}
}
for (i = 0, l = this.onCompletionSubscribers.length; i < l; i++) this.onCompletionSubscribers[i](has_ended);
this.onCompletionSubscribers.length = 0;
}
}
export class SpringMotion<T> extends MotionStore<T> {
elapsed = 0.0;
tick: SpringTick<T>;
loop(stop) {
this.elapsed = 0.0;
if (!this.running) this.cancel = onEachFrame((dt) => this.tick(this.value, (this.elapsed += dt), dt), stop);
}
}
export class TweenMotion<T> extends MotionStore<T> {
tick: TweenTick;
loop(stop) {
if (this.running) this.cancel();
this.cancel = loop((t) => this.tick(t) || (stop(), false));
} }
} }

@ -26,7 +26,7 @@ interface ExtendedDoc extends Document {
const active_documents = new Set<ExtendedDoc>(); const active_documents = new Set<ExtendedDoc>();
let running_animations = 0; let running_animations = 0;
function add_rule(node, name, rule): [CSSStyleSheet, Set<string>] { function add_rule(node): [CSSStyleSheet, Set<string>] {
const { ownerDocument } = node; const { ownerDocument } = node;
if (!active_documents.has(ownerDocument)) { if (!active_documents.has(ownerDocument)) {
active_documents.add(ownerDocument); active_documents.add(ownerDocument);
@ -52,7 +52,7 @@ const gen = (t, step, css) => {
}; };
export function animate_css(css: (t: number) => string, node: HTMLElement, duration: number, t = 0) { export function animate_css(css: (t: number) => string, node: HTMLElement, duration: number, t = 0) {
const [name, rule] = gen(t, duration / (FRAME_RATE || calc_framerate()), css); const [name, rule] = gen(t, duration / (FRAME_RATE || calc_framerate()), css);
const [stylesheet, rules] = add_rule(node, rule); const [stylesheet, rules] = add_rule(node);
if (!rules.has(name)) { if (!rules.has(name)) {
rules.add(name); rules.add(name);
stylesheet.insertRule(rule, stylesheet.cssRules.length); stylesheet.insertRule(rule, stylesheet.cssRules.length);

@ -1,133 +0,0 @@
// import { Readable, writable } from 'svelte/store';
// import { assign, loop, now, Task } from 'svelte/internal';
// import { linear } from 'svelte/easing';
// import { is_date } from './utils';
// function get_interpolator(a, b) {
// if (a === b || a !== a) return () => a;
// const type = typeof a;
// if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
// throw new Error('Cannot interpolate values of different type');
// }
// if (Array.isArray(a)) {
// const arr = b.map((bi, i) => {
// return get_interpolator(a[i], bi);
// });
// return t => arr.map(fn => fn(t));
// }
// if (type === 'object') {
// if (!a || !b) throw new Error('Object cannot be null');
// if (is_date(a) && is_date(b)) {
// a = a.getTime();
// b = b.getTime();
// const delta = b - a;
// return t => new Date(a + t * delta);
// }
// const keys = Object.keys(b);
// const interpolators = {};
// keys.forEach(key => {
// interpolators[key] = get_interpolator(a[key], b[key]);
// });
// return t => {
// const result = {};
// keys.forEach(key => {
// result[key] = interpolators[key](t);
// });
// return result;
// };
// }
// if (type === 'number') {
// const delta = b - a;
// return t => a + t * delta;
// }
// throw new Error(`Cannot interpolate ${type} values`);
// }
// interface Options<T> {
// delay?: number;
// duration?: number | ((from: T, to: T) => number);
// easing?: (t: number) => number;
// interpolate?: (a: T, b: T) => (t: number) => T;
// }
// type Updater<T> = (target_value: T, value: T) => T;
// interface Tweened<T> extends Readable<T> {
// set(value: T, opts: Options<T>): Promise<void>;
// update(updater: Updater<T>, opts: Options<T>): Promise<void>;
// }
// export function tweened<T>(value?: T, defaults: Options<T> = {}): Tweened<T> {
// const store = writable(value);
// let task: Task;
// let target_value = value;
// function set(new_value: T, opts: Options<T>) {
// if (value == null) {
// store.set(value = new_value);
// return Promise.resolve();
// }
// target_value = new_value;
// let previous_task = task;
// let started = false;
// let {
// delay = 0,
// duration = 400,
// easing = linear,
// interpolate = get_interpolator
// } = assign(assign({}, defaults), opts);
// const start = now() + delay;
// let fn;
// task = loop(now => {
// if (now < start) return true;
// if (!started) {
// fn = interpolate(value, new_value);
// if (typeof duration === 'function') duration = duration(value, new_value);
// started = true;
// }
// if (previous_task) {
// previous_task.abort();
// previous_task = null;
// }
// const elapsed = now - start;
// if (elapsed > duration) {
// store.set(value = new_value);
// return false;
// }
// // @ts-ignore
// store.set(value = fn(easing(elapsed / duration)));
// return true;
// });
// return task.promise;
// }
// return {
// set,
// update: (fn, opts: Options<T>) => set(fn(target_value, value), opts),
// subscribe: store.subscribe
// };
// }

@ -1,4 +1,5 @@
import { Store, onEachFrame } from 'svelte/internal'; import { SpringMotion, TweenMotion, now } from 'svelte/internal';
import { is_date } from './utils';
function solve_spring( function solve_spring(
prev_value: number, prev_value: number,
@ -20,62 +21,91 @@ function solve_spring(
return (t: number) => target_value - (Math.cos((f = t * dfm)) * delta + Math.sin(f) * leftover) * Math.exp(t * dm); return (t: number) => target_value - (Math.cos((f = t * dfm)) * delta + Math.sin(f) * leftover) * Math.exp(t * dm);
} }
} }
class MotionStore<T> extends Store<T> { export function spring(value, { mass = 1.0, damping = 10.0, stiffness = 100.0, precision = 0.001, soft = false } = {}) {
elapsed = 0.0; const store = new SpringMotion(value, (set) => {
running = false;
setTick: (prev_value: T, next_value: T) => (current_value: T, elapsed: number, dt: number) => boolean;
tick: (current_value: T, elapsed: number, dt: number) => boolean;
constructor(value, startSetTick) {
super(value);
this.setTick = startSetTick(super.set.bind(this));
}
set(next_value: T) {
this.elapsed = 0.0;
this.tick = this.setTick(this.value, next_value);
if (this.running) return;
else this.running = true;
onEachFrame((dt) => (this.running = this.tick(this.value, (this.elapsed += dt), dt)));
}
}
export function spring(value, params) {
const { mass = 1.0, damping = 10.0, stiffness = 100.0, precision = 0.001, soft = false } = params || {};
return new MotionStore(
value,
parseStructure(value, (set) => {
let velocity = 0.0, let velocity = 0.0,
calc; calc;
return (from_value, to_value) => { return (from_value, to_value) => {
calc = solve_spring(from_value, velocity, to_value, { mass, damping, stiffness, soft }); calc = solve_spring(from_value, velocity, to_value, obj);
return (current, elapsed, dt) => return (current, elapsed, dt) =>
precision > Math.abs((velocity = (-current + (current = calc(elapsed))) / dt)) && precision > Math.abs((velocity = (-current + (current = calc(elapsed))) / dt)) &&
precision > Math.abs(to_value - current) precision > Math.abs(to_value - current)
? (set(to_value), !!(velocity = 0.0)) ? (set(to_value), !!(velocity = 0.0))
: (set(current), true); : (set(current), true);
}; };
}) });
); const obj = {
mass,
damping,
stiffness,
precision,
soft,
set(next_value, params) {
if (params) {
if ('mass' in params) obj.mass = params.mass;
if ('damping' in params) obj.damping = params.damping;
if ('stiffness' in params) obj.stiffness = params.stiffness;
if ('precision' in params) obj.precision = params.precision;
if ('soft' in params) obj.soft = params.soft;
} }
function parseStructure(obj, schema) { return store.set(next_value);
if (typeof obj === 'object' && obj !== null) { },
const keys = Object.keys(obj); setImmediate: store.setImmediate.bind(store),
let i = 0, subscribe: store.subscribe.bind(store),
k = '', onRest: store.onRest.bind(store),
setTickers = keys.map((key) => parseStructure(obj[key], schema)((next_value) => (obj[key] = next_value))), };
tickers, return obj;
pending = 0; }
const target = { ...obj }; function tween_between(a, b) {
const isArray = Array.isArray(obj); if (a === b || a !== a) return () => a;
obj = isArray ? [...obj] : { ...obj }; else if (typeof a === 'number') {
return (set) => (_from_value, to_value) => { return (t) => a + t * (b-a);
for (k in to_value) if (to_value[k] !== obj[k]) target[k] = to_value[k]; } else if (is_date(a) && is_date(b)) {
tickers = setTickers.map((setTicker, i) => ((pending |= 1 << i), setTicker(obj[keys[i]], target[keys[i]]))); a = a.getTime();
return (_current, elapsed, dt) => { b = b.getTime();
for (i = 0; i < tickers.length; i++) const delta = b - a;
if (pending & (1 << i) && !tickers[i](obj[keys[i]], elapsed, dt)) pending &= ~(1 << i); return (t) => new Date(a + t * delta);
set(isArray ? [...obj] : { ...obj }); } else throw new Error(`Cannot interpolate ${typeof a} values`);
return !!pending; }
export function tween(
value,
{
delay: default_delay = 0,
duration: default_duration = 400,
easing: default_easing = (v) => v,
interpolate: default_interpolate = tween_between,
}
) {
let delay = default_delay,
duration = default_duration,
easing = default_easing,
interpolate = default_interpolate;
const store = new TweenMotion(value, (set) => {
let end_time = 0,
calc;
return (from_value, to_value) => {
end_time = now() + delay + duration;
calc = interpolate(from_value, to_value);
return (t) => {
t = 1 - (end_time - t) / duration;
if (t >= 1) return set(calc(easing(1))), false;
if (t >= 0) set(calc(easing(t)));
return true;
}; };
}; };
});
function set(next_value, params) {
delay = (params && params.delay) || default_delay;
duration = (params && 'duration' in params && params.duration) || default_duration;
easing = (params && params.easing) || default_easing;
interpolate = (params && params.interpolate) || default_interpolate;
return store.set(next_value);
} }
return schema; return {
set,
// update: (fn, params) => set(fn(target_value, value), params),
setImmediate: store.setImmediate.bind(store),
subscribe: store.subscribe.bind(store),
onRest: store.onRest.bind(store),
};
} }

@ -1,4 +1,4 @@
import { subscribe, noop, get_store_value, Writable, StartStopNotifier } from 'svelte/internal'; import { get_store_value, Writable, StartStopNotifier, Derived } from 'svelte/internal';
export { get_store_value as get }; export { get_store_value as get };
export function readable<T>(value: T, start: StartStopNotifier<T>) { export function readable<T>(value: T, start: StartStopNotifier<T>) {
const store = new Writable(value, start); const store = new Writable(value, start);
@ -8,38 +8,7 @@ export function writable<T>(value: T, start: StartStopNotifier<T>) {
const store = new Writable(value, start); const store = new Writable(value, start);
return { set: store.set.bind(store), update: store.update.bind(store), subscribe: store.subscribe.bind(store) }; return { set: store.set.bind(store), update: store.update.bind(store), subscribe: store.subscribe.bind(store) };
} }
export function derived<T, S>(stores: S, deriver, initial_value?: T) { export function derived(stores, deriver, initial_value?) {
let cleanup = noop; const store = new Derived(stores, deriver, initial_value);
const dispatcher = return { subscribe: store.subscribe.bind(store) };
deriver.length < 2
? (set, v) => void set(deriver(v))
: (set, v) => void (cleanup(), typeof (cleanup = deriver(v, set)) !== 'function' && (cleanup = noop));
return readable(
initial_value,
Array.isArray(stores)
? (set) => {
set = dispatcher.bind(null, set);
let l = stores.length;
let pending = 1 << l;
const values = new Array(l);
const unsubs = stores.map((store, i) =>
subscribe(
store,
// @ts-ignore
(v) => void ((values[i] = v), !(pending &= ~(1 << i)) && set(values)),
() => void (pending |= 1 << i)
)
);
// @ts-ignore
if (!(pending &= ~(1 << l))) set(values);
return () => {
while (l--) unsubs[l]();
cleanup();
};
}
: (set) => {
const unsub = subscribe(stores, dispatcher.bind(null, set));
return () => void (unsub(), cleanup());
}
);
} }

@ -1,19 +1,20 @@
import { cubicOut, cubicInOut, linear } from 'svelte/easing'; import { cubicOut, cubicInOut, linear } from 'svelte/easing';
type EasingFunction = (t: number) => number; type EasingFunction = (t: number) => number;
interface BasicConfig {
export interface TransitionConfig {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: EasingFunction;
}
interface TimeableConfig extends Omit<BasicConfig, 'duration'> {
duration?: number | ((len: number) => number);
}
export interface TransitionConfig extends BasicConfig {
css?: (t: number, u?: number) => string; css?: (t: number, u?: number) => string;
tick?: (t: number, u?: number) => void; tick?: (t: number, u?: number) => void;
} }
interface BlurParams { interface BlurParams extends BasicConfig {
delay: number;
duration: number;
easing?: EasingFunction;
amount: number; amount: number;
opacity: number; opacity: number;
} }
@ -34,13 +35,7 @@ export function blur(
}; };
} }
interface FadeParams { export function fade(node: Element, { delay = 0, duration = 400, easing = linear }: BasicConfig): TransitionConfig {
delay: number;
duration: number;
easing: EasingFunction;
}
export function fade(node: Element, { delay = 0, duration = 400, easing = linear }: FadeParams): TransitionConfig {
const o = +getComputedStyle(node).opacity; const o = +getComputedStyle(node).opacity;
return { return {
delay, delay,
@ -50,10 +45,7 @@ export function fade(node: Element, { delay = 0, duration = 400, easing = linear
}; };
} }
interface FlyParams { interface FlyParams extends BasicConfig {
delay: number;
duration: number;
easing: EasingFunction;
x: number; x: number;
y: number; y: number;
opacity: number; opacity: number;
@ -79,13 +71,7 @@ export function fly(
}; };
} }
interface SlideParams { export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut }: BasicConfig): TransitionConfig {
delay: number;
duration: number;
easing: EasingFunction;
}
export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut }: SlideParams): TransitionConfig {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const opacity = +style.opacity; const opacity = +style.opacity;
const height = parseFloat(style.height); const height = parseFloat(style.height);
@ -112,10 +98,7 @@ export function slide(node: Element, { delay = 0, duration = 400, easing = cubic
}; };
} }
interface ScaleParams { interface ScaleParams extends BasicConfig {
delay: number;
duration: number;
easing: EasingFunction;
start: number; start: number;
opacity: number; opacity: number;
} }
@ -140,11 +123,8 @@ export function scale(
}; };
} }
interface DrawParams { interface DrawParams extends TimeableConfig {
delay: number;
speed: number; speed: number;
duration: number | ((len: number) => number);
easing: EasingFunction;
} }
export function draw( export function draw(
@ -156,18 +136,10 @@ export function draw(
else if (typeof duration === 'function') duration = duration(len); else if (typeof duration === 'function') duration = duration(len);
return { delay, duration, easing, css: (t, u) => `stroke-dasharray: ${t * len} ${u * len};` }; return { delay, duration, easing, css: (t, u) => `stroke-dasharray: ${t * len} ${u * len};` };
} }
interface CrossFadeConfig extends TimeableConfig {
interface CrossfadeParams { fallback: (node: Element, params: TimeableConfig, intro: boolean) => TransitionConfig;
delay: number;
duration: number | ((len: number) => number);
easing: EasingFunction;
}
interface CrossFadeConfig extends CrossfadeParams {
fallback: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig;
}
interface MarkedCrossFadeConfig extends CrossfadeParams {
key: any;
} }
type MarkedCrossFadeConfig = TimeableConfig & { key: any };
type ElementMap = Map<string, Element>; type ElementMap = Map<string, Element>;
export function crossfade({ export function crossfade({
@ -182,7 +154,7 @@ export function crossfade({
function crossfade( function crossfade(
from_node: Element, from_node: Element,
to_node: Element, to_node: Element,
{ delay = default_delay, easing = default_easing, duration = default_duration }: CrossfadeParams { delay = default_delay, easing = default_easing, duration = default_duration }: TimeableConfig
) { ) {
const from = from_node.getBoundingClientRect(); const from = from_node.getBoundingClientRect();
const to = to_node.getBoundingClientRect(); const to = to_node.getBoundingClientRect();

Loading…
Cancel
Save