Which functions implement animations and transitions?
I'll help you find functions that implement animations and transitions in the Svelte codebase. Let me search for relevant code patterns.
Tool Call: Search files for regex (animate|animation|transition|easing|tween|spring|fly|slide|fade|scale|draw|crossfade)
Status: Completed
Showing matches 1-20 (there were more matches found; use offset: 20 to see next page):
for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition', 'legacy']) {
fs.writeFileSync(`${dir}/${name}.d.ts`, "import './types/index.js';\n");
}
modules: {
[pkg.name]: `${dir}/src/index.d.ts`,
[`${pkg.name}/action`]: `${dir}/src/action/public.d.ts`,
[`${pkg.name}/animate`]: `${dir}/src/animate/public.d.ts`,
[`${pkg.name}/attachments`]: `${dir}/src/attachments/public.d.ts`,
[`${pkg.name}/compiler`]: `${dir}/src/compiler/public.d.ts`,
[`${pkg.name}/easing`]: `${dir}/src/easing/index.js`,
[`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`,
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
[`${pkg.name}/reactivity`]: `${dir}/src/reactivity/index-client.js`,
[`${pkg.name}/reactivity/window`]: `${dir}/src/reactivity/window/index.js`,
8 lines remaining in ancestor node. Read the file to see all.
[`${pkg.name}/server`]: `${dir}/src/server/index.d.ts`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
[`${pkg.name}/events`]: `${dir}/src/events/public.d.ts`,
// TODO remove in Svelte 6
/** @import { FlipParams, AnimationConfig } from './public.js' */
import { cubicOut } from '../easing/index.js';
/**
* The flip function calculates the start and end position of an element and animates between them, translating the x and y values.
* `flip` stands for [First, Last, Invert, Play](https://aerotwist.com/blog/flip-your-animations/).
*
* @param {Element} node
* @param {{ from: DOMRect; to: DOMRect }} fromTo
* @param {FlipParams} params
* @returns {AnimationConfig}
*/
67 lines remaining in ancestor node. Read the file to see all.
export function flip(node, { from, to }, params = {}) {
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
var style = getComputedStyle(node);
// find the transform origin, expressed as a pair of values between 0 and 1
var transform = style.transform === 'none' ? '' : style.transform;
var [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
ox /= node.clientWidth;
oy /= node.clientHeight;
35 lines remaining in ancestor node. Read the file to see all.
var dy = (fy - ty) * sy;
// find the relative scale at the start of the transform
var dsx = from.width / to.width;
var dsy = from.height / to.height;
return {
delay,
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
easing,
css: (t, u) => {
var x = u * dx;
var y = u * dy;
var sx = t + u * dsx;
var sy = t + u * dsy;
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
}
1 lines remaining in ancestor node. Read the file to see all.
/**
* An element can only have one 'animate' directive
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function animation_duplicate(node) {
e(node, 'animation_duplicate', `An element can only have one 'animate' directive\nhttps://svelte.dev/e/animation_duplicate`);
}
/**
* An element that uses the `animate:` directive must be the only child of a keyed `{#each ...}` block
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function animation_invalid_placement(node) {
e(node, 'animation_invalid_placement', `An element that uses the \`animate:\` directive must be the only child of a keyed \`{#each ...}\` block\nhttps://svelte.dev/e/animation_invalid_placement`);
}
/**
* An element that uses the `animate:` directive must be the only child of a keyed `{#each ...}` block. Did you forget to add a key to your each block?
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function animation_missing_key(node) {
e(node, 'animation_missing_key', `An element that uses the \`animate:\` directive must be the only child of a keyed \`{#each ...}\` block. Did you forget to add a key to your each block?\nhttps://svelte.dev/e/animation_missing_key`);
}
export function transition_conflict(node, type, existing) {
e(node, 'transition_conflict', `Cannot use \`${type}:\` alongside existing \`${existing}:\` directive\nhttps://svelte.dev/e/transition_conflict`);
}
export function transition_duplicate(node, type) {
e(node, 'transition_duplicate', `Cannot use multiple \`${type}:\` directives on a single element\nhttps://svelte.dev/e/transition_duplicate`);
}
};
},
AnimateDirective(node) {
return { ...node, type: 'Animation' };
},
};
},
TransitionDirective(node) {
return { ...node, type: 'Transition' };
},
) {
let start = code.indexOf('(') + 1;
let is_global = false;
const global_str = ':global';
const next_global = code.indexOf(global_str);
const str_between = code.substring(start, next_global);
if (!str_between.trim()) {
is_global = true;
start += global_str.length;
} else {
24 lines remaining in ancestor node. Read the file to see all.
metadata: {
// unpopulated at first, differs between types
}
while ((attribute = read(parser))) {
// animate and transition can only be specified once per element so no need
// to check here, use can be used multiple times, same for the on directive
// finally let already has error handling in case of duplicate variable names
if (
attribute.type === 'Attribute' ||
attribute.type === 'BindDirective' ||
attribute.type === 'StyleDirective' ||
attribute.type === 'ClassDirective'
) {
// `bind:attribute` and `attribute` are just the same but `class:attribute`,
14 lines remaining in ancestor node. Read the file to see all.
Let me continue searching for more specific animation and transition implementation files:
Tool Call: 3 matches Status: Completed
file:///Users/faraaz/oss/svelte/packages/svelte/src/animate
file:///Users/faraaz/oss/svelte/packages/svelte/src/animate/index.js
file:///Users/faraaz/oss/svelte/packages/svelte/src/animate/public.d.ts
Tool Call: 3 matches Status: Completed
file:///Users/faraaz/oss/svelte/packages/svelte/src/transition
file:///Users/faraaz/oss/svelte/packages/svelte/src/transition/index.js
file:///Users/faraaz/oss/svelte/packages/svelte/src/transition/public.d.ts
Tool Call: 9 matches Status: Completed
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion/index.js
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion/private.d.ts
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion/public.d.ts
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion/spring.js
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion/tweened.js
file:///Users/faraaz/oss/svelte/packages/svelte/src/motion/utils.js
file:///Users/faraaz/oss/svelte/packages/svelte/tests/motion
file:///Users/faraaz/oss/svelte/packages/svelte/tests/motion/test.ts
Tool Call: 2 matches Status: Completed
file:///Users/faraaz/oss/svelte/packages/svelte/src/easing
file:///Users/faraaz/oss/svelte/packages/svelte/src/easing/index.js
Now let me read the key animation and transition implementation files:
Tool Call: index.js Status: Completed
/** @import { FlipParams, AnimationConfig } from './public.js' */
import { cubicOut } from '../easing/index.js';
/**
* The flip function calculates the start and end position of an element and animates between them, translating the x and y values.
* `flip` stands for [First, Last, Invert, Play](https://aerotwist.com/blog/flip-your-animations/).
*
* @param {Element} node
* @param {{ from: DOMRect; to: DOMRect }} fromTo
* @param {FlipParams} params
* @returns {AnimationConfig}
*/
export function flip(node, { from, to }, params = {}) {
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
var style = getComputedStyle(node);
// find the transform origin, expressed as a pair of values between 0 and 1
var transform = style.transform === 'none' ? '' : style.transform;
var [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
ox /= node.clientWidth;
oy /= node.clientHeight;
// calculate effect of parent transforms and zoom
var zoom = get_zoom(node); // https://drafts.csswg.org/css-viewport/#effective-zoom
var sx = node.clientWidth / to.width / zoom;
var sy = node.clientHeight / to.height / zoom;
// find the starting position of the transform origin
var fx = from.left + from.width * ox;
var fy = from.top + from.height * oy;
// find the ending position of the transform origin
var tx = to.left + to.width * ox;
var ty = to.top + to.height * oy;
// find the translation at the start of the transform
var dx = (fx - tx) * sx;
var dy = (fy - ty) * sy;
// find the relative scale at the start of the transform
var dsx = from.width / to.width;
var dsy = from.height / to.height;
return {
delay,
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
easing,
css: (t, u) => {
var x = u * dx;
var y = u * dy;
var sx = t + u * dsx;
var sy = t + u * dsy;
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
}
};
}
/**
* @param {Element} element
*/
function get_zoom(element) {
if ('currentCSSZoom' in element) {
return /** @type {number} */ (element.currentCSSZoom);
}
/** @type {Element | null} */
var current = element;
var zoom = 1;
while (current !== null) {
zoom *= +getComputedStyle(current).zoom;
current = /** @type {Element | null} */ (current.parentElement);
}
return zoom;
}Tool Call: index.js Status: Completed
/** @import { BlurParams, CrossfadeParams, DrawParams, FadeParams, FlyParams, ScaleParams, SlideParams, TransitionConfig } from './public' */
import { DEV } from 'esm-env';
import * as w from '../internal/client/warnings.js';
/** @param {number} x */
const linear = (x) => x;
/** @param {number} t */
function cubic_out(t) {
const f = t - 1.0;
return f * f * f + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
function cubic_in_out(t) {
return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0;
}
/** @param {number | string} value
* @returns {[number, string]}
*/
function split_css_unit(value) {
const split = typeof value === 'string' && value.match(/^\s*(-?[\d.]+)([^\s]*)\s*$/);
return split ? [parseFloat(split[1]), split[2] || 'px'] : [/** @type {number} */ (value), 'px'];
}
/**
* Animates a `blur` filter alongside an element's opacity.
*
* @param {Element} node
* @param {BlurParams} [params]
* @returns {TransitionConfig}
*/
export function blur(
node,
{ delay = 0, duration = 400, easing = cubic_in_out, amount = 5, opacity = 0 } = {}
) {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const f = style.filter === 'none' ? '' : style.filter;
const od = target_opacity * (1 - opacity);
const [value, unit] = split_css_unit(amount);
return {
delay,
duration,
easing,
css: (_t, u) => `opacity: ${target_opacity - od * u}; filter: ${f} blur(${u * value}${unit});`
};
}
/**
* Animates the opacity of an element from 0 to the current opacity for `in` transitions and from the current opacity to 0 for `out` transitions.
*
* @param {Element} node
* @param {FadeParams} [params]
* @returns {TransitionConfig}
*/
export function fade(node, { delay = 0, duration = 400, easing = linear } = {}) {
const o = +getComputedStyle(node).opacity;
return {
delay,
duration,
easing,
css: (t) => `opacity: ${t * o}`
};
}
/**
* Animates the x and y positions and the opacity of an element. `in` transitions animate from the provided values, passed as parameters to the element's default values. `out` transitions animate from the element's default values to the provided values.
*
* @param {Element} node
* @param {FlyParams} [params]
* @returns {TransitionConfig}
*/
export function fly(
node,
{ delay = 0, duration = 400, easing = cubic_out, x = 0, y = 0, opacity = 0 } = {}
) {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
const od = target_opacity * (1 - opacity);
const [x_value, x_unit] = split_css_unit(x);
const [y_value, y_unit] = split_css_unit(y);
return {
delay,
duration,
easing,
css: (t, u) => `
transform: ${transform} translate(${(1 - t) * x_value}${x_unit}, ${(1 - t) * y_value}${y_unit});
opacity: ${target_opacity - od * u}`
};
}
var slide_warning = false;
/**
* Slides an element in and out.
*
* @param {Element} node
* @param {SlideParams} [params]
* @returns {TransitionConfig}
*/
export function slide(node, { delay = 0, duration = 400, easing = cubic_out, axis = 'y' } = {}) {
const style = getComputedStyle(node);
if (DEV && !slide_warning && /(contents|inline|table)/.test(style.display)) {
slide_warning = true;
Promise.resolve().then(() => (slide_warning = false));
w.transition_slide_display(style.display);
}
const opacity = +style.opacity;
const primary_property = axis === 'y' ? 'height' : 'width';
const primary_property_value = parseFloat(style[primary_property]);
const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right'];
const capitalized_secondary_properties = secondary_properties.map(
(e) => /** @type {'Left' | 'Right' | 'Top' | 'Bottom'} */ (`${e[0].toUpperCase()}${e.slice(1)}`)
);
const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]);
const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]);
const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]);
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]);
const border_width_start_value = parseFloat(
style[`border${capitalized_secondary_properties[0]}Width`]
);
const border_width_end_value = parseFloat(
style[`border${capitalized_secondary_properties[1]}Width`]
);
return {
delay,
duration,
easing,
css: (t) =>
'overflow: hidden;' +
`opacity: ${Math.min(t * 20, 1) * opacity};` +
`${primary_property}: ${t * primary_property_value}px;` +
`padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
`padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
`margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
`margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
`border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
`border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;` +
`min-${primary_property}: 0`
};
}
/**
* Animates the opacity and scale of an element. `in` transitions animate from the provided values, passed as parameters, to an element's current (default) values. `out` transitions animate from an element's default values to the provided values.
*
* @param {Element} node
* @param {ScaleParams} [params]
* @returns {TransitionConfig}
*/
export function scale(
node,
{ delay = 0, duration = 400, easing = cubic_out, start = 0, opacity = 0 } = {}
) {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
const sd = 1 - start;
const od = target_opacity * (1 - opacity);
return {
delay,
duration,
easing,
css: (_t, u) => `
transform: ${transform} scale(${1 - sd * u});
opacity: ${target_opacity - od * u}
`
};
}
/**
* Animates the stroke of an SVG element, like a snake in a tube. `in` transitions begin with the path invisible and draw the path to the screen over time. `out` transitions start in a visible state and gradually erase the path. `draw` only works with elements that have a `getTotalLength` method, like `<path>` and `<polyline>`.
*
* @param {SVGElement & { getTotalLength(): number }} node
* @param {DrawParams} [params]
* @returns {TransitionConfig}
*/
export function draw(node, { delay = 0, speed, duration, easing = cubic_in_out } = {}) {
let len = node.getTotalLength();
const style = getComputedStyle(node);
if (style.strokeLinecap !== 'butt') {
len += parseInt(style.strokeWidth);
}
if (duration === undefined) {
if (speed === undefined) {
duration = 800;
} else {
duration = len / speed;
}
} else if (typeof duration === 'function') {
duration = duration(len);
}
return {
delay,
duration,
easing,
css: (_, u) => `
stroke-dasharray: ${len};
stroke-dashoffset: ${u * len};
`
};
}
/**
* @template T
* @template S
* @param {T} tar
* @param {S} src
* @returns {T & S}
*/
function assign(tar, src) {
// @ts-ignore
for (const k in src) tar[k] = src[k];
return /** @type {T & S} */ (tar);
}
/**
* The `crossfade` function creates a pair of [transitions](https://svelte.dev/docs/svelte/transition) called `send` and `receive`. When an element is 'sent', it looks for a corresponding element being 'received', and generates a transition that transforms the element to its counterpart's position and fades it out. When an element is 'received', the reverse happens. If there is no counterpart, the `fallback` transition is used.
*
* @param {CrossfadeParams & {
* fallback?: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig;
* }} params
* @returns {[(node: any, params: CrossfadeParams & { key: any; }) => () => TransitionConfig, (node: any, params: CrossfadeParams & { key: any; }) => () => TransitionConfig]}
*/
export function crossfade({ fallback, ...defaults }) {
/** @type {Map<any, Element>} */
const to_receive = new Map();
/** @type {Map<any, Element>} */
const to_send = new Map();
/**
* @param {Element} from_node
* @param {Element} node
* @param {CrossfadeParams} params
* @returns {TransitionConfig}
*/
function crossfade(from_node, node, params) {
const {
delay = 0,
duration = /** @param {number} d */ (d) => Math.sqrt(d) * 30,
easing = cubic_out
} = assign(assign({}, defaults), params);
const from = from_node.getBoundingClientRect();
const to = node.getBoundingClientRect();
const dx = from.left - to.left;
const dy = from.top - to.top;
const dw = from.width / to.width;
const dh = from.height / to.height;
const d = Math.sqrt(dx * dx + dy * dy);
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
const opacity = +style.opacity;
return {
delay,
duration: typeof duration === 'function' ? duration(d) : duration,
easing,
css: (t, u) => `
opacity: ${t * opacity};
transform-origin: top left;
transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${
t + (1 - t) * dh
});
`
};
}
/**
* @param {Map<any, Element>} items
* @param {Map<any, Element>} counterparts
* @param {boolean} intro
* @returns {(node: any, params: CrossfadeParams & { key: any; }) => () => TransitionConfig}
*/
function transition(items, counterparts, intro) {
// @ts-expect-error TODO improve typings (are the public types wrong?)
return (node, params) => {
items.set(params.key, node);
return () => {
if (counterparts.has(params.key)) {
const other_node = counterparts.get(params.key);
counterparts.delete(params.key);
return crossfade(/** @type {Element} */ (other_node), node, params);
}
// if the node is disappearing altogether
// (i.e. wasn't claimed by the other list)
// then we need to supply an outro
items.delete(params.key);
return fallback && fallback(node, params, intro);
};
};
}
return [transition(to_send, to_receive, false), transition(to_receive, to_send, true)];
}Tool Call: index.js Status: Completed
import { MediaQuery } from 'svelte/reactivity';
export * from './spring.js';
export * from './tweened.js';
/**
* A [media query](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery) that matches if the user [prefers reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).
*
* ```svelte
* <script>
* import { prefersReducedMotion } from 'svelte/motion';
* import { fly } from 'svelte/transition';
*
* let visible = $state(false);
* </script>
*
* <button onclick={() => visible = !visible}>
* toggle
* </button>
*
* {#if visible}
* <p transition:fly={{ y: prefersReducedMotion.current ? 0 : 200 }}>
* flies in, unless the user prefers reduced motion
* </p>
* {/if}
* ```
* @type {MediaQuery}
* @since 5.7.0
*/
export const prefersReducedMotion = /*@__PURE__*/ new MediaQuery(
'(prefers-reduced-motion: reduce)'
);Tool Call: spring.js Status: Completed
/** @import { Task } from '#client' */
/** @import { SpringOpts, SpringUpdateOpts, TickContext } from './private.js' */
/** @import { Spring as SpringStore } from './public.js' */
import { writable } from '../store/shared/index.js';
import { loop } from '../internal/client/loop.js';
import { raf } from '../internal/client/timing.js';
import { is_date } from './utils.js';
import { set, source } from '../internal/client/reactivity/sources.js';
import { render_effect } from '../internal/client/reactivity/effects.js';
import { tag } from '../internal/client/dev/tracing.js';
import { get } from '../internal/client/runtime.js';
import { deferred, noop } from '../internal/shared/utils.js';
import { DEV } from 'esm-env';
/**
* @template T
* @param {TickContext} ctx
* @param {T} last_value
* @param {T} current_value
* @param {T} target_value
* @returns {T}
*/
function tick_spring(ctx, last_value, current_value, target_value) {
if (typeof current_value === 'number' || is_date(current_value)) {
// @ts-ignore
const delta = target_value - current_value;
// @ts-ignore
const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0
const spring = ctx.opts.stiffness * delta;
const damper = ctx.opts.damping * velocity;
const acceleration = (spring - damper) * ctx.inv_mass;
const d = (velocity + acceleration) * ctx.dt;
if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) {
return target_value; // settled
} else {
ctx.settled = false; // signal loop to keep ticking
// @ts-ignore
return is_date(current_value) ? new Date(current_value.getTime() + d) : current_value + d;
}
} else if (Array.isArray(current_value)) {
// @ts-ignore
return current_value.map((_, i) =>
// @ts-ignore
tick_spring(ctx, last_value[i], current_value[i], target_value[i])
);
} else if (typeof current_value === 'object') {
const next_value = {};
for (const k in current_value) {
// @ts-ignore
next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);
}
// @ts-ignore
return next_value;
} else {
throw new Error(`Cannot spring ${typeof current_value} values`);
}
}
/**
* The spring function in Svelte creates a store whose value is animated, with a motion that simulates the behavior of a spring. This means when the value changes, instead of transitioning at a steady rate, it "bounces" like a spring would, depending on the physics parameters provided. This adds a level of realism to the transitions and can enhance the user experience.
*
* @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead
* @template [T=any]
* @param {T} [value]
* @param {SpringOpts} [opts]
* @returns {SpringStore<T>}
*/
export function spring(value, opts = {}) {
const store = writable(value);
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
/** @type {number} */
let last_time;
/** @type {Task | null} */
let task;
/** @type {object} */
let current_token;
let last_value = /** @type {T} */ (value);
let target_value = /** @type {T | undefined} */ (value);
let inv_mass = 1;
let inv_mass_recovery_rate = 0;
let cancel_task = false;
/**
* @param {T} new_value
* @param {SpringUpdateOpts} opts
* @returns {Promise<void>}
*/
function set(new_value, opts = {}) {
target_value = new_value;
const token = (current_token = {});
if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) {
cancel_task = true; // cancel any running animation
last_time = raf.now();
last_value = new_value;
store.set((value = target_value));
return Promise.resolve();
} else if (opts.soft) {
const rate = opts.soft === true ? 0.5 : +opts.soft;
inv_mass_recovery_rate = 1 / (rate * 60);
inv_mass = 0; // infinite mass, unaffected by spring forces
}
if (!task) {
last_time = raf.now();
cancel_task = false;
task = loop((now) => {
if (cancel_task) {
cancel_task = false;
task = null;
return false;
}
inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
// clamp elapsed time to 1/30th of a second, so that longer pauses
// (blocked thread or inactive tab) don't cause the spring to go haywire
const elapsed = Math.min(now - last_time, 1000 / 30);
/** @type {TickContext} */
const ctx = {
inv_mass,
opts: spring,
settled: true,
dt: (elapsed * 60) / 1000
};
// @ts-ignore
const next_value = tick_spring(ctx, last_value, value, target_value);
last_time = now;
last_value = /** @type {T} */ (value);
store.set((value = /** @type {T} */ (next_value)));
if (ctx.settled) {
task = null;
}
return !ctx.settled;
});
}
return new Promise((fulfil) => {
/** @type {Task} */ (task).promise.then(() => {
if (token === current_token) fulfil();
});
});
}
/** @type {SpringStore<T>} */
// @ts-expect-error - class-only properties are missing
const spring = {
set,
update: (fn, opts) => set(fn(/** @type {T} */ (target_value), /** @type {T} */ (value)), opts),
subscribe: store.subscribe,
stiffness,
damping,
precision
};
return spring;
}
/**
* A wrapper for a value that behaves in a spring-like fashion. Changes to `spring.target` will cause `spring.current` to
* move towards it over time, taking account of the `spring.stiffness` and `spring.damping` parameters.
*
* ```svelte
* <script>
* import { Spring } from 'svelte/motion';
*
* const spring = new Spring(0);
* </script>
*
* <input type="range" bind:value={spring.target} />
* <input type="range" bind:value={spring.current} disabled />
* ```
* @template T
* @since 5.8.0
*/
export class Spring {
#stiffness = source(0.15);
#damping = source(0.8);
#precision = source(0.01);
#current;
#target;
#last_value = /** @type {T} */ (undefined);
#last_time = 0;
#inverse_mass = 1;
#momentum = 0;
/** @type {import('../internal/client/types').Task | null} */
#task = null;
/** @type {ReturnType<typeof deferred> | null} */
#deferred = null;
/**
* @param {T} value
* @param {SpringOpts} [options]
*/
constructor(value, options = {}) {
this.#current = DEV ? tag(source(value), 'Spring.current') : source(value);
this.#target = DEV ? tag(source(value), 'Spring.target') : source(value);
if (typeof options.stiffness === 'number') this.#stiffness.v = clamp(options.stiffness, 0, 1);
if (typeof options.damping === 'number') this.#damping.v = clamp(options.damping, 0, 1);
if (typeof options.precision === 'number') this.#precision.v = options.precision;
if (DEV) {
tag(this.#stiffness, 'Spring.stiffness');
tag(this.#damping, 'Spring.damping');
tag(this.#precision, 'Spring.precision');
tag(this.#current, 'Spring.current');
tag(this.#target, 'Spring.target');
}
}
/**
* Create a spring whose value is bound to the return value of `fn`. This must be called
* inside an effect root (for example, during component initialisation).
*
* ```svelte
* <script>
* import { Spring } from 'svelte/motion';
*
* let { number } = $props();
*
* const spring = Spring.of(() => number);
* </script>
* ```
* @template U
* @param {() => U} fn
* @param {SpringOpts} [options]
*/
static of(fn, options) {
const spring = new Spring(fn(), options);
render_effect(() => {
spring.set(fn());
});
return spring;
}
/** @param {T} value */
#update(value) {
set(this.#target, value);
this.#current.v ??= value;
this.#last_value ??= this.#current.v;
if (!this.#task) {
this.#last_time = raf.now();
var inv_mass_recovery_rate = 1000 / (this.#momentum * 60);
this.#task ??= loop((now) => {
this.#inverse_mass = Math.min(this.#inverse_mass + inv_mass_recovery_rate, 1);
// clamp elapsed time to 1/30th of a second, so that longer pauses
// (blocked thread or inactive tab) don't cause the spring to go haywire
const elapsed = Math.min(now - this.#last_time, 1000 / 30);
/** @type {import('./private').TickContext} */
const ctx = {
inv_mass: this.#inverse_mass,
opts: {
stiffness: this.#stiffness.v,
damping: this.#damping.v,
precision: this.#precision.v
},
settled: true,
dt: (elapsed * 60) / 1000
};
var next = tick_spring(ctx, this.#last_value, this.#current.v, this.#target.v);
this.#last_value = this.#current.v;
this.#last_time = now;
set(this.#current, next);
if (ctx.settled) {
this.#task = null;
}
return !ctx.settled;
});
}
return this.#task.promise;
}
/**
* Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it.
*
* If `options.instant` is `true`, `spring.current` immediately matches `spring.target`.
*
* If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for
* the specified number of milliseconds. This is useful for things like 'fling' gestures.
*
* @param {T} value
* @param {SpringUpdateOpts} [options]
*/
set(value, options) {
this.#deferred?.reject(new Error('Aborted'));
if (options?.instant || this.#current.v === undefined) {
this.#task?.abort();
this.#task = null;
set(this.#current, set(this.#target, value));
this.#last_value = value;
return Promise.resolve();
}
if (options?.preserveMomentum) {
this.#inverse_mass = 0;
this.#momentum = options.preserveMomentum;
}
var d = (this.#deferred = deferred());
d.promise.catch(noop);
this.#update(value).then(() => {
if (d !== this.#deferred) return;
d.resolve(undefined);
});
return d.promise;
}
get current() {
return get(this.#current);
}
get damping() {
return get(this.#damping);
}
set damping(v) {
set(this.#damping, clamp(v, 0, 1));
}
get precision() {
return get(this.#precision);
}
set precision(v) {
set(this.#precision, v);
}
get stiffness() {
return get(this.#stiffness);
}
set stiffness(v) {
set(this.#stiffness, clamp(v, 0, 1));
}
get target() {
return get(this.#target);
}
set target(v) {
this.set(v);
}
}
/**
* @param {number} n
* @param {number} min
* @param {number} max
*/
function clamp(n, min, max) {
return Math.max(min, Math.min(max, n));
}Tool Call: tweened.js Status: Completed
/** @import { Task } from '../internal/client/types' */
/** @import { Tweened } from './public' */
/** @import { TweenedOptions } from './private' */
import { writable } from '../store/shared/index.js';
import { raf } from '../internal/client/timing.js';
import { loop } from '../internal/client/loop.js';
import { linear } from '../easing/index.js';
import { is_date } from './utils.js';
import { set, source } from '../internal/client/reactivity/sources.js';
import { tag } from '../internal/client/dev/tracing.js';
import { get, render_effect } from 'svelte/internal/client';
import { DEV } from 'esm-env';
/**
* @template T
* @param {T} a
* @param {T} b
* @returns {(t: number) => T}
*/
function get_interpolator(a, b) {
if (a === b || a !== a) return () => a;
const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error('Cannot interpolate values of different type');
}
if (Array.isArray(a)) {
const arr = /** @type {Array<any>} */ (b).map((bi, i) => {
return get_interpolator(/** @type {Array<any>} */ (a)[i], bi);
});
// @ts-ignore
return (t) => arr.map((fn) => fn(t));
}
if (type === 'object') {
if (!a || !b) {
throw new Error('Object cannot be null');
}
if (is_date(a) && is_date(b)) {
const an = a.getTime();
const bn = b.getTime();
const delta = bn - an;
// @ts-ignore
return (t) => new Date(an + t * delta);
}
const keys = Object.keys(b);
/** @type {Record<string, (t: number) => T>} */
const interpolators = {};
keys.forEach((key) => {
// @ts-ignore
interpolators[key] = get_interpolator(a[key], b[key]);
});
// @ts-ignore
return (t) => {
/** @type {Record<string, any>} */
const result = {};
keys.forEach((key) => {
result[key] = interpolators[key](t);
});
return result;
};
}
if (type === 'number') {
const delta = /** @type {number} */ (b) - /** @type {number} */ (a);
// @ts-ignore
return (t) => a + t * delta;
}
// for non-numeric values, snap to the final value immediately
return () => b;
}
/**
* A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time.
*
* @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead
* @template T
* @param {T} [value]
* @param {TweenedOptions<T>} [defaults]
* @returns {Tweened<T>}
*/
export function tweened(value, defaults = {}) {
const store = writable(value);
/** @type {Task} */
let task;
let target_value = value;
/**
* @param {T} new_value
* @param {TweenedOptions<T>} [opts]
*/
function set(new_value, opts) {
target_value = new_value;
if (value == null) {
store.set((value = new_value));
return Promise.resolve();
}
/** @type {Task | null} */
let previous_task = task;
let started = false;
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = { ...defaults, ...opts };
if (duration === 0) {
if (previous_task) {
previous_task.abort();
previous_task = null;
}
store.set((value = target_value));
return Promise.resolve();
}
const start = raf.now() + delay;
/** @type {(t: number) => T} */
let fn;
task = loop((now) => {
if (now < start) return true;
if (!started) {
fn = interpolate(/** @type {any} */ (value), new_value);
if (typeof duration === 'function')
duration = duration(/** @type {any} */ (value), new_value);
started = true;
}
if (previous_task) {
previous_task.abort();
previous_task = null;
}
const elapsed = now - start;
if (elapsed > /** @type {number} */ (duration)) {
store.set((value = new_value));
return false;
}
// @ts-ignore
store.set((value = fn(easing(elapsed / duration))));
return true;
});
return task.promise;
}
return {
set,
update: (fn, opts) =>
set(fn(/** @type {any} */ (target_value), /** @type {any} */ (value)), opts),
subscribe: store.subscribe
};
}
/**
* A wrapper for a value that tweens smoothly to its target value. Changes to `tween.target` will cause `tween.current` to
* move towards it over time, taking account of the `delay`, `duration` and `easing` options.
*
* ```svelte
* <script>
* import { Tween } from 'svelte/motion';
*
* const tween = new Tween(0);
* </script>
*
* <input type="range" bind:value={tween.target} />
* <input type="range" bind:value={tween.current} disabled />
* ```
* @template T
* @since 5.8.0
*/
export class Tween {
#current;
#target;
/** @type {TweenedOptions<T>} */
#defaults;
/** @type {import('../internal/client/types').Task | null} */
#task = null;
/**
* @param {T} value
* @param {TweenedOptions<T>} options
*/
constructor(value, options = {}) {
this.#current = source(value);
this.#target = source(value);
this.#defaults = options;
if (DEV) {
tag(this.#current, 'Tween.current');
tag(this.#target, 'Tween.target');
}
}
/**
* Create a tween whose value is bound to the return value of `fn`. This must be called
* inside an effect root (for example, during component initialisation).
*
* ```svelte
* <script>
* import { Tween } from 'svelte/motion';
*
* let { number } = $props();
*
* const tween = Tween.of(() => number);
* </script>
* ```
* @template U
* @param {() => U} fn
* @param {TweenedOptions<U>} [options]
*/
static of(fn, options) {
const tween = new Tween(fn(), options);
render_effect(() => {
tween.set(fn());
});
return tween;
}
/**
* Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it.
*
* If `options` are provided, they will override the tween's defaults.
* @param {T} value
* @param {TweenedOptions<T>} [options]
* @returns
*/
set(value, options) {
set(this.#target, value);
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = { ...this.#defaults, ...options };
if (duration === 0) {
this.#task?.abort();
set(this.#current, value);
return Promise.resolve();
}
const start = raf.now() + delay;
/** @type {(t: number) => T} */
let fn;
let started = false;
let previous_task = this.#task;
this.#task = loop((now) => {
if (now < start) {
return true;
}
if (!started) {
started = true;
const prev = this.#current.v;
fn = interpolate(prev, value);
if (typeof duration === 'function') {
duration = duration(prev, value);
}
previous_task?.abort();
}
const elapsed = now - start;
if (elapsed > /** @type {number} */ (duration)) {
set(this.#current, value);
return false;
}
set(this.#current, fn(easing(elapsed / /** @type {number} */ (duration))));
return true;
});
return this.#task.promise;
}
get current() {
return get(this.#current);
}
get target() {
return get(this.#target);
}
set target(v) {
this.set(v);
}
}Tool Call: index.js Status: Completed
/*
Adapted from https://github.com/mattdesl
Distributed under MIT License https://github.com/mattdesl/eases/blob/master/LICENSE.md
*/
/**
* @param {number} t
* @returns {number}
*/
export function linear(t) {
return t;
}
/**
* @param {number} t
* @returns {number}
*/
export function backInOut(t) {
const s = 1.70158 * 1.525;
if ((t *= 2) < 1) return 0.5 * (t * t * ((s + 1) * t - s));
return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2);
}
/**
* @param {number} t
* @returns {number}
*/
export function backIn(t) {
const s = 1.70158;
return t * t * ((s + 1) * t - s);
}
/**
* @param {number} t
* @returns {number}
*/
export function backOut(t) {
const s = 1.70158;
return --t * t * ((s + 1) * t + s) + 1;
}
/**
* @param {number} t
* @returns {number}
*/
export function bounceOut(t) {
const a = 4.0 / 11.0;
const b = 8.0 / 11.0;
const c = 9.0 / 10.0;
const ca = 4356.0 / 361.0;
const cb = 35442.0 / 1805.0;
const cc = 16061.0 / 1805.0;
const t2 = t * t;
return t < a
? 7.5625 * t2
: t < b
? 9.075 * t2 - 9.9 * t + 3.4
: t < c
? ca * t2 - cb * t + cc
: 10.8 * t * t - 20.52 * t + 10.72;
}
/**
* @param {number} t
* @returns {number}
*/
export function bounceInOut(t) {
return t < 0.5 ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5;
}
/**
* @param {number} t
* @returns {number}
*/
export function bounceIn(t) {
return 1.0 - bounceOut(1.0 - t);
}
/**
* @param {number} t
* @returns {number}
*/
export function circInOut(t) {
if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - t * t) - 1);
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
}
/**
* @param {number} t
* @returns {number}
*/
export function circIn(t) {
return 1.0 - Math.sqrt(1.0 - t * t);
}
/**
* @param {number} t
* @returns {number}
*/
export function circOut(t) {
return Math.sqrt(1 - --t * t);
}
/**
* @param {number} t
* @returns {number}
*/
export function cubicInOut(t) {
return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function cubicIn(t) {
return t * t * t;
}
/**
* @param {number} t
* @returns {number}
*/
export function cubicOut(t) {
const f = t - 1.0;
return f * f * f + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function elasticInOut(t) {
return t < 0.5
? 0.5 * Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) * Math.pow(2.0, 10.0 * (2.0 * t - 1.0))
: 0.5 *
Math.sin(((-13.0 * Math.PI) / 2) * (2.0 * t - 1.0 + 1.0)) *
Math.pow(2.0, -10.0 * (2.0 * t - 1.0)) +
1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function elasticIn(t) {
return Math.sin((13.0 * t * Math.PI) / 2) * Math.pow(2.0, 10.0 * (t - 1.0));
}
/**
* @param {number} t
* @returns {number}
*/
export function elasticOut(t) {
return Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * Math.pow(2.0, -10.0 * t) + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function expoInOut(t) {
return t === 0.0 || t === 1.0
? t
: t < 0.5
? +0.5 * Math.pow(2.0, 20.0 * t - 10.0)
: -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function expoIn(t) {
return t === 0.0 ? t : Math.pow(2.0, 10.0 * (t - 1.0));
}
/**
* @param {number} t
* @returns {number}
*/
export function expoOut(t) {
return t === 1.0 ? t : 1.0 - Math.pow(2.0, -10.0 * t);
}
/**
* @param {number} t
* @returns {number}
*/
export function quadInOut(t) {
t /= 0.5;
if (t < 1) return 0.5 * t * t;
t--;
return -0.5 * (t * (t - 2) - 1);
}
/**
* @param {number} t
* @returns {number}
*/
export function quadIn(t) {
return t * t;
}
/**
* @param {number} t
* @returns {number}
*/
export function quadOut(t) {
return -t * (t - 2.0);
}
/**
* @param {number} t
* @returns {number}
*/
export function quartInOut(t) {
return t < 0.5 ? +8.0 * Math.pow(t, 4.0) : -8.0 * Math.pow(t - 1.0, 4.0) + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function quartIn(t) {
return Math.pow(t, 4.0);
}
/**
* @param {number} t
* @returns {number}
*/
export function quartOut(t) {
return Math.pow(t - 1.0, 3.0) * (1.0 - t) + 1.0;
}
/**
* @param {number} t
* @returns {number}
*/
export function quintInOut(t) {
if ((t *= 2) < 1) return 0.5 * t * t * t * t * t;
return 0.5 * ((t -= 2) * t * t * t * t + 2);
}
/**
* @param {number} t
* @returns {number}
*/
export function quintIn(t) {
return t * t * t * t * t;
}
/**
* @param {number} t
* @returns {number}
*/
export function quintOut(t) {
return --t * t * t * t * t + 1;
}
/**
* @param {number} t
* @returns {number}
*/
export function sineInOut(t) {
return -0.5 * (Math.cos(Math.PI * t) - 1);
}
/**
* @param {number} t
* @returns {number}
*/
export function sineIn(t) {
const v = Math.cos(t * Math.PI * 0.5);
if (Math.abs(v) < 1e-14) return 1;
else return 1 - v;
}
/**
* @param {number} t
* @returns {number}
*/
export function sineOut(t) {
return Math.sin((t * Math.PI) / 2);
}Let me also search for any runtime functions that handle animations:
Tool Call: Search files for regex (create_animation|run_animation|animate_element)
Status: Completed
No matches found
Tool Call: Search files for regex (transition|animate).*function
Status: Completed
No matches found
Tool Call: 1 match Status: Completed
file:///Users/faraaz/oss/svelte/packages/svelte/src/internal/client/dom/elements/transitions.js
Tool Call: transitions.js Status: Completed
/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, TransitionFn, TransitionManager } from '#client' */
import { noop, is_function } from '../../../shared/utils.js';
import { effect } from '../../reactivity/effects.js';
import {
active_effect,
active_reaction,
set_active_effect,
set_active_reaction,
untrack
} from '../../runtime.js';
import { loop } from '../../loop.js';
import { should_intro } from '../../render.js';
import { current_each_item } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '#client/constants';
import { queue_micro_task } from '../task.js';
import { without_reactive_context } from './bindings/shared.js';
/**
* @param {Element} element
* @param {'introstart' | 'introend' | 'outrostart' | 'outroend'} type
* @returns {void}
*/
function dispatch_event(element, type) {
without_reactive_context(() => {
element.dispatchEvent(new CustomEvent(type));
});
}
/**
* Converts a property to the camel-case format expected by Element.animate(), KeyframeEffect(), and KeyframeEffect.setKeyframes().
* @param {string} style
* @returns {string}
*/
function css_property_to_camelcase(style) {
// in compliance with spec
if (style === 'float') return 'cssFloat';
if (style === 'offset') return 'cssOffset';
// do not rename custom @properties
if (style.startsWith('--')) return style;
const parts = style.split('-');
if (parts.length === 1) return parts[0];
return (
parts[0] +
parts
.slice(1)
.map(/** @param {any} word */ (word) => word[0].toUpperCase() + word.slice(1))
.join('')
);
}
/**
* @param {string} css
* @returns {Keyframe}
*/
function css_to_keyframe(css) {
/** @type {Keyframe} */
const keyframe = {};
const parts = css.split(';');
for (const part of parts) {
const [property, value] = part.split(':');
if (!property || value === undefined) break;
const formatted_property = css_property_to_camelcase(property.trim());
keyframe[formatted_property] = value.trim();
}
return keyframe;
}
/** @param {number} t */
const linear = (t) => t;
/**
* Called inside keyed `{#each ...}` blocks (as `$.animation(...)`). This creates an animation manager
* and attaches it to the block, so that moves can be animated following reconciliation.
* @template P
* @param {Element} element
* @param {() => AnimateFn<P | undefined>} get_fn
* @param {(() => P) | null} get_params
*/
export function animation(element, get_fn, get_params) {
var item = /** @type {EachItem} */ (current_each_item);
/** @type {DOMRect} */
var from;
/** @type {DOMRect} */
var to;
/** @type {Animation | undefined} */
var animation;
/** @type {null | { position: string, width: string, height: string, transform: string }} */
var original_styles = null;
item.a ??= {
element,
measure() {
from = this.element.getBoundingClientRect();
},
apply() {
animation?.abort();
to = this.element.getBoundingClientRect();
if (
from.left !== to.left ||
from.right !== to.right ||
from.top !== to.top ||
from.bottom !== to.bottom
) {
const options = get_fn()(this.element, { from, to }, get_params?.());
animation = animate(this.element, options, undefined, 1, () => {
animation?.abort();
animation = undefined;
});
}
},
fix() {
// If an animation is already running, transforming the element is likely to fail,
// because the styles applied by the animation take precedence. In the case of crossfade,
// that means the `translate(...)` of the crossfade transition overrules the `translate(...)`
// we would apply below, leading to the element jumping somewhere to the top left.
if (element.getAnimations().length) return;
// It's important to destructure these to get fixed values - the object itself has getters,
// and changing the style to 'absolute' can for example influence the width.
var { position, width, height } = getComputedStyle(element);
if (position !== 'absolute' && position !== 'fixed') {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
original_styles = {
position: style.position,
width: style.width,
height: style.height,
transform: style.transform
};
style.position = 'absolute';
style.width = width;
style.height = height;
var to = element.getBoundingClientRect();
if (from.left !== to.left || from.top !== to.top) {
var transform = `translate(${from.left - to.left}px, ${from.top - to.top}px)`;
style.transform = style.transform ? `${style.transform} ${transform}` : transform;
}
}
},
unfix() {
if (original_styles) {
var style = /** @type {HTMLElement | SVGElement} */ (element).style;
style.position = original_styles.position;
style.width = original_styles.width;
style.height = original_styles.height;
style.transform = original_styles.transform;
}
}
};
// in the case of a `<svelte:element>`, it's possible for `$.animation(...)` to be called
// when an animation manager already exists, if the tag changes. in that case, we need to
// swap out the element rather than creating a new manager, in case it happened at the same
// moment as a reconciliation
item.a.element = element;
}
/**
* Called inside block effects as `$.transition(...)`. This creates a transition manager and
* attaches it to the current effect — later, inside `pause_effect` and `resume_effect`, we
* use this to create `intro` and `outro` transitions.
* @template P
* @param {number} flags
* @param {HTMLElement} element
* @param {() => TransitionFn<P | undefined>} get_fn
* @param {(() => P) | null} get_params
* @returns {void}
*/
export function transition(flags, element, get_fn, get_params) {
var is_intro = (flags & TRANSITION_IN) !== 0;
var is_outro = (flags & TRANSITION_OUT) !== 0;
var is_both = is_intro && is_outro;
var is_global = (flags & TRANSITION_GLOBAL) !== 0;
/** @type {'in' | 'out' | 'both'} */
var direction = is_both ? 'both' : is_intro ? 'in' : 'out';
/** @type {AnimationConfig | ((opts: { direction: 'in' | 'out' }) => AnimationConfig) | undefined} */
var current_options;
var inert = element.inert;
/**
* The default overflow style, stashed so we can revert changes during the transition
* that are necessary to work around a Safari <18 bug
* TODO 6.0 remove this, if older versions of Safari have died out enough
*/
var overflow = element.style.overflow;
/** @type {Animation | undefined} */
var intro;
/** @type {Animation | undefined} */
var outro;
function get_options() {
var previous_reaction = active_reaction;
var previous_effect = active_effect;
set_active_reaction(null);
set_active_effect(null);
try {
// If a transition is still ongoing, we use the existing options rather than generating
// new ones. This ensures that reversible transitions reverse smoothly, rather than
// jumping to a new spot because (for example) a different `duration` was used
return (current_options ??= get_fn()(element, get_params?.() ?? /** @type {P} */ ({}), {
direction
}));
} finally {
set_active_reaction(previous_reaction);
set_active_effect(previous_effect);
}
}
/** @type {TransitionManager} */
var transition = {
is_global,
in() {
element.inert = inert;
if (!is_intro) {
outro?.abort();
outro?.reset?.();
return;
}
if (!is_outro) {
// if we intro then outro then intro again, we want to abort the first intro,
// if it's not a bidirectional transition
intro?.abort();
}
dispatch_event(element, 'introstart');
intro = animate(element, get_options(), outro, 1, () => {
dispatch_event(element, 'introend');
// Ensure we cancel the animation to prevent leaking
intro?.abort();
intro = current_options = undefined;
element.style.overflow = overflow;
});
},
out(fn) {
if (!is_outro) {
fn?.();
current_options = undefined;
return;
}
element.inert = true;
dispatch_event(element, 'outrostart');
outro = animate(element, get_options(), intro, 0, () => {
dispatch_event(element, 'outroend');
fn?.();
});
},
stop: () => {
intro?.abort();
outro?.abort();
}
};
var e = /** @type {Effect} */ (active_effect);
(e.transitions ??= []).push(transition);
// if this is a local transition, we only want to run it if the parent (branch) effect's
// parent (block) effect is where the state change happened. we can determine that by
// looking at whether the block effect is currently initializing
if (is_intro && should_intro) {
var run = is_global;
if (!run) {
var block = /** @type {Effect | null} */ (e.parent);
// skip over transparent blocks (e.g. snippets, else-if blocks)
while (block && (block.f & EFFECT_TRANSPARENT) !== 0) {
while ((block = block.parent)) {
if ((block.f & BLOCK_EFFECT) !== 0) break;
}
}
run = !block || (block.f & EFFECT_RAN) !== 0;
}
if (run) {
effect(() => {
untrack(() => transition.in());
});
}
}
}
/**
* Animates an element, according to the provided configuration
* @param {Element} element
* @param {AnimationConfig | ((opts: { direction: 'in' | 'out' }) => AnimationConfig)} options
* @param {Animation | undefined} counterpart The corresponding intro/outro to this outro/intro
* @param {number} t2 The target `t` value — `1` for intro, `0` for outro
* @param {(() => void)} on_finish Called after successfully completing the animation
* @returns {Animation}
*/
function animate(element, options, counterpart, t2, on_finish) {
var is_intro = t2 === 1;
if (is_function(options)) {
// In the case of a deferred transition (such as `crossfade`), `option` will be
// a function rather than an `AnimationConfig`. We need to call this function
// once the DOM has been updated...
/** @type {Animation} */
var a;
var aborted = false;
queue_micro_task(() => {
if (aborted) return;
var o = options({ direction: is_intro ? 'in' : 'out' });
a = animate(element, o, counterpart, t2, on_finish);
});
// ...but we want to do so without using `async`/`await` everywhere, so
// we return a facade that allows everything to remain synchronous
return {
abort: () => {
aborted = true;
a?.abort();
},
deactivate: () => a.deactivate(),
reset: () => a.reset(),
t: () => a.t()
};
}
counterpart?.deactivate();
if (!options?.duration) {
on_finish();
return {
abort: noop,
deactivate: noop,
reset: noop,
t: () => t2
};
}
const { delay = 0, css, tick, easing = linear } = options;
var keyframes = [];
if (is_intro && counterpart === undefined) {
if (tick) {
tick(0, 1); // TODO put in nested effect, to avoid interleaved reads/writes?
}
if (css) {
var styles = css_to_keyframe(css(0, 1));
keyframes.push(styles, styles);
}
}
var get_t = () => 1 - t2;
// create a dummy animation that lasts as long as the delay (but with whatever devtools
// multiplier is in effect). in the common case that it is `0`, we keep it anyway so that
// the CSS keyframes aren't created until the DOM is updated
//
// fill forwards to prevent the element from rendering without styles applied
// see https://github.com/sveltejs/svelte/issues/14732
var animation = element.animate(keyframes, { duration: delay, fill: 'forwards' });
animation.onfinish = () => {
// remove dummy animation from the stack to prevent conflict with main animation
animation.cancel();
// for bidirectional transitions, we start from the current position,
// rather than doing a full intro/outro
var t1 = counterpart?.t() ?? 1 - t2;
counterpart?.abort();
var delta = t2 - t1;
var duration = /** @type {number} */ (options.duration) * Math.abs(delta);
var keyframes = [];
if (duration > 0) {
/**
* Whether or not the CSS includes `overflow: hidden`, in which case we need to
* add it as an inline style to work around a Safari <18 bug
* TODO 6.0 remove this, if possible
*/
var needs_overflow_hidden = false;
if (css) {
var n = Math.ceil(duration / (1000 / 60)); // `n` must be an integer, or we risk missing the `t2` value
for (var i = 0; i <= n; i += 1) {
var t = t1 + delta * easing(i / n);
var styles = css_to_keyframe(css(t, 1 - t));
keyframes.push(styles);
needs_overflow_hidden ||= styles.overflow === 'hidden';
}
}
if (needs_overflow_hidden) {
/** @type {HTMLElement} */ (element).style.overflow = 'hidden';
}
get_t = () => {
var time = /** @type {number} */ (
/** @type {globalThis.Animation} */ (animation).currentTime
);
return t1 + delta * easing(time / duration);
};
if (tick) {
loop(() => {
if (animation.playState !== 'running') return false;
var t = get_t();
tick(t, 1 - t);
return true;
});
}
}
animation = element.animate(keyframes, { duration, fill: 'forwards' });
animation.onfinish = () => {
get_t = () => t2;
tick?.(t2, 1 - t2);
on_finish();
};
};
return {
abort: () => {
if (animation) {
animation.cancel();
// This prevents memory leaks in Chromium
animation.effect = null;
// This prevents onfinish to be launched after cancel(),
// which can happen in some rare cases
// see https://github.com/sveltejs/svelte/issues/13681
animation.onfinish = noop;
}
},
deactivate: () => {
on_finish = noop;
},
reset: () => {
if (t2 === 0) {
tick?.(1, 0);
}
},
t: () => get_t()
};
}Based on my exploration of the Svelte codebase, I can provide you with a comprehensive overview of the functions that implement animations and transitions in Svelte:
These are built-in transition functions that can be used with transition:, in:, and out: directives:
export function blur(
node,
{ delay = 0, duration = 400, easing = cubic_in_out, amount = 5, opacity = 0 } = {}
) {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const f = style.filter === 'none' ? '' : style.filter;
const od = target_opacity * (1 - opacity);
const [value, unit] = split_css_unit(amount);
return {
delay,
duration,
easing,
css: (_t, u) => `opacity: ${target_opacity - od * u}; filter: ${f} blur(${u * value}${unit});`
};
}
export function fade(node, { delay = 0, duration = 400, easing = linear } = {}) {
const o = +getComputedStyle(node).opacity;
return {
delay,
duration,
easing,
css: (t) => `opacity: ${t * o}`
};
}
export function fly(
node,
{ delay = 0, duration = 400, easing = cubic_out, x = 0, y = 0, opacity = 0 } = {}
) {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
const od = target_opacity * (1 - opacity);
const [x_value, x_unit] = split_css_unit(x);
const [y_value, y_unit] = split_css_unit(y);
return {
delay,
duration,
easing,
css: (t, u) => `
transform: ${transform} translate(${(1 - t) * x_value}${x_unit}, ${(1 - t) * y_value}${y_unit});
opacity: ${target_opacity - od * u}`
};
}
export function slide(node, { delay = 0, duration = 400, easing = cubic_out, axis = 'y' } = {}) {
const style = getComputedStyle(node);
// ... implementation details for sliding animation
const opacity = +style.opacity;
const primary_property = axis === 'y' ? 'height' : 'width';
// ... calculates dimensions and margins/padding
return {
delay,
duration,
easing,
css: (t) =>
'overflow: hidden;' +
`opacity: ${Math.min(t * 20, 1) * opacity};` +
`${primary_property}: ${t * primary_property_value}px;`
// ... additional CSS properties
};
}
export function scale(
node,
{ delay = 0, duration = 400, easing = cubic_out, start = 0, opacity = 0 } = {}
) {
const style = getComputedStyle(node);
const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
const sd = 1 - start;
const od = target_opacity * (1 - opacity);
return {
delay,
duration,
easing,
css: (_t, u) => `
transform: ${transform} scale(${1 - sd * u});
opacity: ${target_opacity - od * u}
`
};
}
export function draw(node, { delay = 0, speed, duration, easing = cubic_in_out } = {}) {
let len = node.getTotalLength();
const style = getComputedStyle(node);
if (style.strokeLinecap !== 'butt') {
len += parseInt(style.strokeWidth);
}
// ... duration calculation logic
return {
delay,
duration,
easing,
css: (_, u) => `
stroke-dasharray: ${len};
stroke-dashoffset: ${u * len};
`
};
}
export function crossfade({ fallback, ...defaults }) {
// Creates a pair of transitions for crossfading elements
const to_receive = new Map();
const to_send = new Map();
// Returns [send, receive] functions for crossfade transitions
}
For the animate: directive used in keyed {#each} blocks:
export function flip(node, { from, to }, params = {}) {
var { delay = 0, duration = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
var style = getComputedStyle(node);
// FLIP animation implementation - calculates transform between positions
var transform = style.transform === 'none' ? '' : style.transform;
// ... position and scale calculations
return {
delay,
duration: typeof duration === 'function' ? duration(Math.sqrt(dx * dx + dy * dy)) : duration,
easing,
css: (t, u) => {
var x = u * dx;
var y = u * dy;
var sx = t + u * dsx;
var sy = t + u * dsy;
return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx}, ${sy});`;
}
};
}
export function spring(value, opts = {}) {
// Deprecated function - creates spring-based animated store
const store = writable(value);
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
// ... spring physics implementation
}
export class Spring {
// New Spring class for spring-based animations
// Handles spring physics with stiffness, damping, and precision
}
export function tweened(value, defaults = {}) {
// Deprecated function - creates tweened animated store
const store = writable(value);
// ... interpolation and easing implementation
}
export class Tween {
// New Tween class for smooth interpolated animations
}
A collection of easing functions used by animations and transitions:
export function linear(t) {
return t;
}
And many more including: backIn, backOut, backInOut, bounceIn, bounceOut, bounceInOut, circIn, circOut, circInOut, cubicIn, cubicOut, cubicInOut, elasticIn, elasticOut, elasticInOut, expoIn, expoOut, expoInOut, quadIn, quadOut, quadInOut, quartIn, quartOut, quartInOut, quintIn, quintOut, quintInOut, sineIn, sineOut, sineInOut.
These are internal functions that handle the actual execution of animations:
export function animation(element, get_fn, get_params) {
// Manages FLIP animations for keyed each blocks
var item = current_each_item;
// ... animation manager implementation
}
export function transition(flags, element, get_fn, get_params) {
// Manages intro/outro transitions
var is_intro = (flags & TRANSITION_IN) !== 0;
var is_outro = (flags & TRANSITION_OUT) !== 0;
// ... transition manager implementation
}
function animate(element, options, counterpart, t2, on_finish) {
// Core animation function that handles Web Animations API
// Manages keyframes, timing, and animation lifecycle
}
The animation and transition system in Svelte is organized into several layers:
- High-level transition functions (
blur,fade,fly,slide,scale,draw,crossfade) for common UI transitions - Animation functions (
flip) for list reordering animations - Motion classes/functions (
Spring,Tween,spring,tweened) for value-based animations - Easing functions for controlling animation timing curves
- Runtime functions that manage the actual execution using the Web Animations API
These functions work together to provide Svelte's declarative animation system, allowing developers to easily add smooth animations and transitions to their applications.