Mathias Picker 1 week ago committed by GitHub
commit 2d315b2677
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: capture FLIP sizes before any sibling leaves flex flow

@ -630,13 +630,15 @@ function reconcile(state, array, anchor, flags, get_key) {
var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && length === 0 ? anchor : null;
if (is_animated) {
// Doing all the reads _then_ all the writes minimises layout flushes.
// `measure` and `capture_size` are both reads, so they share a loop.
for (i = 0; i < destroy_length; i += 1) {
to_destroy[i].nodes?.a?.measure();
}
for (i = 0; i < destroy_length; i += 1) {
to_destroy[i].nodes?.a?.fix();
var am = to_destroy[i].nodes?.a;
am?.measure();
am?.capture_size();
}
for (i = 0; i < destroy_length; i += 1) to_destroy[i].nodes?.a?.set_position();
for (i = 0; i < destroy_length; i += 1) to_destroy[i].nodes?.a?.set_transform();
}
pause_effects(state, to_destroy, controlled_anchor);

@ -96,6 +96,10 @@ export function animation(element, get_fn, get_params) {
/** @type {null | { position: string, width: string, height: string, transform: string }} */
var original_styles = null;
/** Captured pre-absolute size, set by `capture_size`, consumed by `set_position`. */
var frozen_width = '';
/** Captured pre-absolute size, set by `capture_size`, consumed by `set_position`. */
var frozen_height = '';
nodes.a ??= {
element,
@ -128,41 +132,57 @@ export function animation(element, get_fn, get_params) {
);
}
},
fix() {
capture_size() {
original_styles = null;
// If an animation is already running, transforming the element is likely to fail,
// because the styles applied by the animation take precedence. In the case of crossfade,
// that means the `translate(...)` of the crossfade transition overrules the `translate(...)`
// we would apply below, leading to the element jumping somewhere to the top left.
if (element.getAnimations().length) return;
if (this.element.getAnimations().length) return;
// It's important to destructure these to get fixed values - the object itself has getters,
// and changing the style to 'absolute' can for example influence the width.
var { position, width, height } = getComputedStyle(element);
if (position !== 'absolute' && position !== 'fixed') {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
original_styles = {
position: style.position,
width: style.width,
height: style.height,
transform: style.transform
};
style.position = 'absolute';
style.width = width;
style.height = 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;
}
// and changing the style to 'absolute' can for example influence the width. We also have
// to capture this BEFORE any sibling has been mutated, so that the recorded dimensions
// reflect the original layout (matters in flex/grid containers).
var { position, width, height } = getComputedStyle(this.element);
if (position === 'absolute' || position === 'fixed') return;
var style = /** @type {HTMLElement | SVGElement} */ (this.element).style;
original_styles = {
position: style.position,
width: style.width,
height: style.height,
transform: style.transform
};
frozen_width = width;
frozen_height = height;
},
set_position() {
if (original_styles === null) return;
var style = /** @type {HTMLElement | SVGElement} */ (this.element).style;
style.position = 'absolute';
style.width = frozen_width;
style.height = frozen_height;
},
set_transform() {
if (original_styles === null) return;
var to = this.element.getBoundingClientRect();
if (from.left !== to.left || from.top !== to.top) {
var style = /** @type {HTMLElement | SVGElement} */ (this.element).style;
var translate = `translate(${from.left - to.left}px, ${from.top - to.top}px)`;
style.transform = style.transform ? `${style.transform} ${translate}` : translate;
}
},
unfix() {
if (original_styles) {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
var style = /** @type {HTMLElement | SVGElement} */ (this.element).style;
style.position = original_styles.position;
style.width = original_styles.width;

@ -126,8 +126,25 @@ export interface AnimationManager {
measure: () => void;
/** Called during keyed each block reconciliation, after updates — this triggers the animation */
apply: () => void;
/** Fix the element position, so that siblings can move to the correct destination */
fix: () => void;
/**
* Capture the element's pre-absolute-positioning size + original styles.
* Pure read pass runs before any items have been mutated so the captured
* dimensions reflect the original layout (matters in flex/grid containers).
*/
capture_size: () => void;
/**
* Apply `position: absolute` and the captured size. Pure write pass runs
* after all items' sizes have been captured, so the layout invalidation
* is batched into a single subsequent flush.
*/
set_position: () => void;
/**
* Read the element's post-absolute bounding rect and apply a compensating
* transform so it visually stays at its captured-`from` position. The first
* call in a batch pays one layout flush; subsequent calls in the same
* batch are flush-free because `transform` is compositor-only.
*/
set_transform: () => void;
/** Unfix the element position if the outro is aborted */
unfix: () => void;
}

Loading…
Cancel
Save