From 4cb1470c05094e0c489c84868fa0a7ff95c433e3 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sun, 6 Jan 2019 00:04:38 -0500 Subject: [PATCH] refactor bidirectional transitions --- .../render-dom/wrappers/Element/index.ts | 12 +- src/internal/keyed-each.js | 1 + src/internal/style_manager.js | 3 - src/internal/transitions.js | 164 +++++++----------- .../transition-css-duration/_config.js | 2 + 5 files changed, 73 insertions(+), 109 deletions(-) diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 436716e4a7..31c8b1ca49 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -593,24 +593,18 @@ export default class ElementWrapper extends Wrapper { const fn = component.qualify(intro.name); block.builders.intro.addConditional(`@intros.enabled`, deindent` - if (${name}) ${name}.invalidate(); - @add_render_callback(() => { if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true); - ${name}.run(1, () => { - ${name} = null; - }); + ${name}.run(1); }); `); block.builders.outro.addBlock(deindent` if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false); - ${name}.run(0, () => { - ${name} = null; - }); + ${name}.run(0); `); - block.builders.destroy.addConditional('detach', `if (${name}) ${name}.abort();`); + block.builders.destroy.addConditional('detach', `if (${name}) ${name}.end();`); } else { diff --git a/src/internal/keyed-each.js b/src/internal/keyed-each.js index 8429e2961d..b1094e0a82 100644 --- a/src/internal/keyed-each.js +++ b/src/internal/keyed-each.js @@ -52,6 +52,7 @@ export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic var did_move = {}; function insert(block) { + console.log(`inserting`, block.key); block[intro_method](node, next); lookup[block.key] = block; next = block.first; diff --git a/src/internal/style_manager.js b/src/internal/style_manager.js index 0ec992025e..1e73d37b0a 100644 --- a/src/internal/style_manager.js +++ b/src/internal/style_manager.js @@ -41,8 +41,6 @@ export function create_rule(a, b, duration, ease, fn) { } export function delete_rule(node, name) { - console.log(`delete ${name} from ${node.textContent}`, { active }); - node.style.animation = node.style.animation .split(', ') .filter(anim => anim.indexOf(name) < 0) @@ -55,7 +53,6 @@ export function clear_rules() { requestAnimationFrame(() => { if (active) return; let i = stylesheet.cssRules.length; - console.log(`clear_rules ${i}`); while (i--) stylesheet.deleteRule(i); current_rules = {}; }); diff --git a/src/internal/transitions.js b/src/internal/transitions.js index 0d65f4d8b7..e033453836 100644 --- a/src/internal/transitions.js +++ b/src/internal/transitions.js @@ -173,156 +173,126 @@ export function create_out_transition(node, fn, params) { export function create_bidirectional_transition(node, fn, params, intro) { let config = fn(node, params); - let ready = !intro; let t = intro ? 0 : 1; - let running = false; let running_program = null; let pending_program = null; let animation_name = null; function clear_animation() { if (animation_name) delete_rule(node, animation_name); - animation_name = null; } - function start(program, duration, easing) { - node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`)); - - program.a = t; - program.d = program.b - t; - program.duration = duration * Math.abs(program.d); - program.end = program.start + program.duration; - - if (config.css) { - clear_animation(); - animation_name = create_rule(t, program.b, program.duration, easing, config.css); - - node.style.animation = (node.style.animation ? ', ' : '') + `${animation_name} ${program.duration}ms linear 1 forwards`; - } - - running_program = program; - pending_program = null; - } - - function done() { - const program = running_program; - running_program = null; - - t = program.b; - - if (config.tick) config.tick(t, 1 - t); - - node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}end`)); - - if (!program.b && !program.invalidated) { - program.group.callbacks.push(() => { - program.callback(); - clear_animation(); - }); - - if (--program.group.remaining === 0) { - program.group.callbacks.forEach(run); - } - } else { - clear_animation(); - } - - running = !!pending_program; + function init(program, duration) { + const d = program.b - t; + duration *= Math.abs(d); + + return { + a: t, + b: program.b, + d, + duration, + start: program.start, + end: program.start + duration, + group: program.group + }; } - function go(b, callback) { + function go(b) { const { delay = 0, duration = 300, - easing = linear + easing = linear, + tick = noop, + css } = config; const program = { start: window.performance.now() + delay, - b, - callback + b }; - if (!ready) { - if (config.css && delay) { - // TODO can we just use the normal rule, but delay it? - animation_name = create_rule(0, 1, 1, linear, () => config.css(0, 1)); - node.style.animation = (node.style.animation ? ', ' : '') + `${animation_name} 9999s linear 1 both`; - } - - if (config.tick) config.tick(0, 1); - ready = true; - } - if (!b) { program.group = outros; outros.remaining += 1; } - if (delay) { + if (running_program) { pending_program = program; } else { - start(program, duration, easing); - } + // if this is an intro, and there's a delay, we need to do + // and initial tick and/or apply CSS animation immediately + if (css) { + animation_name = create_rule(t, b, duration, easing, css); + node.style.animation = (node.style.animation ? `${node.style.animation}, ` : '') + `${animation_name} ${duration}ms linear ${delay}ms 1 both`; + } - if (!running) { - running = true; + if (b) tick(0, 1); + + running_program = init(program, duration); + node.dispatchEvent(new window.CustomEvent(`${running_program.b ? 'intro' : 'outro'}start`)); loop(now => { - if (running_program && now >= running_program.end) { - done(); - } + if (pending_program && now > pending_program.start) { + running_program = init(pending_program, duration); + pending_program = null; + + node.dispatchEvent(new window.CustomEvent(`${running_program.b ? 'intro' : 'outro'}start`)); - if (pending_program && now >= pending_program.start) { - start(pending_program, duration, easing); + if (css) { + clear_animation(); + animation_name = create_rule(t, running_program.b, running_program.duration, easing, config.css); + + node.style.animation = (node.style.animation ? ', ' : '') + `${animation_name} ${running_program.duration}ms linear 1 forwards`; + } } - if (running) { - if (running_program) { + if (running_program) { + if (now >= running_program.end) { + tick(t = running_program.b, 1 - t); + node.dispatchEvent(new window.CustomEvent(`${running_program.b ? 'intro' : 'outro'}end`)); + + if (!pending_program) { + // we're done + if (running_program.b) { + // intro — we can tidy up immediately + clear_animation(); + } else { + // outro — needs to be coordinated + if (!--running_program.group.remaining) run_all(running_program.group.callbacks); + } + } + + running_program = null; + } + + else if (now >= running_program.start) { const p = now - running_program.start; t = running_program.a + running_program.d * easing(p / running_program.duration); - if (config.tick) config.tick(t, 1 - t); + tick(t, 1 - t); } - - return true; } + + return !!(running_program || pending_program); }); } } return { - run(b, callback = noop) { + run(b) { if (typeof config === 'function') { wait().then(() => { config = config(); - go(b, callback); + go(b); }); } else { - go(b, callback); + go(b); } }, - abort(reset) { - if (reset) { - // if an outro was aborted by an intro, we need - // to reset the node to its initial state - if (config.tick) config.tick(1, 0); - } - + end() { clear_animation(); - running_program = pending_program = null; - running = false; - }, - - invalidate() { - // invalidation happens when a (bidirectional) outro is interrupted by an - // intro — callbacks should not fire, as that would cause the nodes to - // be removed from the DOM - if (running_program) { - running_program.invalidated = true; - } } }; } \ No newline at end of file diff --git a/test/runtime/samples/transition-css-duration/_config.js b/test/runtime/samples/transition-css-duration/_config.js index ed25357681..28a78944bb 100644 --- a/test/runtime/samples/transition-css-duration/_config.js +++ b/test/runtime/samples/transition-css-duration/_config.js @@ -4,8 +4,10 @@ export default { const div = target.querySelector('div'); raf.tick(25); + component.visible = false; + raf.tick(26); assert.ok(~div.style.animation.indexOf('25ms')); }, };