fix: improve animation heuristics (#10119)

* fix: improve animation heuristics

better fix

better fix

* improve-animation

* more fixes

* use rAF

* feedback

* fix absolute positioning

* fix more

* revert

* more fixes
pull/10138/head
Dominic Gannaway 10 months ago committed by GitHub
parent f3265c580c
commit 08579461b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: improve animation transition heuristics

@ -25,7 +25,6 @@ import {
mutable_source, mutable_source,
push_destroy_fn, push_destroy_fn,
render_effect, render_effect,
schedule_task,
set_signal_value, set_signal_value,
source source
} from './runtime.js'; } from './runtime.js';
@ -486,6 +485,17 @@ function reconcile_tracked_array(
key = is_computed_key ? keys[a] : item; key = is_computed_key ? keys[a] : item;
map_set(item_index, key, a); map_set(item_index, key, a);
} }
// If keys are animated, we need to do updates before actual moves
if (is_animated) {
for (b = start; b <= a_end; ++b) {
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
if (a !== undefined) {
item = array[a];
block = a_blocks[b];
update_each_item_block(block, item, a, flags);
}
}
}
for (b = start; b <= a_end; ++b) { for (b = start; b <= a_end; ++b) {
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
block = a_blocks[b]; block = a_blocks[b];
@ -501,22 +511,9 @@ function reconcile_tracked_array(
if (pos === MOVED_BLOCK) { if (pos === MOVED_BLOCK) {
mark_lis(sources); mark_lis(sources);
} }
// If keys are animated, we need to do updates before actual moves
var should_create;
if (is_animated) {
var i = b_length;
while (i-- > 0) {
b_end = i + start;
a = sources[i];
if (pos === MOVED_BLOCK) {
block = b_blocks[b_end];
item = array[b_end];
update_each_item_block(block, item, b_end, flags);
}
}
}
var last_block; var last_block;
var last_sibling; var last_sibling;
var should_create;
while (b_length-- > 0) { while (b_length-- > 0) {
b_end = b_length + start; b_end = b_length + start;
a = sources[b_length]; a = sources[b_length];
@ -715,7 +712,7 @@ function update_each_item_block(block, item, index, type) {
// Handle each item animations // Handle each item animations
const each_animation = block.a; const each_animation = block.a;
if (transitions !== null && (type & EACH_KEYED) !== 0 && each_animation !== null) { if (transitions !== null && (type & EACH_KEYED) !== 0 && each_animation !== null) {
each_animation(block, transitions, index, index_is_reactive); each_animation(block, transitions);
} }
if (index_is_reactive) { if (index_is_reactive) {
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index); set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index);

@ -38,6 +38,7 @@ let current_scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling // Used for handling scheduling
let is_micro_task_queued = false; let is_micro_task_queued = false;
let is_task_queued = false; let is_task_queued = false;
let is_raf_queued = false;
// Used for $inspect // Used for $inspect
export let is_batching_effect = false; export let is_batching_effect = false;
@ -52,7 +53,7 @@ let current_queued_effects = [];
/** @type {Array<() => void>} */ /** @type {Array<() => void>} */
let current_queued_tasks = []; let current_queued_tasks = [];
/** @type {Array<() => void>} */ /** @type {Array<() => void>} */
let current_queued_microtasks = []; let current_raf_tasks = [];
let flush_count = 0; let flush_count = 0;
// Handle signal reactivity tree dependencies and consumer // Handle signal reactivity tree dependencies and consumer
@ -586,11 +587,6 @@ function flush_queued_effects(effects) {
function process_microtask() { function process_microtask() {
is_micro_task_queued = false; is_micro_task_queued = false;
if (current_queued_microtasks.length > 0) {
const tasks = current_queued_microtasks.slice();
current_queued_microtasks = [];
run_all(tasks);
}
if (flush_count > 101) { if (flush_count > 101) {
return; return;
} }
@ -637,6 +633,13 @@ function process_task() {
run_all(tasks); run_all(tasks);
} }
function process_raf_task() {
is_raf_queued = false;
const tasks = current_raf_tasks.slice();
current_raf_tasks = [];
run_all(tasks);
}
/** /**
* @param {() => void} fn * @param {() => void} fn
* @returns {void} * @returns {void}
@ -653,12 +656,12 @@ export function schedule_task(fn) {
* @param {() => void} fn * @param {() => void} fn
* @returns {void} * @returns {void}
*/ */
export function schedule_microtask(fn) { export function schedule_raf_task(fn) {
if (!is_micro_task_queued) { if (!is_raf_queued) {
is_micro_task_queued = true; is_raf_queued = true;
queueMicrotask(process_microtask); requestAnimationFrame(process_raf_task);
} }
current_queued_microtasks.push(fn); current_raf_tasks.push(fn);
} }
/** /**
@ -721,8 +724,8 @@ export function flushSync(fn) {
if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) { if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) {
flushSync(); flushSync();
} }
if (is_micro_task_queued) { if (is_raf_queued) {
process_microtask(); process_raf_task();
} }
if (is_task_queued) { if (is_task_queued) {
process_task(); process_task();

@ -21,7 +21,7 @@ import {
managed_effect, managed_effect,
managed_pre_effect, managed_pre_effect,
mark_subtree_inert, mark_subtree_inert,
schedule_microtask, schedule_raf_task,
untrack untrack
} from './runtime.js'; } from './runtime.js';
import { raf } from './timing.js'; import { raf } from './timing.js';
@ -646,7 +646,8 @@ function each_item_transition(transition) {
transitions.delete(transition); transitions.delete(transition);
if (transition.r !== 'key') { if (transition.r !== 'key') {
for (let other of transitions) { for (let other of transitions) {
if (other.r === 'key' || other.r === 'in') { const type = other.r;
if (type === 'key' || type === 'in') {
transitions.delete(other); transitions.delete(other);
} }
} }
@ -664,26 +665,18 @@ function each_item_transition(transition) {
* *
* @param {import('./types.js').EachItemBlock} block * @param {import('./types.js').EachItemBlock} block
* @param {Set<import('./types.js').Transition>} transitions * @param {Set<import('./types.js').Transition>} transitions
* @param {number} index
* @param {boolean} index_is_reactive
*/ */
function each_item_animate(block, transitions, index, index_is_reactive) { function each_item_animate(block, transitions) {
let prev_index = block.i; const from_dom = /** @type {Element} */ (get_first_element(block));
if (index_is_reactive) { const from = from_dom.getBoundingClientRect();
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v; // Cancel any existing key transitions
} for (const transition of transitions) {
const items = block.p.v; const type = transition.r;
if (prev_index !== index && /** @type {number} */ (index) < items.length) { if (type === 'key') {
const from_dom = /** @type {Element} */ (get_first_element(block)); transition.c();
const from = from_dom.getBoundingClientRect();
// Cancel any existing key transitions
for (const transition of transitions) {
if (transition.r === 'key') {
transition.c();
}
} }
schedule_microtask(() => {
trigger_transitions(transitions, 'key', from);
});
} }
schedule_raf_task(() => {
trigger_transitions(transitions, 'key', from);
});
} }

@ -288,14 +288,7 @@ export type EachBlock = {
export type EachItemBlock = { export type EachItemBlock = {
/** transition */ /** transition */
a: a: null | ((block: EachItemBlock, transitions: Set<Transition>) => void);
| null
| ((
block: EachItemBlock,
transitions: Set<Transition>,
index: number,
index_is_reactive: boolean
) => void);
/** dom */ /** dom */
d: null | TemplateNode | Array<TemplateNode>; d: null | TemplateNode | Array<TemplateNode>;
/** effect */ /** effect */

@ -53,9 +53,9 @@ export default test({
divs = target.querySelectorAll('div'); divs = target.querySelectorAll('div');
assert.ok(~divs[0].style.transform); assert.ok(~divs[0].style.transform);
assert.equal(divs[1].style.transform, ''); assert.equal(divs[1].style.transform, 'translate(1px, 0px)');
assert.equal(divs[2].style.transform, ''); assert.equal(divs[2].style.transform, 'translate(1px, 0px)');
assert.equal(divs[3].style.transform, ''); assert.equal(divs[3].style.transform, 'translate(1px, 0px)');
assert.ok(~divs[4].style.transform); assert.ok(~divs[4].style.transform);
raf.tick(100); raf.tick(100);

Loading…
Cancel
Save