diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index 6fb244d074..b517f7d372 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -214,7 +214,7 @@ function animate(element, options, counterpart, t2, callback) { if (css) { // WAAPI var keyframes = []; - var n = options.duration / (1000 / 60); // TODO should be adjusted_duration? + var n = adjusted_duration / (1000 / 60); for (var i = 0; i <= n; i += 1) { var t = t1 + delta * easing(i / n); diff --git a/packages/svelte/tests/animation-helpers.js b/packages/svelte/tests/animation-helpers.js index 4c4c5b1254..a941a643cc 100644 --- a/packages/svelte/tests/animation-helpers.js +++ b/packages/svelte/tests/animation-helpers.js @@ -83,12 +83,26 @@ class Animation { } /** - * @param {number} target_frame + * @param {number} t */ - #apply_keyframe(target_frame) { - const keyframes = this.#keyframes; - const keyframes_size = keyframes.length - 1; - const frame = keyframes[Math.min(keyframes_size, Math.floor(keyframes.length * target_frame))]; + #apply_keyframe(t) { + const n = Math.min(1, Math.max(0, t)) * (this.#keyframes.length - 1); + + const lower = this.#keyframes[Math.floor(n)]; + const upper = this.#keyframes[Math.ceil(n)]; + + let frame = lower; + if (lower !== upper) { + frame = {}; + + for (const key in lower) { + frame[key] = interpolate( + /** @type {string} */ (lower[key]), + /** @type {string} */ (upper[key]), + n % 1 + ); + } + } for (let prop in frame) { // @ts-ignore @@ -114,6 +128,40 @@ class Animation { } } +/** + * @param {string} a + * @param {string} b + * @param {number} p + */ +function interpolate(a, b, p) { + if (a === b) return a; + + const fallback = p < 0.5 ? a : b; + + const a_match = a.match(/[\d.]+|[^\d.]+/g); + const b_match = b.match(/[\d.]+|[^\d.]+/g); + + if (!a_match || !b_match) return fallback; + if (a_match.length !== b_match.length) return fallback; + + let result = ''; + + for (let i = 0; i < a_match.length; i += 2) { + const a_num = parseFloat(a_match[i]); + const b_num = parseFloat(b_match[i]); + result += a_num + (b_num - a_num) * p; + + if (a_match[i + 1] !== b_match[i + 1]) { + // bail + return fallback; + } + + result += a_match[i + 1] ?? ''; + } + + return result; +} + /** * @param {Keyframe[]} keyframes * @param {{duration: number}} options diff --git a/packages/svelte/tests/runtime-legacy/samples/transition-css-iframe/_config.js b/packages/svelte/tests/runtime-legacy/samples/transition-css-iframe/_config.js index 313ebafcb8..255de7dca7 100644 --- a/packages/svelte/tests/runtime-legacy/samples/transition-css-iframe/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/transition-css-iframe/_config.js @@ -18,7 +18,7 @@ export default test({ raf.tick(25); assert.equal(div.style.opacity, '0.25'); - raf.tick(49); - assert.equal(div.style.opacity, '0'); + raf.tick(35); + assert.equal(div.style.opacity, '0.18333333333333335'); } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/transition-css-in-out-in/_config.js b/packages/svelte/tests/runtime-legacy/samples/transition-css-in-out-in/_config.js index ba518f8773..3d87dc6e10 100644 --- a/packages/svelte/tests/runtime-legacy/samples/transition-css-in-out-in/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/transition-css-in-out-in/_config.js @@ -18,10 +18,9 @@ export default test({ raf.tick(75); - // these numbers look wrong because they're not lerped... maybe we should fix that - assert.equal(div.style.scale, '0.8333333333333334'); // intro continues while outro plays - assert.equal(div.style.opacity, '0.8333333333333334'); - assert.equal(div.style.rotate, '300deg'); + assert.equal(div.style.scale, '0.75'); // intro continues while outro plays + assert.equal(div.style.opacity, '0.75'); + assert.equal(div.style.rotate, '270deg'); component.visible = true;