Skip to content

Commit 5665498

Browse files
authored
fix: measure elements before taking siblings out of the flow (#11216)
* Revert "fix: take outroing elements out of the flow when animating siblings (#11208)" This reverts commit c44234d. * fix: measure elements before taking siblings out of the flow * lint * add changeset back * changeset
1 parent 27eb91b commit 5665498

File tree

5 files changed

+73
-57
lines changed

5 files changed

+73
-57
lines changed

.changeset/lemon-trees-act.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: measure elements before taking siblings out of the flow

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function pause_effects(items, controlled_anchor, callback) {
7171
parent_node.append(controlled_anchor);
7272
}
7373

74-
run_out_transitions(transitions, true, () => {
74+
run_out_transitions(transitions, () => {
7575
for (var i = 0; i < length; i++) {
7676
destroy_effect(items[i].e);
7777
}
@@ -312,7 +312,10 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
312312

313313
if ((item.e.f & INERT) !== 0) {
314314
resume_effect(item.e);
315-
to_animate.delete(item);
315+
if (is_animated) {
316+
item.a?.unfix();
317+
to_animate.delete(item);
318+
}
316319
}
317320

318321
if (item !== current) {
@@ -391,6 +394,16 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
391394

392395
var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && length === 0 ? anchor : null;
393396

397+
if (is_animated) {
398+
for (i = 0; i < to_destroy.length; i += 1) {
399+
to_destroy[i].a?.measure();
400+
}
401+
402+
for (i = 0; i < to_destroy.length; i += 1) {
403+
to_destroy[i].a?.fix();
404+
}
405+
}
406+
394407
pause_effects(to_destroy, controlled_anchor, () => {
395408
for (var i = 0; i < to_destroy.length; i += 1) {
396409
var item = to_destroy[i];

packages/svelte/src/internal/client/dom/elements/transitions.js

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ export function animation(element, get_fn, get_params) {
8888
/** @type {import('#client').Animation | undefined} */
8989
var animation;
9090

91+
/** @type {null | { position: string, width: string, height: string }} */
92+
var original_styles = null;
93+
9194
item.a ??= {
9295
element,
9396
measure() {
@@ -106,11 +109,43 @@ export function animation(element, get_fn, get_params) {
106109
) {
107110
const options = get_fn()(this.element, { from, to }, get_params?.());
108111

109-
animation = animate(this.element, options, false, undefined, 1, () => {
112+
animation = animate(this.element, options, undefined, 1, () => {
110113
animation?.abort();
111114
animation = undefined;
112115
});
113116
}
117+
},
118+
fix() {
119+
var computed_style = getComputedStyle(element);
120+
121+
if (computed_style.position !== 'absolute' && computed_style.position !== 'fixed') {
122+
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
123+
124+
original_styles = {
125+
position: style.position,
126+
width: style.width,
127+
height: style.height
128+
};
129+
130+
style.position = 'absolute';
131+
style.width = computed_style.width;
132+
style.height = computed_style.height;
133+
var to = element.getBoundingClientRect();
134+
135+
if (from.left !== to.left || from.top !== to.top) {
136+
var transform = `translate(${from.left - to.left}px, ${from.top - to.top}px)`;
137+
style.transform = style.transform ? `${style.transform} ${transform}` : transform;
138+
}
139+
}
140+
},
141+
unfix() {
142+
if (original_styles) {
143+
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
144+
145+
style.position = original_styles.position;
146+
style.width = original_styles.width;
147+
style.height = original_styles.height;
148+
}
114149
}
115150
};
116151

@@ -169,7 +204,7 @@ export function transition(flags, element, get_fn, get_params) {
169204

170205
if (is_intro) {
171206
dispatch_event(element, 'introstart');
172-
intro = animate(element, get_options(), false, outro, 1, () => {
207+
intro = animate(element, get_options(), outro, 1, () => {
173208
dispatch_event(element, 'introend');
174209
intro = current_options = undefined;
175210
});
@@ -178,12 +213,12 @@ export function transition(flags, element, get_fn, get_params) {
178213
reset?.();
179214
}
180215
},
181-
out(fn, position_absolute = false) {
216+
out(fn) {
182217
if (is_outro) {
183218
element.inert = true;
184219

185220
dispatch_event(element, 'outrostart');
186-
outro = animate(element, get_options(), position_absolute, intro, 0, () => {
221+
outro = animate(element, get_options(), intro, 0, () => {
187222
dispatch_event(element, 'outroend');
188223
outro = current_options = undefined;
189224
fn?.();
@@ -229,13 +264,12 @@ export function transition(flags, element, get_fn, get_params) {
229264
* Animates an element, according to the provided configuration
230265
* @param {Element} element
231266
* @param {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig)} options
232-
* @param {boolean} position_absolute
233267
* @param {import('#client').Animation | undefined} counterpart The corresponding intro/outro to this outro/intro
234268
* @param {number} t2 The target `t` value — `1` for intro, `0` for outro
235269
* @param {(() => void) | undefined} callback
236270
* @returns {import('#client').Animation}
237271
*/
238-
function animate(element, options, position_absolute, counterpart, t2, callback) {
272+
function animate(element, options, counterpart, t2, callback) {
239273
if (is_function(options)) {
240274
// In the case of a deferred transition (such as `crossfade`), `option` will be
241275
// a function rather than an `AnimationConfig`. We need to call this function
@@ -245,7 +279,7 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
245279

246280
effect(() => {
247281
var o = untrack(() => options({ direction: t2 === 1 ? 'in' : 'out' }));
248-
a = animate(element, o, position_absolute, counterpart, t2, callback);
282+
a = animate(element, o, counterpart, t2, callback);
249283
});
250284

251285
// ...but we want to do so without using `async`/`await` everywhere, so
@@ -285,9 +319,6 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
285319
/** @type {import('#client').Task} */
286320
var task;
287321

288-
/** @type {null | { position: string, width: string, height: string }} */
289-
var original_styles = null;
290-
291322
if (css) {
292323
// WAAPI
293324
var keyframes = [];
@@ -299,37 +330,6 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
299330
keyframes.push(css_to_keyframe(styles));
300331
}
301332

302-
if (position_absolute) {
303-
// we take the element out of the flow, so that sibling elements with an `animate:`
304-
// directive can transform to the correct position
305-
var computed_style = getComputedStyle(element);
306-
307-
if (computed_style.position !== 'absolute' && computed_style.position !== 'fixed') {
308-
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
309-
310-
original_styles = {
311-
position: style.position,
312-
width: style.width,
313-
height: style.height
314-
};
315-
316-
var rect_a = element.getBoundingClientRect();
317-
style.position = 'absolute';
318-
style.width = computed_style.width;
319-
style.height = computed_style.height;
320-
var rect_b = element.getBoundingClientRect();
321-
322-
if (rect_a.left !== rect_b.left || rect_a.top !== rect_b.top) {
323-
var transform = `translate(${rect_a.left - rect_b.left}px, ${rect_a.top - rect_b.top}px)`;
324-
for (var keyframe of keyframes) {
325-
keyframe.transform = keyframe.transform
326-
? `${keyframe.transform} ${transform}`
327-
: transform;
328-
}
329-
}
330-
}
331-
}
332-
333333
animation = element.animate(keyframes, {
334334
delay,
335335
duration,
@@ -340,6 +340,10 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
340340
animation.finished
341341
.then(() => {
342342
callback?.();
343+
344+
if (t2 === 1) {
345+
animation.cancel();
346+
}
343347
})
344348
.catch((e) => {
345349
// Error for DOMException: The user aborted a request. This results in two things:
@@ -380,15 +384,6 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
380384
task?.abort();
381385
},
382386
deactivate: () => {
383-
if (original_styles) {
384-
// revert `animate:` position fixing
385-
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
386-
387-
style.position = original_styles.position;
388-
style.width = original_styles.width;
389-
style.height = original_styles.height;
390-
}
391-
392387
callback = undefined;
393388
},
394389
reset: () => {

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,23 +334,22 @@ export function pause_effect(effect, callback) {
334334

335335
pause_children(effect, transitions, true);
336336

337-
run_out_transitions(transitions, false, () => {
337+
run_out_transitions(transitions, () => {
338338
destroy_effect(effect);
339339
if (callback) callback();
340340
});
341341
}
342342

343343
/**
344344
* @param {import('#client').TransitionManager[]} transitions
345-
* @param {boolean} position_absolute
346345
* @param {() => void} fn
347346
*/
348-
export function run_out_transitions(transitions, position_absolute, fn) {
347+
export function run_out_transitions(transitions, fn) {
349348
var remaining = transitions.length;
350349
if (remaining > 0) {
351350
var check = () => --remaining || fn();
352351
for (var transition of transitions) {
353-
transition.out(check, position_absolute);
352+
transition.out(check);
354353
}
355354
} else {
356355
fn();

packages/svelte/src/internal/client/types.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export interface TransitionManager {
7979
/** Called inside `resume_effect` */
8080
in: () => void;
8181
/** Called inside `pause_effect` */
82-
out: (callback?: () => void, position_absolute?: boolean) => void;
82+
out: (callback?: () => void) => void;
8383
/** Called inside `destroy_effect` */
8484
stop: () => void;
8585
}
@@ -91,6 +91,10 @@ export interface AnimationManager {
9191
measure: () => void;
9292
/** Called during keyed each block reconciliation, after updates — this triggers the animation */
9393
apply: () => void;
94+
/** Fix the element position, so that siblings can move to the correct destination */
95+
fix: () => void;
96+
/** Unfix the element position if the outro is aborted */
97+
unfix: () => void;
9498
}
9599

96100
export interface Animation {

0 commit comments

Comments
 (0)