/
use-child-aware-animation.ts
78 lines (67 loc) · 2.73 KB
/
use-child-aware-animation.ts
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
import { usePrevious } from 'hooks/use-previous';
import { MutableRefObject, useLayoutEffect, useState } from 'react';
// This isn't very reusable for different kinds of layouts, but easily tweakable nonetheless.
// This is an experiment anyway.
// Refer to src/components/FlipAnimationTechnique/hooks/use-animation.ts for comments common to both hooks
export const useChildAwareAnimation = (elementRef: MutableRefObject<null | HTMLDivElement>, animated: boolean) => {
const [parentClientRect, setParentClientRect] = useState(null as null | ClientRect);
const [childClientRect, setChildClientRect] = useState(null as null | ClientRect);
const { animated: prevAnimated } = usePrevious({ animated });
useLayoutEffect(() => {
if (animated === prevAnimated) return;
if (!elementRef || !elementRef.current) return;
const parentEl = elementRef.current;
const childEl = elementRef.current.querySelector('div');
if (!childEl) return;
// Get first and last client rects for both child and parent
const parentFirst = parentClientRect;
const parentLast = parentEl.getBoundingClientRect();
const childFirst = childClientRect;
const childLast = childEl.getBoundingClientRect();
setParentClientRect(parentEl.getBoundingClientRect());
setChildClientRect(childEl.getBoundingClientRect());
if (!parentFirst || !parentLast) return;
if (!childFirst || !childLast) return;
const parentDeltaX = parentFirst.left - parentLast.left;
const parentDeltaY = parentFirst.top - parentLast.top;
const parentDeltaW = parentFirst.width / parentLast.width;
const parentDeltaH = parentFirst.height / parentLast.height;
// Get scale offset for the parent, because we want the child to scale inversely to the parent
const childDeltaW = parentLast.width / parentFirst.width;
const childDeltaH = parentLast.height / parentFirst.height;
parentEl.animate(
[
{
transformOrigin: 'top left',
transform: `translate(${parentDeltaX}px, ${parentDeltaY}px) scale(${parentDeltaW}, ${parentDeltaH})`,
},
{
transformOrigin: 'top left',
transform: 'none',
},
],
{
duration: 250,
easing: 'ease-in-out',
fill: 'both',
}
);
// In this case, the child is centered, so just ensure the child scales correctly.
childEl.animate(
[
{
transformOrigin: 'center',
transform: `scale(${childDeltaW}, ${childDeltaH})`,
},
{
transformOrigin: 'center',
transform: 'none',
},
],
{
duration: 250,
easing: 'ease-in-out',
}
);
}, [elementRef, animated, prevAnimated, parentClientRect, childClientRect]);
};