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 c44234dc2f.

* fix: measure elements before taking siblings out of the flow

* lint

* add changeset back

* changeset
pull/11223/head
Rich Harris 8 months ago committed by GitHub
parent 27eb91bbce
commit 56654986a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: measure elements before taking siblings out of the flow

@ -71,7 +71,7 @@ function pause_effects(items, controlled_anchor, callback) {
parent_node.append(controlled_anchor); parent_node.append(controlled_anchor);
} }
run_out_transitions(transitions, true, () => { run_out_transitions(transitions, () => {
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
destroy_effect(items[i].e); destroy_effect(items[i].e);
} }
@ -312,8 +312,11 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
if ((item.e.f & INERT) !== 0) { if ((item.e.f & INERT) !== 0) {
resume_effect(item.e); resume_effect(item.e);
if (is_animated) {
item.a?.unfix();
to_animate.delete(item); to_animate.delete(item);
} }
}
if (item !== current) { if (item !== current) {
if (seen.has(item)) { if (seen.has(item)) {
@ -391,6 +394,16 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && length === 0 ? anchor : null; var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && length === 0 ? anchor : null;
if (is_animated) {
for (i = 0; i < to_destroy.length; i += 1) {
to_destroy[i].a?.measure();
}
for (i = 0; i < to_destroy.length; i += 1) {
to_destroy[i].a?.fix();
}
}
pause_effects(to_destroy, controlled_anchor, () => { pause_effects(to_destroy, controlled_anchor, () => {
for (var i = 0; i < to_destroy.length; i += 1) { for (var i = 0; i < to_destroy.length; i += 1) {
var item = to_destroy[i]; var item = to_destroy[i];

@ -88,6 +88,9 @@ export function animation(element, get_fn, get_params) {
/** @type {import('#client').Animation | undefined} */ /** @type {import('#client').Animation | undefined} */
var animation; var animation;
/** @type {null | { position: string, width: string, height: string }} */
var original_styles = null;
item.a ??= { item.a ??= {
element, element,
measure() { measure() {
@ -106,11 +109,43 @@ export function animation(element, get_fn, get_params) {
) { ) {
const options = get_fn()(this.element, { from, to }, get_params?.()); const options = get_fn()(this.element, { from, to }, get_params?.());
animation = animate(this.element, options, false, undefined, 1, () => { animation = animate(this.element, options, undefined, 1, () => {
animation?.abort(); animation?.abort();
animation = undefined; animation = undefined;
}); });
} }
},
fix() {
var computed_style = getComputedStyle(element);
if (computed_style.position !== 'absolute' && computed_style.position !== 'fixed') {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
original_styles = {
position: style.position,
width: style.width,
height: style.height
};
style.position = 'absolute';
style.width = computed_style.width;
style.height = computed_style.height;
var to = element.getBoundingClientRect();
if (from.left !== to.left || from.top !== to.top) {
var transform = `translate(${from.left - to.left}px, ${from.top - to.top}px)`;
style.transform = style.transform ? `${style.transform} ${transform}` : transform;
}
}
},
unfix() {
if (original_styles) {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
style.position = original_styles.position;
style.width = original_styles.width;
style.height = original_styles.height;
}
} }
}; };
@ -169,7 +204,7 @@ export function transition(flags, element, get_fn, get_params) {
if (is_intro) { if (is_intro) {
dispatch_event(element, 'introstart'); dispatch_event(element, 'introstart');
intro = animate(element, get_options(), false, outro, 1, () => { intro = animate(element, get_options(), outro, 1, () => {
dispatch_event(element, 'introend'); dispatch_event(element, 'introend');
intro = current_options = undefined; intro = current_options = undefined;
}); });
@ -178,12 +213,12 @@ export function transition(flags, element, get_fn, get_params) {
reset?.(); reset?.();
} }
}, },
out(fn, position_absolute = false) { out(fn) {
if (is_outro) { if (is_outro) {
element.inert = true; element.inert = true;
dispatch_event(element, 'outrostart'); dispatch_event(element, 'outrostart');
outro = animate(element, get_options(), position_absolute, intro, 0, () => { outro = animate(element, get_options(), intro, 0, () => {
dispatch_event(element, 'outroend'); dispatch_event(element, 'outroend');
outro = current_options = undefined; outro = current_options = undefined;
fn?.(); fn?.();
@ -229,13 +264,12 @@ export function transition(flags, element, get_fn, get_params) {
* Animates an element, according to the provided configuration * Animates an element, according to the provided configuration
* @param {Element} element * @param {Element} element
* @param {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig)} options * @param {import('#client').AnimationConfig | ((opts: { direction: 'in' | 'out' }) => import('#client').AnimationConfig)} options
* @param {boolean} position_absolute
* @param {import('#client').Animation | undefined} counterpart The corresponding intro/outro to this outro/intro * @param {import('#client').Animation | undefined} counterpart The corresponding intro/outro to this outro/intro
* @param {number} t2 The target `t` value `1` for intro, `0` for outro * @param {number} t2 The target `t` value `1` for intro, `0` for outro
* @param {(() => void) | undefined} callback * @param {(() => void) | undefined} callback
* @returns {import('#client').Animation} * @returns {import('#client').Animation}
*/ */
function animate(element, options, position_absolute, counterpart, t2, callback) { function animate(element, options, counterpart, t2, callback) {
if (is_function(options)) { if (is_function(options)) {
// In the case of a deferred transition (such as `crossfade`), `option` will be // In the case of a deferred transition (such as `crossfade`), `option` will be
// a function rather than an `AnimationConfig`. We need to call this function // 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)
effect(() => { effect(() => {
var o = untrack(() => options({ direction: t2 === 1 ? 'in' : 'out' })); var o = untrack(() => options({ direction: t2 === 1 ? 'in' : 'out' }));
a = animate(element, o, position_absolute, counterpart, t2, callback); a = animate(element, o, counterpart, t2, callback);
}); });
// ...but we want to do so without using `async`/`await` everywhere, so // ...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)
/** @type {import('#client').Task} */ /** @type {import('#client').Task} */
var task; var task;
/** @type {null | { position: string, width: string, height: string }} */
var original_styles = null;
if (css) { if (css) {
// WAAPI // WAAPI
var keyframes = []; var keyframes = [];
@ -299,37 +330,6 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
keyframes.push(css_to_keyframe(styles)); keyframes.push(css_to_keyframe(styles));
} }
if (position_absolute) {
// we take the element out of the flow, so that sibling elements with an `animate:`
// directive can transform to the correct position
var computed_style = getComputedStyle(element);
if (computed_style.position !== 'absolute' && computed_style.position !== 'fixed') {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
original_styles = {
position: style.position,
width: style.width,
height: style.height
};
var rect_a = element.getBoundingClientRect();
style.position = 'absolute';
style.width = computed_style.width;
style.height = computed_style.height;
var rect_b = element.getBoundingClientRect();
if (rect_a.left !== rect_b.left || rect_a.top !== rect_b.top) {
var transform = `translate(${rect_a.left - rect_b.left}px, ${rect_a.top - rect_b.top}px)`;
for (var keyframe of keyframes) {
keyframe.transform = keyframe.transform
? `${keyframe.transform} ${transform}`
: transform;
}
}
}
}
animation = element.animate(keyframes, { animation = element.animate(keyframes, {
delay, delay,
duration, duration,
@ -340,6 +340,10 @@ function animate(element, options, position_absolute, counterpart, t2, callback)
animation.finished animation.finished
.then(() => { .then(() => {
callback?.(); callback?.();
if (t2 === 1) {
animation.cancel();
}
}) })
.catch((e) => { .catch((e) => {
// Error for DOMException: The user aborted a request. This results in two things: // 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)
task?.abort(); task?.abort();
}, },
deactivate: () => { deactivate: () => {
if (original_styles) {
// revert `animate:` position fixing
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
style.position = original_styles.position;
style.width = original_styles.width;
style.height = original_styles.height;
}
callback = undefined; callback = undefined;
}, },
reset: () => { reset: () => {

@ -334,7 +334,7 @@ export function pause_effect(effect, callback) {
pause_children(effect, transitions, true); pause_children(effect, transitions, true);
run_out_transitions(transitions, false, () => { run_out_transitions(transitions, () => {
destroy_effect(effect); destroy_effect(effect);
if (callback) callback(); if (callback) callback();
}); });
@ -342,15 +342,14 @@ export function pause_effect(effect, callback) {
/** /**
* @param {import('#client').TransitionManager[]} transitions * @param {import('#client').TransitionManager[]} transitions
* @param {boolean} position_absolute
* @param {() => void} fn * @param {() => void} fn
*/ */
export function run_out_transitions(transitions, position_absolute, fn) { export function run_out_transitions(transitions, fn) {
var remaining = transitions.length; var remaining = transitions.length;
if (remaining > 0) { if (remaining > 0) {
var check = () => --remaining || fn(); var check = () => --remaining || fn();
for (var transition of transitions) { for (var transition of transitions) {
transition.out(check, position_absolute); transition.out(check);
} }
} else { } else {
fn(); fn();

@ -79,7 +79,7 @@ export interface TransitionManager {
/** Called inside `resume_effect` */ /** Called inside `resume_effect` */
in: () => void; in: () => void;
/** Called inside `pause_effect` */ /** Called inside `pause_effect` */
out: (callback?: () => void, position_absolute?: boolean) => void; out: (callback?: () => void) => void;
/** Called inside `destroy_effect` */ /** Called inside `destroy_effect` */
stop: () => void; stop: () => void;
} }
@ -91,6 +91,10 @@ export interface AnimationManager {
measure: () => void; measure: () => void;
/** Called during keyed each block reconciliation, after updates — this triggers the animation */ /** Called during keyed each block reconciliation, after updates — this triggers the animation */
apply: () => void; apply: () => void;
/** Fix the element position, so that siblings can move to the correct destination */
fix: () => void;
/** Unfix the element position if the outro is aborted */
unfix: () => void;
} }
export interface Animation { export interface Animation {

Loading…
Cancel
Save