this repo has no description
at main 255 lines 9.6 kB view raw
1import { cubicOut, cubicInOut, linear } from '../easing/index.js'; 2import { assign, split_css_unit, is_function } from '../internal/index.js'; 3 4/** 5 * Animates a `blur` filter alongside an element's opacity. 6 * 7 * https://svelte.dev/docs/svelte-transition#blur 8 * @param {Element} node 9 * @param {import('./public').BlurParams} [params] 10 * @returns {import('./public').TransitionConfig} 11 */ 12export function blur( 13 node, 14 { delay = 0, duration = 400, easing = cubicInOut, amount = 5, opacity = 0 } = {} 15) { 16 const style = getComputedStyle(node); 17 const target_opacity = +style.opacity; 18 const f = style.filter === 'none' ? '' : style.filter; 19 const od = target_opacity * (1 - opacity); 20 const [value, unit] = split_css_unit(amount); 21 return { 22 delay, 23 duration, 24 easing, 25 css: (_t, u) => `opacity: ${target_opacity - od * u}; filter: ${f} blur(${u * value}${unit});` 26 }; 27} 28 29/** 30 * 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. 31 * 32 * https://svelte.dev/docs/svelte-transition#fade 33 * @param {Element} node 34 * @param {import('./public').FadeParams} [params] 35 * @returns {import('./public').TransitionConfig} 36 */ 37export function fade(node, { delay = 0, duration = 400, easing = linear } = {}) { 38 const o = +getComputedStyle(node).opacity; 39 return { 40 delay, 41 duration, 42 easing, 43 css: (t) => `opacity: ${t * o}` 44 }; 45} 46 47/** 48 * 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. 49 * 50 * https://svelte.dev/docs/svelte-transition#fly 51 * @param {Element} node 52 * @param {import('./public').FlyParams} [params] 53 * @returns {import('./public').TransitionConfig} 54 */ 55export function fly( 56 node, 57 { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 } = {} 58) { 59 const style = getComputedStyle(node); 60 const target_opacity = +style.opacity; 61 const transform = style.transform === 'none' ? '' : style.transform; 62 const od = target_opacity * (1 - opacity); 63 const [xValue, xUnit] = split_css_unit(x); 64 const [yValue, yUnit] = split_css_unit(y); 65 return { 66 delay, 67 duration, 68 easing, 69 css: (t, u) => ` 70 transform: ${transform} translate(${(1 - t) * xValue}${xUnit}, ${(1 - t) * yValue}${yUnit}); 71 opacity: ${target_opacity - od * u}` 72 }; 73} 74 75/** 76 * Slides an element in and out. 77 * 78 * https://svelte.dev/docs/svelte-transition#slide 79 * @param {Element} node 80 * @param {import('./public').SlideParams} [params] 81 * @returns {import('./public').TransitionConfig} 82 */ 83export function slide(node, { delay = 0, duration = 400, easing = cubicOut, axis = 'y' } = {}) { 84 const style = getComputedStyle(node); 85 const opacity = +style.opacity; 86 const primary_property = axis === 'y' ? 'height' : 'width'; 87 const primary_property_value = parseFloat(style[primary_property]); 88 const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right']; 89 const capitalized_secondary_properties = secondary_properties.map( 90 (e) => `${e[0].toUpperCase()}${e.slice(1)}` 91 ); 92 const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`]); 93 const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`]); 94 const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`]); 95 const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]); 96 const border_width_start_value = parseFloat( 97 style[`border${capitalized_secondary_properties[0]}Width`] 98 ); 99 const border_width_end_value = parseFloat( 100 style[`border${capitalized_secondary_properties[1]}Width`] 101 ); 102 return { 103 delay, 104 duration, 105 easing, 106 css: (t) => 107 'overflow: hidden;' + 108 `opacity: ${Math.min(t * 20, 1) * opacity};` + 109 `${primary_property}: ${t * primary_property_value}px;` + 110 `padding-${secondary_properties[0]}: ${t * padding_start_value}px;` + 111 `padding-${secondary_properties[1]}: ${t * padding_end_value}px;` + 112 `margin-${secondary_properties[0]}: ${t * margin_start_value}px;` + 113 `margin-${secondary_properties[1]}: ${t * margin_end_value}px;` + 114 `border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` + 115 `border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;` 116 }; 117} 118 119/** 120 * Animates the opacity and scale of an element. `in` transitions animate from an element's current (default) values to the provided values, passed as parameters. `out` transitions animate from the provided values to an element's default values. 121 * 122 * https://svelte.dev/docs/svelte-transition#scale 123 * @param {Element} node 124 * @param {import('./public').ScaleParams} [params] 125 * @returns {import('./public').TransitionConfig} 126 */ 127export function scale( 128 node, 129 { delay = 0, duration = 400, easing = cubicOut, start = 0, opacity = 0 } = {} 130) { 131 const style = getComputedStyle(node); 132 const target_opacity = +style.opacity; 133 const transform = style.transform === 'none' ? '' : style.transform; 134 const sd = 1 - start; 135 const od = target_opacity * (1 - opacity); 136 return { 137 delay, 138 duration, 139 easing, 140 css: (_t, u) => ` 141 transform: ${transform} scale(${1 - sd * u}); 142 opacity: ${target_opacity - od * u} 143 ` 144 }; 145} 146 147/** 148 * 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>`. 149 * 150 * https://svelte.dev/docs/svelte-transition#draw 151 * @param {SVGElement & { getTotalLength(): number }} node 152 * @param {import('./public').DrawParams} [params] 153 * @returns {import('./public').TransitionConfig} 154 */ 155export function draw(node, { delay = 0, speed, duration, easing = cubicInOut } = {}) { 156 let len = node.getTotalLength(); 157 const style = getComputedStyle(node); 158 if (style.strokeLinecap !== 'butt') { 159 len += parseInt(style.strokeWidth); 160 } 161 if (duration === undefined) { 162 if (speed === undefined) { 163 duration = 800; 164 } else { 165 duration = len / speed; 166 } 167 } else if (typeof duration === 'function') { 168 duration = duration(len); 169 } 170 return { 171 delay, 172 duration, 173 easing, 174 css: (_, u) => ` 175 stroke-dasharray: ${len}; 176 stroke-dashoffset: ${u * len}; 177 ` 178 }; 179} 180 181/** 182 * The `crossfade` function creates a pair of [transitions](https://svelte.dev/docs#template-syntax-element-directives-transition-fn) 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. 183 * 184 * https://svelte.dev/docs/svelte-transition#crossfade 185 * @param {import('./public').CrossfadeParams & { 186 * fallback?: (node: Element, params: import('./public').CrossfadeParams, intro: boolean) => import('./public').TransitionConfig; 187 * }} params 188 * @returns {[(node: any, params: import('./public').CrossfadeParams & { key: any; }) => () => import('./public').TransitionConfig, (node: any, params: import('./public').CrossfadeParams & { key: any; }) => () => import('./public').TransitionConfig]} 189 */ 190export function crossfade({ fallback, ...defaults }) { 191 /** @type {Map<any, Element>} */ 192 const to_receive = new Map(); 193 /** @type {Map<any, Element>} */ 194 const to_send = new Map(); 195 /** 196 * @param {Element} from_node 197 * @param {Element} node 198 * @param {import('./public').CrossfadeParams} params 199 * @returns {import('./public').TransitionConfig} 200 */ 201 function crossfade(from_node, node, params) { 202 const { 203 delay = 0, 204 duration = (d) => Math.sqrt(d) * 30, 205 easing = cubicOut 206 } = assign(assign({}, defaults), params); 207 const from = from_node.getBoundingClientRect(); 208 const to = node.getBoundingClientRect(); 209 const dx = from.left - to.left; 210 const dy = from.top - to.top; 211 const dw = from.width / to.width; 212 const dh = from.height / to.height; 213 const d = Math.sqrt(dx * dx + dy * dy); 214 const style = getComputedStyle(node); 215 const transform = style.transform === 'none' ? '' : style.transform; 216 const opacity = +style.opacity; 217 return { 218 delay, 219 duration: is_function(duration) ? duration(d) : duration, 220 easing, 221 css: (t, u) => ` 222 opacity: ${t * opacity}; 223 transform-origin: top left; 224 transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${ 225 t + (1 - t) * dh 226 }); 227 ` 228 }; 229 } 230 231 /** 232 * @param {Map<any, Element>} items 233 * @param {Map<any, Element>} counterparts 234 * @param {boolean} intro 235 * @returns {(node: any, params: import('./public').CrossfadeParams & { key: any; }) => () => import('./public').TransitionConfig} 236 */ 237 function transition(items, counterparts, intro) { 238 return (node, params) => { 239 items.set(params.key, node); 240 return () => { 241 if (counterparts.has(params.key)) { 242 const other_node = counterparts.get(params.key); 243 counterparts.delete(params.key); 244 return crossfade(other_node, node, params); 245 } 246 // if the node is disappearing altogether 247 // (i.e. wasn't claimed by the other list) 248 // then we need to supply an outro 249 items.delete(params.key); 250 return fallback && fallback(node, params, intro); 251 }; 252 }; 253 } 254 return [transition(to_send, to_receive, false), transition(to_receive, to_send, true)]; 255}