diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index e1bf02033a..9335fcaa68 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -28,24 +28,20 @@ let pending_inserts = false; const run_timed = (now: number) => { /* Runs every timed out task */ let last_index = timed_tasks.length - 1; - while (last_index >= 0 && now >= timed_tasks[last_index].timestamp) timed_tasks[last_index--].callback(now); + while (~last_index && now >= timed_tasks[last_index].timestamp) timed_tasks[last_index--].callback(now); if (pending_inserts) { - for ( - let i = 0, j = last_index, this_task: TimeoutTask, that_task: TimeoutTask; - i < pending_insert_timed.length; - i++ - ) + for (let i = 0, j = 0, this_task: TimeoutTask, that_task: TimeoutTask; i < pending_insert_timed.length; i++) if (now >= (this_task = pending_insert_timed[i]).timestamp) this_task.callback(now); else { /* moves each task up until this_task.timestamp > task.timestamp */ - for (j = last_index; j > 0 && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--) + for (j = last_index; ~j && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--) timed_tasks[j + 1] = that_task; - timed_tasks[j] = this_task; + timed_tasks[j + 1] = this_task; last_index++; } pending_inserts = !!(pending_insert_timed.length = 0); } - return (running_timed = !!(timed_tasks.length = last_index)); + return (running_timed = !!(timed_tasks.length = last_index + 1)); }; const unsafe_loop = (fn) => { if (!next_frame_length) raf(run); @@ -73,15 +69,15 @@ export const setAnimationTimeout = (callback: () => void, timestamp: number): Ta */ export const useTween = ( run: (now: number) => void, - stop: () => void, + stop: (now: number) => void, end_time: number, duration = end_time - now() ): TaskCanceller => { let running = true; unsafe_loop((t) => { if (!running) return false; - t = 1 - (end_time - t) / duration; - if (t >= 1) return run(1), stop(), false; + t = (end_time - t) / duration; + if (t >= 1) return run(1), stop(t), false; if (t >= 0) run(t); return running; }); diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index d62cc2ea4d..b1274fe8f3 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -4,14 +4,13 @@ import { resolved_promise } from './environment'; const dirty_components = []; let update_scheduled = false; -export function schedule_update(component) { +export const schedule_update = (component) => { dirty_components.push(component); if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush); -} -export function tick() { - if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush); - return resolved_promise; -} +}; +export const tick = () => + update_scheduled ? resolved_promise : ((update_scheduled = true), resolved_promise.then(flush)); + export const binding_callbacks = []; const render_callbacks = []; const seen_callbacks = new Set(); @@ -20,38 +19,55 @@ export const add_render_callback = (fn) => const flush_callbacks = []; export const add_flush_callback = (fn) => void flush_callbacks.push(fn); +const measure_callbacks = []; +export const add_measure_callback = (fn) => void measure_callbacks.push(fn); let flushing = false; export function flush() { if (flushing) return; else flushing = true; - for (; dirty_components.length; ) { + let i = 0, + j = 0, + $$, + before_update, + dirty; + + // RUN LOGIC, CANCEL STYLES + do { // update components + beforeUpdate - for (let i = 0, $$; i < dirty_components.length; i++) { + for (i = 0; i < dirty_components.length; i++) { ({ $$ } = set_current_component(dirty_components[i])); if ($$.fragment === null) continue; - const { update, before_update, dirty, after_update } = $$; - update(); - for (let j = 0; j < before_update.length; j++) before_update[j](); + $$.update(); + for (j = 0, { before_update } = $$; j < before_update.length; j++) before_update[j](); + ({ dirty } = $$); $$.dirty = [-1]; if ($$.fragment) $$.fragment.p($$.ctx, dirty); - render_callbacks.push(...after_update); + render_callbacks.push(...$$.after_update); } dirty_components.length = 0; // update bindings in reverse order - for (let i = binding_callbacks.length - 1; i >= 0; i--) binding_callbacks[i](); + i = binding_callbacks.length; + while (i--) binding_callbacks[i](); binding_callbacks.length = 0; // afterUpdate - for (let i = 0; i < render_callbacks.length; i++) render_callbacks[i](); + for (i = 0; i < render_callbacks.length; i++) render_callbacks[i](); render_callbacks.length = 0; seen_callbacks.clear(); - } + } while (dirty_components.length); + + update_scheduled = false; - for (let i = 0; i < flush_callbacks.length; i++) flush_callbacks[i](); + // STYLE MEASURE CHANGES + for (i = 0; i < measure_callbacks.length; i++) flush_callbacks.push(measure_callbacks[i]()); + measure_callbacks.length = 0; + + // APPLY STYLE CHANGES + for (i = 0; i < flush_callbacks.length; i++) flush_callbacks[i](); flush_callbacks.length = 0; - update_scheduled = flushing = false; -} \ No newline at end of file + flushing = false; +} diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 0051ba6618..f409d9c1a8 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,5 +1,5 @@ import { element } from './dom'; -import { raf } from './environment'; +import { raf, now } from './environment'; const enum SVELTE { RULE = `__svelte_`, STYLESHEET = `__svelte_stylesheet`, @@ -43,22 +43,23 @@ function hash(str: string) { while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); return hash >>> 0; } -const gen = (t, step, css) => { +const gen = (step, css) => { let rule = '{\n'; - for (; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + for (let t = 0; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; rule += `100% {${css(1)}}\n}`; const name = SVELTE.RULE + hash(rule); return [name, `@keyframes ${name} ${rule}`]; }; -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); +export function animate_css(css: (t: number) => string, node: HTMLElement, duration: number, delay = 0) { + const [name, rule] = gen(Math.max(1 / 1000, (FRAME_RATE || calc_framerate()) / duration), css); const [stylesheet, rules] = add_rule(node); if (!rules.has(name)) { rules.add(name); stylesheet.insertRule(rule, stylesheet.cssRules.length); } const previous = node.style.animation; - node.style.animation = (previous ? previous + ', ' : '') + `${duration}ms linear 0ms 1 normal both running ${name}`; + node.style.animation = + (previous ? previous + ', ' : '') + `${duration}ms linear ${delay}ms 1 normal both running ${name}`; running_animations++; return () => { const prev = (node.style.animation || '').split(', '); diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index d2b90c3d1a..8f476be7d6 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -4,12 +4,12 @@ import { setAnimationTimeout, useTween } from './loop'; import { animate_css } from './style_manager'; import { custom_event } from './dom'; import { TransitionConfig } from '../transition'; -import { add_render_callback, add_flush_callback } from './scheduler'; +import { add_measure_callback, add_render_callback } from './scheduler'; import { Fragment } from './Component'; -function startStopDispatcher(node: Element, direction: boolean) { - add_render_callback(() => node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}start`))); - return () => node.dispatchEvent(custom_event(`${!direction ? 'intro' : 'outro'}end`)); +function startStopDispatcher(node: Element, is_intro: boolean) { + node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}start`)); + return () => node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}end`)); } const outroing = new Set(); @@ -47,65 +47,73 @@ export function transition_out(block: Fragment, local?: 0 | 1, detach?: 0 | 1, c block.o(local); } -const eased = (fn: (t: number) => any, easing: (t: number) => number) => (easing ? (t: number) => fn(easing(t)) : fn); -const runner = (fn: (t0: number, t1: number) => any, reversed: boolean) => - reversed ? (t: number) => fn(1 - t, t) : (t: number) => fn(t, 1 - t); +const eased = (fn: (t: number) => any, easing: (t: number) => number, start, end) => (t: number) => + fn(start + (end - start) * easing(t)); +//easing ? (!is_intro ? (t: number) => fn(easing(t)) : (t: number) => fn(1 - easing(1 - t))) : fn; +const runner = (fn: (t0: number, t1: number) => any, is_intro: boolean) => + is_intro ? (t: number) => fn(t, 1 - t) : (t: number) => fn(1 - t, t); +const mirror = (fn, easing, is_intro) => { + const run = is_intro ? (t) => fn(1 - t, t) : (t) => fn(t, 1 - t); + return easing ? (is_intro ? (t) => run(1 - easing(1 - t)) : (t) => run(easing(t))) : run; +}; type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig; export function run_transition( node: HTMLElement, fn: TransitionFn, is_intro = true, params = {}, - reversed_from = -1 + left_duration = 0, + prev_left = 0 ): StopResetReverse { - let config = fn(node, params); + let config; let running = true; let cancel_css; let cancel_raf; let dispatch_end; let end_time; + let t; + let start_ratio; const group = outros; if (!is_intro) group.r++; - function start({ delay = 0, duration = 300, easing, tick, css }: TransitionConfig) { + const start = ({ delay = 0, duration = 300, easing, tick, css }: TransitionConfig) => { if (!running) return; - const start_time = ~reversed_from ? reversed_from : now() + delay; - end_time = start_time + duration; - if (css) - cancel_css = animate_css( - runner(eased(css, easing), is_intro), - node, - duration, - (end_time - start_time) / duration - ); + t = duration - (left_duration > 0 ? left_duration : 0); + end_time = now() + t; + start_ratio = 1 - easing((t - prev_left) / duration); + if (css) cancel_css = animate_css(eased(runner(css, is_intro), easing, start_ratio, 1), node, t, 0); dispatch_end = startStopDispatcher(node, is_intro); cancel_raf = tick - ? useTween(runner(eased(tick, easing), is_intro), stop, duration, end_time) + ? useTween(eased(runner(tick, is_intro), easing, start_ratio, 1), stop, end_time, t) : setAnimationTimeout(stop, end_time); - } - function stop(reset_reverse?: 1 | -1) { - if (!is_intro && 1 === reset_reverse && config && 'tick' in config) config.tick(1, 0); + }; + + const stop = (end_reset_reverse?: number | 1 | -1) => { if (!running) return; else running = false; if (cancel_css) cancel_css(); if (cancel_raf) cancel_raf(); - if (dispatch_end) dispatch_end(); + if (t > end_time && dispatch_end) dispatch_end(); if (!is_intro && !--group.r) for (let i = 0; i < group.c.length; i++) group.c[i](); - if (!~reset_reverse) return run_transition(node, fn, !is_intro, params, end_time); - else if (!~reversed_from) running_bidi.delete(node); - } - // @ts-ignore - if (typeof config === 'function') add_flush_callback(() => start((config = config()))); - else start(config); + if (!~end_reset_reverse) + return run_transition(node, () => config, !is_intro, params, end_time - now(), left_duration); + else if (left_duration) running_bidi.delete(node); + }; + + add_measure_callback(() => { + config = fn(node, params); + return () => start(typeof config === 'function' ? (config = config()) : config); + }); + return stop; } export type StopResetReverse = (reset_reverse?: 1 | -1) => StopResetReverse; const running_bidi: Map = new Map(); export function run_bidirectional_transition(node: HTMLElement, fn: TransitionFn, is_intro: boolean, params: any) { let cancel; - if (running_bidi.has(node)) running_bidi.set(node, (cancel = running_bidi.get(node)(-1))); + if (running_bidi.has(node) && (cancel = running_bidi.get(node)(-1))) running_bidi.set(node, cancel); else running_bidi.set(node, (cancel = run_transition(node, fn, is_intro, params, -1))); return cancel; } diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index b9b3604f91..1b727bd4ff 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -136,8 +136,3 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, export function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; } -export const minmax = (min: number, max: number) => (value: number) => (min > value ? min : value > max ? max : value); -export const clamper = (min: number, max: number) => - min !== -Infinity || max !== Infinity - ? (fn: (value: number) => any) => (value: number) => (min > (value = fn(value)) ? min : value > max ? max : value) - : (fn: (value: number) => any) => fn; diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index 837ae16e51..800e1288b2 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -58,16 +58,14 @@ export function fly( const style = getComputedStyle(node); const target_opacity = +style.opacity; const transform = style.transform === 'none' ? '' : style.transform; - const od = target_opacity * (1 - opacity); - return { delay, duration, easing, css: (t, u) => ` - transform: ${transform} translate(${(1 - t) * x}px, ${(1 - t) * y}px); - opacity: ${target_opacity - od * u};`, + transform: ${transform} translate(${u * x}px, ${u * y}px); + opacity: ${target_opacity - od * u}`, }; }