aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/utils.mjs
blob: 46632ba73ecac04b1905e0cac42a3220244fc072 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
 * Do callback when HTMLElement in removed
 *
 * @param {HTMLElement} element observing
 * @param {Function} callback
 */
export const onRemove = (element, callback) => {
  const parent = element.parentNode;
  if (!parent) throw new Error("The node must already be attached");

  const obs = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      for (const el of mutation.removedNodes) {
        if (el === element) {
          obs.disconnect();
          callback();
        }
      }
    }
  });
  obs.observe(parent, { childList: true, });
}

/**
 * Animate transition of DOMRect, with Web Animation API
 *
 * @param {HTMLElement} element                 Element which animation applies
 * @param {DOMRect}     rect                    DOMRect for transition
 * @param {Object}      options
 * @param {Boolean}     options.resume          If true, transition starts from rect to DOMRect of element
 * @param {Number}      options.duration        Duration of animation in milliseconds
 * @returns {Animation} https://developer.mozilla.org/en-US/docs/Web/API/Animation
 */
export const animateRectTransition = (element, rect, options = {}) => {
  if (!element.parentElement) throw new Error("The node must already be attached");

  const { width: w1, height: h1, left: x1, top: y1 } = rect
  const { width: w2, height: h2, left: x2, top: y2 } = element.getBoundingClientRect()

  const rw = (w1 ?? w2) / w2
  const rh = (h1 ?? h2) / h2
  const dx = x1 - x2;
  const dy = y1 - y2;

  if (dx === 0 && dy === 0 || !isFinite(rw) || !isFinite(rh)) {
    return element.animate([], { duration: 0 })
  }

  const transform1 = `translate(0, 0) scale(1, 1)`;
  const transform2 = `translate(${dx}px, ${dy}px) scale(${rw}, ${rh})`;
  const keyframes = [
    { transform: transform1, opacity: 1 },
    { transform: transform2, opacity: 0.3 },
  ]
  if (options.resume === true) keyframes.reverse()

  return element.animate(
    keyframes,
    {
      duration: options.duration ?? 300,
      easing: 'ease-in-out',
    }
  );
}


/**
 * Throttle for function call
 *
 * @param {Function} func
 * @param {Number} delay milliseconds
 * @returns {Any} return value of function call, or null if throttled
 */
export function throttle(func, delay) {
  let timerFlag = null;

  return function(...args) {
    const context = this
    if (timerFlag !== null) return null

    timerFlag = setTimeout(
      () => timerFlag = null,
      typeof delay === 'function' ? delay.call(context) : delay
    );

    return func.call(context, ...args);
  };
}