mirror of
https://github.com/sveltejs/svelte.git
synced 2024-12-01 17:30:59 +01:00
allow animations to be aborted - fixes #1458
This commit is contained in:
parent
ee052dc7bd
commit
31e387e76c
@ -33,6 +33,9 @@ export default class Block {
|
||||
claim: CodeBuilder;
|
||||
hydrate: CodeBuilder;
|
||||
mount: CodeBuilder;
|
||||
measure: CodeBuilder;
|
||||
fix: CodeBuilder;
|
||||
animate: CodeBuilder;
|
||||
intro: CodeBuilder;
|
||||
update: CodeBuilder;
|
||||
outro: CodeBuilder;
|
||||
@ -40,7 +43,7 @@ export default class Block {
|
||||
};
|
||||
|
||||
maintainContext: boolean;
|
||||
animation?: string;
|
||||
hasAnimation: boolean;
|
||||
hasIntroMethod: boolean;
|
||||
hasOutroMethod: boolean;
|
||||
outros: number;
|
||||
@ -72,13 +75,16 @@ export default class Block {
|
||||
claim: new CodeBuilder(),
|
||||
hydrate: new CodeBuilder(),
|
||||
mount: new CodeBuilder(),
|
||||
measure: new CodeBuilder(),
|
||||
fix: new CodeBuilder(),
|
||||
animate: new CodeBuilder(),
|
||||
intro: new CodeBuilder(),
|
||||
update: new CodeBuilder(),
|
||||
outro: new CodeBuilder(),
|
||||
destroy: new CodeBuilder(),
|
||||
};
|
||||
|
||||
this.animation = null;
|
||||
this.hasAnimation = false;
|
||||
this.hasIntroMethod = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros
|
||||
this.hasOutroMethod = false;
|
||||
this.outros = 0;
|
||||
@ -129,8 +135,8 @@ export default class Block {
|
||||
this.outros += 1;
|
||||
}
|
||||
|
||||
addAnimation(name) {
|
||||
this.animation = name;
|
||||
addAnimation() {
|
||||
this.hasAnimation = true;
|
||||
}
|
||||
|
||||
addVariable(name: string, init?: string) {
|
||||
@ -189,11 +195,6 @@ export default class Block {
|
||||
this.builders.hydrate.addLine(`this.first = ${this.first};`);
|
||||
}
|
||||
|
||||
if (this.animation) {
|
||||
properties.addBlock(`node: null,`);
|
||||
this.builders.hydrate.addLine(`this.node = ${this.animation};`);
|
||||
}
|
||||
|
||||
if (this.builders.create.isEmpty() && this.builders.hydrate.isEmpty()) {
|
||||
properties.addBlock(`c: @noop,`);
|
||||
} else {
|
||||
@ -255,6 +256,22 @@ export default class Block {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasAnimation) {
|
||||
properties.addBlock(deindent`
|
||||
${dev ? `r: function measure` : `r`}() {
|
||||
${this.builders.measure}
|
||||
},
|
||||
|
||||
${dev ? `f: function fix` : `f`}() {
|
||||
${this.builders.fix}
|
||||
},
|
||||
|
||||
${dev ? `a: function animate` : `a`}() {
|
||||
${this.builders.animate}
|
||||
},
|
||||
`);
|
||||
}
|
||||
|
||||
if (this.hasIntroMethod || this.hasOutroMethod) {
|
||||
if (hasIntros) {
|
||||
properties.addBlock(deindent`
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Block from '../dom/Block';
|
||||
import Node from './shared/Node';
|
||||
import Expression from './shared/Expression';
|
||||
|
||||
@ -15,4 +16,12 @@ export default class Animation extends Node {
|
||||
? new Expression(compiler, this, scope, info.expression)
|
||||
: null;
|
||||
}
|
||||
|
||||
build(
|
||||
block: Block,
|
||||
parentNode: string,
|
||||
parentNodes: string
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
@ -315,7 +315,7 @@ export default class EachBlock extends Node {
|
||||
const dynamic = this.block.hasUpdateMethod;
|
||||
|
||||
const rects = block.getUniqueName('rects');
|
||||
const destroy = this.block.animation
|
||||
const destroy = this.block.hasAnimation
|
||||
? `@fixAndOutroAndDestroyBlock`
|
||||
: this.block.hasOutroMethod
|
||||
? `@outroAndDestroyBlock`
|
||||
@ -325,9 +325,9 @@ export default class EachBlock extends Node {
|
||||
const ${this.each_block_value} = ${snippet};
|
||||
|
||||
${this.block.hasOutroMethod && `@transitionManager.groupOutros();`}
|
||||
${this.block.animation && `const ${rects} = @measure(${blocks});`}
|
||||
${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`}
|
||||
${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context});
|
||||
${this.block.animation && `@animate(${blocks}, ${rects}, %animations-${this.children[0].animation.name}, {});`}
|
||||
${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`}
|
||||
`);
|
||||
|
||||
if (this.compiler.options.nestedTransitions) {
|
||||
|
@ -195,7 +195,7 @@ export default class Element extends Node {
|
||||
|
||||
if (this.intro) block.addIntro();
|
||||
if (this.outro) block.addOutro();
|
||||
if (this.animation) block.addAnimation(this.var);
|
||||
if (this.animation) block.addAnimation();
|
||||
|
||||
const valueAttribute = this.attributes.find((attribute: Attribute) => attribute.name === 'value');
|
||||
|
||||
@ -367,6 +367,7 @@ export default class Element extends Node {
|
||||
if (this.ref) this.addRef(block);
|
||||
this.addAttributes(block);
|
||||
this.addTransitions(block);
|
||||
this.addAnimation(block);
|
||||
this.addActions(block);
|
||||
|
||||
if (this.initialUpdate) {
|
||||
@ -763,6 +764,31 @@ export default class Element extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
addAnimation(block: Block) {
|
||||
if (!this.animation) return;
|
||||
|
||||
const rect = block.getUniqueName('rect');
|
||||
const animation = block.getUniqueName('animation');
|
||||
|
||||
block.addVariable(rect);
|
||||
block.addVariable(animation);
|
||||
|
||||
block.builders.measure.addBlock(deindent`
|
||||
${rect} = ${this.var}.getBoundingClientRect();
|
||||
`);
|
||||
|
||||
block.builders.fix.addBlock(deindent`
|
||||
@fixPosition(${this.var});
|
||||
if (${animation}) ${animation}.stop();
|
||||
`);
|
||||
|
||||
const params = this.animation.expression ? this.animation.expression.snippet : '{}';
|
||||
block.builders.animate.addBlock(deindent`
|
||||
if (${animation}) ${animation}.stop();
|
||||
${animation} = @wrapAnimation(${this.var}, ${rect}, %animations-${this.animation.name}, ${params});
|
||||
`);
|
||||
}
|
||||
|
||||
addActions(block: Block) {
|
||||
this.actions.forEach(action => {
|
||||
const { expression } = action;
|
||||
|
92
src/shared/animations.js
Normal file
92
src/shared/animations.js
Normal file
@ -0,0 +1,92 @@
|
||||
import { transitionManager, linear, generateRule, hash } from './transitions.js';
|
||||
|
||||
export function wrapAnimation(node, from, fn, params) {
|
||||
if (!from) return;
|
||||
|
||||
const to = node.getBoundingClientRect();
|
||||
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return;
|
||||
|
||||
// console.log({ x: from.x, y: from.y }, { x: to.x, y: to.y }, node.textContent.trim())
|
||||
|
||||
const info = fn(node, { from, to }, params);
|
||||
|
||||
const duration = 'duration' in info ? info.duration : 300;
|
||||
const delay = 'delay' in info ? info.delay : 0;
|
||||
const ease = info.easing || linear;
|
||||
const start = window.performance.now() + delay;
|
||||
const end = start + duration;
|
||||
|
||||
const program = {
|
||||
a: 0,
|
||||
t: 0,
|
||||
b: 1,
|
||||
delta: 1,
|
||||
duration,
|
||||
start,
|
||||
end
|
||||
};
|
||||
|
||||
const animation = {
|
||||
pending: delay ? program : null,
|
||||
program: delay ? null : program,
|
||||
running: !delay,
|
||||
|
||||
start() {
|
||||
if (info.css) {
|
||||
const rule = generateRule(program, ease, info.css);
|
||||
program.name = `__svelte_${hash(rule)}`;
|
||||
|
||||
transitionManager.addRule(rule, program.name);
|
||||
|
||||
node.style.animation = (node.style.animation || '')
|
||||
.split(', ')
|
||||
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
|
||||
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
|
||||
.join(', ');
|
||||
}
|
||||
},
|
||||
|
||||
update: now => {
|
||||
const p = now - program.start;
|
||||
const t = program.a + program.delta * ease(p / program.duration);
|
||||
if (info.tick) info.tick(t, 1 - t);
|
||||
},
|
||||
|
||||
done() {
|
||||
if (info.tick) info.tick(1, 0);
|
||||
this.stop();
|
||||
},
|
||||
|
||||
stop() {
|
||||
if (info.css) transitionManager.deleteRule(node, program.name);
|
||||
animation.running = false;
|
||||
}
|
||||
};
|
||||
|
||||
transitionManager.add(animation);
|
||||
|
||||
if (info.tick) info.tick(0, 1);
|
||||
if (!delay) animation.start();
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
export function fixPosition(node) {
|
||||
const style = getComputedStyle(node);
|
||||
|
||||
if (style.position !== 'absolute' && style.position !== 'fixed') {
|
||||
const { width, height } = style;
|
||||
const a = node.getBoundingClientRect();
|
||||
node.style.position = 'absolute';
|
||||
node.style.width = width;
|
||||
node.style.height = height;
|
||||
const b = node.getBoundingClientRect();
|
||||
|
||||
if (a.left !== b.left || a.top !== b.top) {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { assign } from './utils.js';
|
||||
import { noop } from './utils.js';
|
||||
export * from './animations.js';
|
||||
export * from './await-block.js';
|
||||
export * from './dom.js';
|
||||
export * from './keyed-each.js';
|
||||
|
@ -12,25 +12,7 @@ export function outroAndDestroyBlock(block, lookup) {
|
||||
}
|
||||
|
||||
export function fixAndOutroAndDestroyBlock(block, lookup) {
|
||||
const { node } = block;
|
||||
const style = getComputedStyle(node);
|
||||
|
||||
if (style.position !== 'absolute' && style.position !== 'fixed') {
|
||||
const { width, height } = style;
|
||||
const a = node.getBoundingClientRect();
|
||||
node.style.position = 'absolute';
|
||||
node.style.width = width;
|
||||
node.style.height = height;
|
||||
const b = node.getBoundingClientRect();
|
||||
|
||||
if (a.left !== b.left || a.top !== b.top) {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
block.f();
|
||||
outroAndDestroyBlock(block, lookup);
|
||||
}
|
||||
|
||||
@ -121,10 +103,10 @@ export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic
|
||||
}
|
||||
|
||||
export function measure(blocks) {
|
||||
const measurements = {};
|
||||
const rects = {};
|
||||
let i = blocks.length;
|
||||
while (i--) measurements[blocks[i].key] = blocks[i].node.getBoundingClientRect();
|
||||
return measurements;
|
||||
while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect();
|
||||
return rects;
|
||||
}
|
||||
|
||||
export function animate(blocks, rects, fn, params) {
|
||||
@ -138,67 +120,6 @@ export function animate(blocks, rects, fn, params) {
|
||||
|
||||
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) continue;
|
||||
|
||||
const info = fn(block.node, { from, to }, params);
|
||||
|
||||
const duration = 'duration' in info ? info.duration : 300;
|
||||
const delay = 'delay' in info ? info.delay : 0;
|
||||
const ease = info.easing || linear;
|
||||
const start = window.performance.now() + delay;
|
||||
const end = start + duration;
|
||||
|
||||
const program = {
|
||||
a: 0,
|
||||
t: 0,
|
||||
b: 1,
|
||||
delta: 1,
|
||||
duration,
|
||||
start,
|
||||
end
|
||||
};
|
||||
|
||||
const animation = {
|
||||
pending: delay ? program : null,
|
||||
program: delay ? null : program,
|
||||
running: !delay,
|
||||
|
||||
start() {
|
||||
if (info.css) {
|
||||
const rule = generateRule(program, ease, info.css);
|
||||
program.name = `__svelte_${hash(rule)}`;
|
||||
|
||||
transitionManager.addRule(rule, program.name);
|
||||
|
||||
block.node.style.animation = (block.node.style.animation || '')
|
||||
.split(', ')
|
||||
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
|
||||
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
|
||||
.join(', ');
|
||||
}
|
||||
},
|
||||
|
||||
update: now => {
|
||||
const p = now - program.start;
|
||||
const t = program.a + program.delta * ease(p / program.duration);
|
||||
if (info.tick) info.tick(t, 1 - t);
|
||||
},
|
||||
|
||||
done() {
|
||||
if (info.css) {
|
||||
transitionManager.deleteRule(block.node, program.name);
|
||||
}
|
||||
|
||||
if (info.tick) {
|
||||
info.tick(1, 0);
|
||||
}
|
||||
|
||||
animation.running = false;
|
||||
}
|
||||
};
|
||||
|
||||
transitionManager.add(animation);
|
||||
|
||||
if (info.tick) info.tick(0, 1);
|
||||
|
||||
if (!delay) animation.start();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user