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,
push_destroy_fn,
render_effect,
schedule_task,
set_signal_value,
source
} from './runtime.js';
@ -486,6 +485,17 @@ function reconcile_tracked_array(
key = is_computed_key ? keys[a] : item;
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) {
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
block = a_blocks[b];
@ -501,22 +511,9 @@ function reconcile_tracked_array(
if (pos === MOVED_BLOCK) {
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_sibling;
var should_create;
while (b_length-- > 0) {
b_end = b_length + start;
a = sources[b_length];
@ -715,7 +712,7 @@ function update_each_item_block(block, item, index, type) {
// Handle each item animations
const each_animation = block.a;
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) {
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
let is_micro_task_queued = false;
let is_task_queued = false;
let is_raf_queued = false;
// Used for $inspect
export let is_batching_effect = false;
@ -52,7 +53,7 @@ let current_queued_effects = [];
/** @type {Array<() => void>} */
let current_queued_tasks = [];
/** @type {Array<() => void>} */
let current_queued_microtasks = [];
let current_raf_tasks = [];
let flush_count = 0;
// Handle signal reactivity tree dependencies and consumer
@ -586,11 +587,6 @@ function flush_queued_effects(effects) {
function process_microtask() {
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) {
return;
}
@ -637,6 +633,13 @@ function process_task() {
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
* @returns {void}
@ -653,12 +656,12 @@ export function schedule_task(fn) {
* @param {() => void} fn
* @returns {void}
*/
export function schedule_microtask(fn) {
if (!is_micro_task_queued) {
is_micro_task_queued = true;
queueMicrotask(process_microtask);
export function schedule_raf_task(fn) {
if (!is_raf_queued) {
is_raf_queued = true;
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) {
flushSync();
}
if (is_micro_task_queued) {
process_microtask();
if (is_raf_queued) {
process_raf_task();
}
if (is_task_queued) {
process_task();

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

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

Loading…
Cancel
Save