Skip to content

Commit 31e387e

Browse files
committed
allow animations to be aborted - fixes #1458
1 parent ee052dc commit 31e387e

File tree

7 files changed

+163
-97
lines changed

7 files changed

+163
-97
lines changed

src/compile/dom/Block.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,17 @@ export default class Block {
3333
claim: CodeBuilder;
3434
hydrate: CodeBuilder;
3535
mount: CodeBuilder;
36+
measure: CodeBuilder;
37+
fix: CodeBuilder;
38+
animate: CodeBuilder;
3639
intro: CodeBuilder;
3740
update: CodeBuilder;
3841
outro: CodeBuilder;
3942
destroy: CodeBuilder;
4043
};
4144

4245
maintainContext: boolean;
43-
animation?: string;
46+
hasAnimation: boolean;
4447
hasIntroMethod: boolean;
4548
hasOutroMethod: boolean;
4649
outros: number;
@@ -72,13 +75,16 @@ export default class Block {
7275
claim: new CodeBuilder(),
7376
hydrate: new CodeBuilder(),
7477
mount: new CodeBuilder(),
78+
measure: new CodeBuilder(),
79+
fix: new CodeBuilder(),
80+
animate: new CodeBuilder(),
7581
intro: new CodeBuilder(),
7682
update: new CodeBuilder(),
7783
outro: new CodeBuilder(),
7884
destroy: new CodeBuilder(),
7985
};
8086

81-
this.animation = null;
87+
this.hasAnimation = false;
8288
this.hasIntroMethod = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros
8389
this.hasOutroMethod = false;
8490
this.outros = 0;
@@ -129,8 +135,8 @@ export default class Block {
129135
this.outros += 1;
130136
}
131137

