aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/utils.mjs
blob: d9ed2d725b237a8f74cdffd3ef0e01f9d3ed6678 (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
89
/**
 * 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 window.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 ?? 500,
    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)
  }
}