atagen

malice

public
Star0
Watch0
48af827771081b0e3b68719cf046485aaab65304
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/motion.tsTypescript67 lines2.3 KB
1
import { cubicOut } from 'svelte/easing';
2
import { fade, slide, type TransitionConfig } from 'svelte/transition';
3
4
const entering = new Set<string>();
5
const leaving = new Set<string>();
6
let busyUntil = 0;
7
8
export function motionDuration(ms: number): number {
9
  return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : ms;
10
}
11
12
export function motionActive(): boolean {
13
  return performance.now() < busyUntil;
14
}
15
16
function noteBusy(ms: number): void {
17
  busyUntil = Math.max(busyUntil, performance.now() + ms + 60);
18
}
19
20
export function setRowMotion(enter: Iterable<string>, leave: Iterable<string>): void {
21
  entering.clear();
22
  leaving.clear();
23
  for (const key of enter) entering.add(key);
24
  for (const key of leave) leaving.add(key);
25
}
26
27
function rowKey(node: Element): string {
28
  const { item, row } = (node as HTMLElement).dataset;
29
  return item ?? row ?? '';
30
}
31
32
function reveal(node: Element, ms: number, axis: 'x' | 'y' = 'y'): TransitionConfig {
33
  const duration = motionDuration(ms);
34
  if (duration === 0) return { duration: 0 };
35
  noteBusy(duration);
36
  const base = slide(node, { duration, easing: cubicOut, axis });
37
  return { duration, easing: cubicOut, css: (t, u) => `${base.css?.(t, u) ?? ''};opacity: ${t};` };
38
}
39
40
export function rowIn(node: Element): TransitionConfig {
41
  return entering.has(rowKey(node)) ? reveal(node, 200) : { duration: 0 };
42
}
43
44
export function rowOut(node: Element): TransitionConfig {
45
  return leaving.has(rowKey(node)) ? reveal(node, 160) : { duration: 0 };
46
}
47
48
export function expand(node: Element, params: { axis?: 'x' | 'y' } = {}): TransitionConfig {
49
  return reveal(node, 170, params.axis);
50
}
51
52
export function dissolve(node: Element): TransitionConfig {
53
  const duration = motionDuration(150);
54
  if (duration > 0) noteBusy(duration);
55
  return fade(node, { duration });
56
}
57
58
export function settleHeight(node: HTMLElement, from: number): void {
59
  const duration = motionDuration(180);
60
  const to = node.offsetHeight;
61
  if (duration === 0 || Math.abs(to - from) < 2 || node.getAnimations({ subtree: true }).length > 0) return;
62
  noteBusy(duration);
63
  node.animate(
64
    [{ height: `${from}px`, overflow: 'hidden' }, { height: `${to}px`, overflow: 'hidden' }],
65
    { duration, easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' }
66
  );
67
}