refactor bidirectional transitions

pull/1971/head
Richard Harris 7 years ago
parent 765309388e
commit 4cb1470c05

@ -593,24 +593,18 @@ export default class ElementWrapper extends Wrapper {
const fn = component.qualify(intro.name); const fn = component.qualify(intro.name);
block.builders.intro.addConditional(`@intros.enabled`, deindent` block.builders.intro.addConditional(`@intros.enabled`, deindent`
if (${name}) ${name}.invalidate();
@add_render_callback(() => { @add_render_callback(() => {
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true); if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true);
${name}.run(1, () => { ${name}.run(1);
${name} = null;
});
}); });
`); `);
block.builders.outro.addBlock(deindent` block.builders.outro.addBlock(deindent`
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false); if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false);
${name}.run(0, () => { ${name}.run(0);
${name} = null;
});
`); `);
block.builders.destroy.addConditional('detach', `if (${name}) ${name}.abort();`); block.builders.destroy.addConditional('detach', `if (${name}) ${name}.end();`);
} }
else { else {

@ -52,6 +52,7 @@ export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic
var did_move = {}; var did_move = {};
function insert(block) { function insert(block) {
console.log(`inserting`, block.key);
block[intro_method](node, next); block[intro_method](node, next);
lookup[block.key] = block; lookup[block.key] = block;
next = block.first; next = block.first;

@ -41,8 +41,6 @@ export function create_rule(a, b, duration, ease, fn) {
} }
export function delete_rule(node, name) { export function delete_rule(node, name) {
console.log(`delete ${name} from ${node.textContent}`, { active });
node.style.animation = node.style.animation node.style.animation = node.style.animation
.split(', ') .split(', ')
.filter(anim => anim.indexOf(name) < 0) .filter(anim => anim.indexOf(name) < 0)
@ -55,7 +53,6 @@ export function clear_rules() {
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (active) return; if (active) return;
let i = stylesheet.cssRules.length; let i = stylesheet.cssRules.length;
console.log(`clear_rules ${i}`);
while (i--) stylesheet.deleteRule(i); while (i--) stylesheet.deleteRule(i);
current_rules = {}; current_rules = {};
}); });

@ -173,156 +173,126 @@ export function create_out_transition(node, fn, params) {
export function create_bidirectional_transition(node, fn, params, intro) { export function create_bidirectional_transition(node, fn, params, intro) {
let config = fn(node, params); let config = fn(node, params);
let ready = !intro;
let t = intro ? 0 : 1; let t = intro ? 0 : 1;
let running = false;
let running_program = null; let running_program = null;
let pending_program = null; let pending_program = null;
let animation_name = null; let animation_name = null;
function clear_animation() { function clear_animation() {
if (animation_name) delete_rule(node, animation_name); if (animation_name) delete_rule(node, animation_name);
animation_name = null;
} }
function start(program, duration, easing) { function init(program, duration) {
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`)); const d = program.b - t;
duration *= Math.abs(d);
program.a = t; return {
program.d = program.b - t; a: t,
program.duration = duration * Math.abs(program.d); b: program.b,
program.end = program.start + program.duration; d,
duration,
if (config.css) { start: program.start,
clear_animation(); end: program.start + duration,
animation_name = create_rule(t, program.b, program.duration, easing, config.css); group: program.group
};
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 go(b, callback) { function go(b) {
const { const {
delay = 0, delay = 0,
duration = 300, duration = 300,
easing = linear easing = linear,
tick = noop,
css
} = config; } = config;
const program = { const program = {
start: window.performance.now() + delay, start: window.performance.now() + delay,
b, b
callback
}; };
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) { if (!b) {
program.group = outros; program.group = outros;
outros.remaining += 1; outros.remaining += 1;
} }
if (delay) { if (running_program) {
pending_program = program; pending_program = program;
} else { } 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) { if (b) tick(0, 1);
running = true;
running_program = init(program, duration);
node.dispatchEvent(new window.CustomEvent(`${running_program.b ? 'intro' : 'outro'}start`));
loop(now => { loop(now => {
if (running_program && now >= running_program.end) { if (pending_program && now > pending_program.start) {
done(); running_program = init(pending_program, duration);
} pending_program = null;
node.dispatchEvent(new window.CustomEvent(`${running_program.b ? 'intro' : 'outro'}start`));
if (css) {
clear_animation();
animation_name = create_rule(t, running_program.b, running_program.duration, easing, config.css);
if (pending_program && now >= pending_program.start) { node.style.animation = (node.style.animation ? ', ' : '') + `${animation_name} ${running_program.duration}ms linear 1 forwards`;
start(pending_program, duration, easing); }
} }
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; const p = now - running_program.start;
t = running_program.a + running_program.d * easing(p / running_program.duration); 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 { return {
run(b, callback = noop) { run(b) {
if (typeof config === 'function') { if (typeof config === 'function') {
wait().then(() => { wait().then(() => {
config = config(); config = config();
go(b, callback); go(b);
}); });
} else { } else {
go(b, callback); go(b);
} }
}, },
abort(reset) { end() {
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);
}
clear_animation(); clear_animation();
running_program = pending_program = null; 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;
}
} }
}; };
} }

@ -4,8 +4,10 @@ export default {
const div = target.querySelector('div'); const div = target.querySelector('div');
raf.tick(25); raf.tick(25);
component.visible = false; component.visible = false;
raf.tick(26);
assert.ok(~div.style.animation.indexOf('25ms')); assert.ok(~div.style.animation.indexOf('25ms'));
}, },
}; };

Loading…
Cancel
Save