Skip to content

Commit 0857946

Browse files
authored
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
1 parent f3265c5 commit 0857946

File tree

6 files changed

+52
-61
lines changed

6 files changed

+52
-61
lines changed

.changeset/soft-tigers-wink.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: improve animation transition heuristics

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

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
mutable_source,
2626
push_destroy_fn,
2727
render_effect,
28-
schedule_task,
2928
set_signal_value,
3029
source
3130
} from './runtime.js';
@@ -486,6 +485,17 @@ function reconcile_tracked_array(
486485
key = is_computed_key ? keys[a] : item;
487486
map_set(item_index, key, a);
488487
}
488+
// If keys are animated, we need to do updates before actual moves
489+
if (is_animated) {
490+
for (b = start; b <= a_end; ++b) {
491+
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
492+
if (a !== undefined) {
493+
item = array[a];
494+
block = a_blocks[b];
495+
update_each_item_block(block, item, a, flags);
496+
}
497+
}
498+
}
489499
for (b = start; b <= a_end; ++b) {
490500
a = map_get(item_index, /** @type {V} */ (a_blocks[b].k));
491501
block = a_blocks[b];
@@ -501,22 +511,9 @@ function reconcile_tracked_array(
501511
if (pos === MOVED_BLOCK) {
502512
mark_lis(sources);
503513
}
504-
// If keys are animated, we need to do updates before actual moves
505-
var should_create;
506-
if (is_animated) {
507-
var i = b_length;
508-
while (i-- > 0) {
509-
b_end = i + start;
510-
a = sources[i];
511-
if (pos === MOVED_BLOCK) {
512-
block = b_blocks[b_end];
513-
item = array[b_end];
514-
update_each_item_block(block, item, b_end, flags);
515-
}
516-
}
517-
}
518514
var last_block;
519515
var last_sibling;
516+
var should_create;
520517
while (b_length-- > 0) {
521518
b_end = b_length + start;
522519
a = sources[b_length];
@@ -715,7 +712,7 @@ function update_each_item_block(block, item, index, type) {
715712
// Handle each item animations
716713
const each_animation = block.a;
717714
if (transitions !== null && (type & EACH_KEYED) !== 0 && each_animation !== null) {
718-
each_animation(block, transitions, index, index_is_reactive);
715+
each_animation(block, transitions);
719716
}
720717
if (index_is_reactive) {
721718
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index);

packages/svelte/src/internal/client/runtime.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let current_scheduler_mode = FLUSH_MICROTASK;
3838
// Used for handling scheduling
3939
let is_micro_task_queued = false;
4040
let is_task_queued = false;
41+
let is_raf_queued = false;
4142
// Used for $inspect
4243
export let is_batching_effect = false;
4344

@@ -52,7 +53,7 @@ let current_queued_effects = [];
5253
/** @type {Array<() => void>} */
5354
let current_queued_tasks = [];
5455
/** @type {Array<() => void>} */
55-
let current_queued_microtasks = [];
56+
let current_raf_tasks = [];
5657
let flush_count = 0;
5758
// Handle signal reactivity tree dependencies and consumer
5859

@@ -586,11 +587,6 @@ function flush_queued_effects(effects) {
586587

587588
function process_microtask() {
588589
is_micro_task_queued = false;
589-
if (current_queued_microtasks.length > 0) {
590-
const tasks = current_queued_microtasks.slice();
591-
current_queued_microtasks = [];
592-
run_all(tasks);
593-
}
594590
if (flush_count > 101) {
595591
return;
596592
}
@@ -637,6 +633,13 @@ function process_task() {
637633
run_all(tasks);
638634
}
639635

636+
function process_raf_task() {
637+
is_raf_queued = false;
638+
const tasks = current_raf_tasks.slice();
639+
current_raf_tasks = [];
640+
run_all(tasks);
641+
}
642+
640643
/**
641644
* @param {() => void} fn
642645
* @returns {void}
@@ -653,12 +656,12 @@ export function schedule_task(fn) {
653656
* @param {() => void} fn
654657
* @returns {void}
655658
*/
656-
export function schedule_microtask(fn) {
657-
if (!is_micro_task_queued) {
658-
is_micro_task_queued = true;
659-
queueMicrotask(process_microtask);
659+
export function schedule_raf_task(fn) {
660+
if (!is_raf_queued) {
661+
is_raf_queued = true;
662+
requestAnimationFrame(process_raf_task);
660663
}
661-
current_queued_microtasks.push(fn);
664+
current_raf_tasks.push(fn);
662665
}
663666

664667
/**
@@ -721,8 +724,8 @@ export function flushSync(fn) {
721724
if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) {
722725
flushSync();
723726
}
724-
if (is_micro_task_queued) {
725-
process_microtask();
727+
if (is_raf_queued) {
728+
process_raf_task();
726729
}
727730
if (is_task_queued) {
728731
process_task();

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

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
managed_effect,
2222
managed_pre_effect,
2323
mark_subtree_inert,
24-
schedule_microtask,
24+
schedule_raf_task,
2525
untrack
2626
} from './runtime.js';
2727
import { raf } from './timing.js';
@@ -646,7 +646,8 @@ function each_item_transition(transition) {
646646
transitions.delete(transition);
647647
if (transition.r !== 'key') {
648648
for (let other of transitions) {
649-
if (other.r === 'key' || other.r === 'in') {
649+
const type = other.r;
650+
if (type === 'key' || type === 'in') {
650651
transitions.delete(other);
651652
}
652653
}
@@ -664,26 +665,18 @@ function each_item_transition(transition) {
664665
*
665666
* @param {import('./types.js').EachItemBlock} block
666667
* @param {Set<import('./types.js').Transition>} transitions
667-
* @param {number} index
668-
* @param {boolean} index_is_reactive
669668
*/
670-
function each_item_animate(block, transitions, index, index_is_reactive) {
671-
let prev_index = block.i;
672-
if (index_is_reactive) {
673-
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
674-
}
675-
const items = block.p.v;
676-
if (prev_index !== index && /** @type {number} */ (index) < items.length) {
677-
const from_dom = /** @type {Element} */ (get_first_element(block));
678-
const from = from_dom.getBoundingClientRect();
679-
// Cancel any existing key transitions
680-
for (const transition of transitions) {
681-
if (transition.r === 'key') {
682-
transition.c();
683-
}
669+
function each_item_animate(block, transitions) {
670+
const from_dom = /** @type {Element} */ (get_first_element(block));
671+
const from = from_dom.getBoundingClientRect();
672+
// Cancel any existing key transitions
673+
for (const transition of transitions) {
674+
const type = transition.r;
675+
if (type === 'key') {
676+
transition.c();
684677
}
685-
schedule_microtask(() => {
686-
trigger_transitions(transitions, 'key', from);
687-
});
688678
}
679+
schedule_raf_task(() => {
680+
trigger_transitions(transitions, 'key', from);
681+
});
689682
}

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,7 @@ export type EachBlock = {
288288

289289
export type EachItemBlock = {
290290
/** transition */
291-
a:
292-
| null
293-
| ((
294-
block: EachItemBlock,
295-
transitions: Set<Transition>,
296-
index: number,
297-
index_is_reactive: boolean
298-
) => void);
291+
a: null | ((block: EachItemBlock, transitions: Set<Transition>) => void);
299292
/** dom */
300293
d: null | TemplateNode | Array<TemplateNode>;
301294
/** effect */

packages/svelte/tests/runtime-legacy/samples/dynamic-element-animation/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export default test({
5353

5454
divs = target.querySelectorAll('div');
5555
assert.ok(~divs[0].style.transform);
56-
assert.equal(divs[1].style.transform, '');
57-
assert.equal(divs[2].style.transform, '');
58-
assert.equal(divs[3].style.transform, '');
56+
assert.equal(divs[1].style.transform, 'translate(1px, 0px)');
57+
assert.equal(divs[2].style.transform, 'translate(1px, 0px)');
58+
assert.equal(divs[3].style.transform, 'translate(1px, 0px)');
5959
assert.ok(~divs[4].style.transform);
6060

6161
raf.tick(100);

0 commit comments

Comments
 (0)