132-
addAnimation(name) {
133-
this.animation = name;
138+
addAnimation() {
139+
this.hasAnimation = true;
134140
}
135141

136142
addVariable(name: string, init?: string) {
@@ -189,11 +195,6 @@ export default class Block {
189195
this.builders.hydrate.addLine(`this.first = ${this.first};`);
190196
}
191197

192-
if (this.animation) {
193-
properties.addBlock(`node: null,`);
194-
this.builders.hydrate.addLine(`this.node = ${this.animation};`);
195-
}
196-
197198
if (this.builders.create.isEmpty() && this.builders.hydrate.isEmpty()) {
198199
properties.addBlock(`c: @noop,`);
199200
} else {
@@ -255,6 +256,22 @@ export default class Block {
255256
}
256257
}
257258

259+
if (this.hasAnimation) {
260+
properties.addBlock(deindent`
261+
${dev ? `r: function measure` : `r`}() {
262+
${this.builders.measure}
263+
},
264+
265+
${dev ? `f: function fix` : `f`}() {
266+
${this.builders.fix}
267+
},
268+
269+
${dev ? `a: function animate` : `a`}() {
270+
${this.builders.animate}
271+
},
272+
`);
273+
}
274+
258275
if (this.hasIntroMethod || this.hasOutroMethod) {
259276
if (hasIntros) {
260277
properties.addBlock(deindent`

src/compile/nodes/Animation.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Block from '../dom/Block';
12
import Node from './shared/Node';
23
import Expression from './shared/Expression';
34

@@ -15,4 +16,12 @@ export default class Animation extends Node {
1516
? new Expression(compiler, this, scope, info.expression)
1617
: null;
1718
}
19+
20+
build(
21+
block: Block,
22+
parentNode: string,
23+
parentNodes: string
24+
) {
25+
26+
}
1827
}

src/compile/nodes/EachBlock.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ export default class EachBlock extends Node {
315315
const dynamic = this.block.hasUpdateMethod;
316316

317317
const rects = block.getUniqueName('rects');
318-
const destroy = this.block.animation
318+
const destroy = this.block.hasAnimation
319319
? `@fixAndOutroAndDestroyBlock`
320320
: this.block.hasOutroMethod
321321
? `@outroAndDestroyBlock`
@@ -325,9 +325,9 @@ export default class EachBlock extends Node {
325325
const ${this.each_block_value} = ${snippet};
326326
327327
${this.block.hasOutroMethod && `@transitionManager.groupOutros();`}
328-
${this.block.animation && `const ${rects} = @measure(${blocks});`}
328+
${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`}
329329
${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});
330-
${this.block.animation && `@animate(${blocks}, ${rects}, %animations-${this.children[0].animation.name}, {});`}
330+
${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`}
331331
`);
332332

333333
if (this.compiler.options.nestedTransitions) {

src/compile/nodes/Element.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export default class Element extends Node {
195195

196196
if (this.intro) block.addIntro();
197197
if (this.outro) block.addOutro();
198-
if (this.animation) block.addAnimation(this.var);
198+
if (this.animation) block.addAnimation();
199199

200200
const valueAttribute = this.attributes.find((attribute: Attribute) => attribute.name === 'value');
201201

@@ -367,6 +367,7 @@ export default class Element extends Node {
367367
if (this.ref) this.addRef(block);
368368
this.addAttributes(block);
369369
this.addTransitions(block);
370+
this.addAnimation(block);
370371
this.addActions(block);
371372

372373
if (this.initialUpdate) {
@@ -763,6 +764,31 @@ export default class Element extends Node {
763764
}
764765
}
765766

767+
addAnimation(block: Block) {
768+
if (!this.animation) return;
769+
770+
const rect = block.getUniqueName('rect');
771+
const animation = block.getUniqueName('animation');
772+
773+
block.addVariable(rect);
774+
block.addVariable(animation);
775+
776+
block.builders.measure.addBlock(deindent`
777+
${rect} = ${this.var}.getBoundingClientRect();
778+
`);
779+
780+
block.builders.fix.addBlock(deindent`
781+
@fixPosition(${this.var});
782+
if (${animation}) ${animation}.stop();
783+
`);
784+
785+
const params = this.animation.expression ? this.animation.expression.snippet : '{}';
786+
block.builders.animate.addBlock(deindent`
787+
if (${animation}) ${animation}.stop();
788+
${animation} = @wrapAnimation(${this.var}, ${rect}, %animations-${this.animation.name}, ${params});
789+
`);
790+
}
791+
766792
addActions(block: Block) {
767793
this.actions.forEach(action => {
768794
const { expression } = action;

src/shared/animations.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { transitionManager, linear, generateRule, hash } from './transitions.js';
2+
3+
export function wrapAnimation(node, from, fn, params) {
4+
if (!from) return;
5+
6+
const to = node.getBoundingClientRect();
7+
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return;
8+
9+
// console.log({ x: from.x, y: from.y }, { x: to.x, y: to.y }, node.textContent.trim())
10+
11+
const info = fn(node, { from, to }, params);
12+
13+
const duration = 'duration' in info ? info.duration : 300;
14+
const delay = 'delay' in info ? info.delay : 0;
15+
const ease = info.easing || linear;
16+
const start = window.performance.now() + delay;
17+
const end = start + duration;
18+
19+
const program = {
20+
a: 0,
21+
t: 0,
22+
b: 1,
23+
delta: 1,
24+
duration,
25+
start,
26+
end
27+
};
28+
29+
const animation = {
30+
pending: delay ? program : null,
31+
program: delay ? null : program,
32+
running: !delay,
33+
34+
start() {
35+
if (info.css) {
36+
const rule = generateRule(program, ease, info.css);
37+
program.name = `__svelte_${hash(rule)}`;
38+
39+
transitionManager.addRule(rule, program.name);
40+
41+
node.style.animation = (node.style.animation || '')
42+
.split(', ')
43+
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
44+
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
45+
.join(', ');
46+
}
47+
},
48+
49+
update: now => {
50+
const p = now - program.start;
51+
const t = program.a + program.delta * ease(p / program.duration);
52+
if (info.tick) info.tick(t, 1 - t);
53+
},
54+
55+
done() {
56+
if (info.tick) info.tick(1, 0);
57+
this.stop();
58+
},
59+
60+
stop() {
61+
if (info.css) transitionManager.deleteRule(node, program.name);
62+
animation.running = false;
63+
}
64+
};
65+
66+
transitionManager.add(animation);
67+
68+
if (info.tick) info.tick(0, 1);
69+
if (!delay) animation.start();
70+
71+
return animation;
72+
}
73+
74+
export function fixPosition(node) {
75+
const style = getComputedStyle(node);
76+
77+
if (style.position !== 'absolute' && style.position !== 'fixed') {
78+
const { width, height } = style;
79+
const a = node.getBoundingClientRect();
80+
node.style.position = 'absolute';
81+
node.style.width = width;
82+
node.style.height = height;
83+
const b = node.getBoundingClientRect();
84+
85+
if (a.left !== b.left || a.top !== b.top) {
86+
const style = getComputedStyle(node);
87+
const transform = style.transform === 'none' ? '' : style.transform;
88+
89+
node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;
90+
}
91+
}
92+
}

src/shared/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assign } from './utils.js';
22
import { noop } from './utils.js';
3+
export * from './animations.js';
34
export * from './await-block.js';
45
export * from './dom.js';
56
export * from './keyed-each.js';

src/shared/keyed-each.js

+5-84
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,7 @@ export function outroAndDestroyBlock(block, lookup) {
1212
}
1313

1414
export function fixAndOutroAndDestroyBlock(block, lookup) {
15-
const { node } = block;
16-
const style = getComputedStyle(node);
17-
18-
if (style.position !== 'absolute' && style.position !== 'fixed') {
19-
const { width, height } = style;
20-
const a = node.getBoundingClientRect();
21-
node.style.position = 'absolute';
22-
node.style.width = width;
23-
node.style.height = height;
24-
const b = node.getBoundingClientRect();
25-
26-
if (a.left !== b.left || a.top !== b.top) {
27-
const style = getComputedStyle(node);
28-
const transform = style.transform === 'none' ? '' : style.transform;
29-
30-
node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;
31-
}
32-
}
33-
15+
block.f();
3416
outroAndDestroyBlock(block, lookup);
3517
}
3618

@@ -121,10 +103,10 @@ export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic
121103
}
122104

123105
export function measure(blocks) {
124-
const measurements = {};
106+
const rects = {};
125107
let i = blocks.length;
126-
while (i--) measurements[blocks[i].key] = blocks[i].node.getBoundingClientRect();
127-
return measurements;
108+
while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect();
109+
return rects;
128110
}
129111

130112
export function animate(blocks, rects, fn, params) {
@@ -138,67 +120,6 @@ export function animate(blocks, rects, fn, params) {
138120

139121
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) continue;
140122

141-
const info = fn(block.node, { from, to }, params);
142-
143-
const duration = 'duration' in info ? info.duration : 300;
144-
const delay = 'delay' in info ? info.delay : 0;
145-
const ease = info.easing || linear;
146-
const start = window.performance.now() + delay;
147-
const end = start + duration;
148-
149-
const program = {
150-
a: 0,
151-
t: 0,
152-
b: 1,
153-
delta: 1,
154-
duration,
155-
start,
156-
end
157-
};
158-
159-
const animation = {
160-
pending: delay ? program : null,
161-
program: delay ? null : program,
162-
running: !delay,
163-
164-
start() {
165-
if (info.css) {
166-
const rule = generateRule(program, ease, info.css);
167-
program.name = `__svelte_${hash(rule)}`;
168-
169-
transitionManager.addRule(rule, program.name);
170-
171-
block.node.style.animation = (block.node.style.animation || '')
172-
.split(', ')
173-
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
174-
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
175-
.join(', ');
176-
}
177-
},
178-
179-
update: now => {
180-
const p = now - program.start;
181-
const t = program.a + program.delta * ease(p / program.duration);
182-
if (info.tick) info.tick(t, 1 - t);
183-
},
184-
185-
done() {
186-
if (info.css) {
187-
transitionManager.deleteRule(block.node, program.name);
188-
}
189-
190-
if (info.tick) {
191-
info.tick(1, 0);
192-
}
193-
194-
animation.running = false;
195-
}
196-
};
197-
198-
transitionManager.add(animation);
199-
200-
if (info.tick) info.tick(0, 1);
201-
202-
if (!delay) animation.start();
123+
203124
}
204125
}

0 commit comments

Comments
 (0)