reimplement single-direction transitions

pull/1971/head
Richard Harris 7 years ago
parent d48cb16217
commit 1f35487ecd

@ -220,9 +220,10 @@ export default class Block {
if (this.hasIntroMethod || this.hasOutroMethod) {
this.addVariable('#current');
this.addVariable('#mounted');
if (!this.builders.mount.isEmpty()) {
this.builders.mount.addLine(`#current = true;`);
this.builders.mount.addLine(`#current = #mounted = true;`);
}
if (!this.builders.outro.isEmpty()) {
@ -352,9 +353,8 @@ export default class Block {
} else {
properties.addBlock(deindent`
${dev ? 'i: function intro' : 'i'}(#target, anchor) {
if (#current) return;
${this.builders.intro}
this.m(#target, anchor);
if (!#mounted) this.m(#target, anchor);
},
`);
}
@ -364,8 +364,6 @@ export default class Block {
} else {
properties.addBlock(deindent`
${dev ? 'o: function outro' : 'o'}(#outrocallback) {
if (!#current) return;
${this.outros > 1 && `#outrocallback = @callAfter(#outrocallback, ${this.outros});`}
${this.builders.outro}

@ -621,27 +621,24 @@ export default class ElementWrapper extends Wrapper {
? intro.expression.render(block)
: '{}';
const fn = component.qualify(intro.name); // TODO add built-in transitions?
const fn = component.qualify(intro.name);
if (outro) {
block.builders.intro.addBlock(deindent`
if (${introName}) ${introName}.abort(1);
if (${outroName}) ${outroName}.abort(1);
block.builders.intro.addConditional(`@intros.enabled`, deindent`
@add_render_callback(() => {
if (${introName}) ${introName}.end();
${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet});
});
`);
} else {
block.builders.intro.addConditional(`@intros.enabled`, deindent`
if (!${introName}) {
@add_render_callback(() => {
${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet});
});
}
`);
}
block.builders.intro.addConditional(`@intros.enabled`, deindent`
@add_render_callback(() => {
${introName} = @create_transition(${this.var}, ${fn}, ${snippet}, true);
${introName}.run(1, () => {
${introName} = null;
});
});
`);
block.builders.outro.addBlock(deindent`
if (${introName}) ${introName}.abort();
`);
}
if (outro) {
@ -653,17 +650,16 @@ export default class ElementWrapper extends Wrapper {
const fn = component.qualify(outro.name);
block.builders.intro.addBlock(deindent`
if (${outroName}) ${outroName}.abort(1);
if (${outroName}) ${outroName}.end();
`);
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
block.builders.outro.addBlock(deindent`
${outroName} = @create_transition(${this.var}, ${fn}, ${snippet}, false);
${outroName}.run(0, #outrocallback);
${outroName} = @create_out_transition(${this.var}, ${fn}, ${snippet}, #outrocallback);
`);
block.builders.destroy.addConditional('detach', `if (${outroName}) ${outroName}.abort();`);
block.builders.destroy.addConditional('detach', `if (${outroName}) ${outroName}.end();`);
}
}
}

@ -27,6 +27,7 @@ export default class Tag extends Wrapper {
if (this.node.shouldCache) block.addVariable(value, snippet);
if (dependencies.size) {
// TODO can we check `#current` at the top of the `update` method, instead of in individual updates?
const changedCheck = (
(block.hasOutros ? `!#current || ` : '') +
[...dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')

@ -28,7 +28,7 @@ export function animate(node, from, fn, params) {
if (css) {
if (delay) node.style.cssText = cssText;
name = create_rule({ a: 0, b: 1, d: 1, duration }, easing, css);
name = create_rule(0, 1, duration, easing, css);
node.style.animation = (node.style.animation || '')
.split(', ')

@ -13,12 +13,12 @@ function hash(str) {
return hash >>> 0;
}
export function create_rule({ a, b, d, duration }, ease, fn) {
export function create_rule(a, b, duration, ease, fn) {
const step = 16.666 / duration;
let keyframes = '{\n';
for (let p = 0; p <= 1; p += step) {
const t = a + d * ease(p);
const t = a + (b - a) * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
}
@ -41,6 +41,8 @@ export function create_rule({ a, b, d, 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)
@ -53,6 +55,7 @@ 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 = {};
});

@ -1,4 +1,4 @@
import { identity as linear, noop, run } from './utils.js';
import { identity as linear, noop, run, run_all } from './utils.js';
import { loop } from './loop.js';
import { create_rule, delete_rule } from './style_manager.js';
@ -24,6 +24,127 @@ export function group_outros() {
};
}
export function create_in_transition(node, fn, params) {
let config = fn(node, params);
let running = true;
let animation_name;
function cleanup() {
delete_rule(node, animation_name);
}
wait().then(() => {
if (typeof config === 'function') config = config();
const {
delay = 0,
duration = 300,
easing = linear,
tick = noop,
css
} = config;
if (css) {
animation_name = create_rule(0, 1, duration, easing, css);
node.style.animation = (node.style.animation ? ', ' : '') + `${animation_name} ${duration}ms linear ${delay}ms 1 both`;
}
tick(0, 1);
const start_time = window.performance.now() + delay;
const end_time = start_time + duration;
loop(now => {
if (running) {
if (now > end_time) {
tick(1, 0);
cleanup();
return running = false;
}
if (now > start_time) {
const t = easing((now - start_time) / duration);
tick(t, 1 - t);
}
}
return running;
});
});
return {
end() {
if (running) {
cleanup();
running = false;
}
}
};
}
export function create_out_transition(node, fn, params, callback) {
let config = fn(node, params);
let running = true;
let animation_name;
const group = outros;
group.remaining += 1;
group.callbacks.push(callback); // TODO do we even need multiple callbacks? can we just have the one?
wait().then(() => {
if (typeof config === 'function') config = config();
const {
delay = 0,
duration = 300,
easing = linear,
tick = noop,
css
} = config;
if (css) {
animation_name = create_rule(1, 0, duration, easing, css);
node.style.animation += (node.style.animation ? ', ' : '') + `${animation_name} ${duration}ms linear ${delay}ms 1 both`;
}
const start_time = window.performance.now() + delay;
const end_time = start_time + duration;
loop(now => {
if (running) {
if (now > end_time) {
tick(0, 1);
if (!--group.remaining) {
// this will result in `end()` being called,
// so we don't need to clean up here
run_all(group.callbacks);
}
return false;
}
if (now > start_time) {
const t = easing((now - start_time) / duration);
tick(1 - t, t);
}
}
return running;
});
});
return {
end() {
if (running) {
delete_rule(node, animation_name);
running = false;
}
}
};
}
export function create_transition(node, fn, params, intro) {
let config = fn(node, params);
@ -40,17 +161,17 @@ export function create_transition(node, fn, params, intro) {
animation_name = null;
}
function start(program, delay, duration, easing) {
function start(program, duration, easing) {
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`));
program.a = t;
program.d = program.b - program.a;
program.duration = duration * Math.abs(program.b - program.a);
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(program, easing, config.css);
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`;
}
@ -101,7 +222,7 @@ export function create_transition(node, fn, params, intro) {
if (!ready) {
if (config.css && delay) {
// TODO can we just use the normal rule, but delay it?
animation_name = create_rule({ a: 0, b: 1, d: 1, duration: 1, }, linear, () => config.css(0, 1));
animation_name = create_rule(0, 1, 1, linear, () => config.css(0, 1));
node.style.animation = (node.style.animation ? ', ' : '') + `${animation_name} 9999s linear 1 both`;
}
@ -117,7 +238,7 @@ export function create_transition(node, fn, params, intro) {
if (delay) {
pending_program = program;
} else {
start(program, delay, duration, easing);
start(program, duration, easing);
}
if (!running) {
@ -129,7 +250,7 @@ export function create_transition(node, fn, params, intro) {
}
if (pending_program && now >= pending_program.start) {
start(pending_program, delay, duration, easing);
start(pending_program, duration, easing);
}
if (running) {

Loading…
Cancel
Save