Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/soft-tigers-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: improve animation transition heuristics
29 changes: 13 additions & 16 deletions packages/svelte/src/internal/client/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
mutable_source,
push_destroy_fn,
render_effect,
schedule_task,
set_signal_value,
source
} from './runtime.js';
Expand Down Expand Up @@ -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];
Expand All @@ -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];
Expand Down Expand Up @@ -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);
Expand Down
29 changes: 16 additions & 13 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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;

Expand All @@ -51,7 +52,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

Expand Down Expand Up @@ -585,11 +586,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;
}
Expand Down Expand Up @@ -636,6 +632,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}
Expand All @@ -652,12 +655,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);
}

/**
Expand Down Expand Up @@ -720,8 +723,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();
Expand Down
35 changes: 14 additions & 21 deletions packages/svelte/src/internal/client/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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);
});
}
9 changes: 1 addition & 8 deletions packages/svelte/src/internal/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down