diff --git a/.changeset/soft-tigers-wink.md b/.changeset/soft-tigers-wink.md new file mode 100644 index 0000000000..561fbb69a6 --- /dev/null +++ b/.changeset/soft-tigers-wink.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve animation transition heuristics diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index c51af78572..e66574fba0 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -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} */ (block.i), index); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 38c7e3d12b..a475796e5c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -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(); diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 3317520e6d..6ce0f2f403 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -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} 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} */ (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); + }); } diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index af21817c69..31dd4f1162 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -288,14 +288,7 @@ export type EachBlock = { export type EachItemBlock = { /** transition */ - a: - | null - | (( - block: EachItemBlock, - transitions: Set, - index: number, - index_is_reactive: boolean - ) => void); + a: null | ((block: EachItemBlock, transitions: Set) => void); /** dom */ d: null | TemplateNode | Array; /** effect */ diff --git a/packages/svelte/tests/runtime-legacy/samples/dynamic-element-animation/_config.js b/packages/svelte/tests/runtime-legacy/samples/dynamic-element-animation/_config.js index 5a6f0818fb..6315a32d86 100644 --- a/packages/svelte/tests/runtime-legacy/samples/dynamic-element-animation/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/dynamic-element-animation/_config.js @@ -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);