-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathuseResizeCanvas.ts
More file actions
196 lines (185 loc) · 6.36 KB
/
useResizeCanvas.ts
File metadata and controls
196 lines (185 loc) · 6.36 KB
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import { useEffect, useState, MutableRefObject, useCallback } from 'react';
import { Bounds } from '@rive-app/canvas';
import { Dimensions, UseRiveOptions } from '../types';
import useDevicePixelRatio from './useDevicePixelRatio';
import useContainerSize from './useContainerSize';
import { getOptions } from '../utils';
interface UseResizeCanvasProps {
/**
* Whether or not Rive is loaded and renderer is associated with the canvas
*/
riveLoaded: boolean;
/**
* Ref to the canvas element
*/
canvasElem: HTMLCanvasElement | null;
/**
* Ref to the container element of the canvas
*/
containerRef: MutableRefObject<HTMLElement | null>;
/**
* (Optional) Callback to be invoked after the canvas has been resized due to a resize
* of its parent container. This is where you would want to reset the layout
* dimensions for the Rive renderer to dictate the new min/max bounds of the
* canvas.
*
* Using the high-level JS runtime, this might be a simple call to `rive.resizeToCanvas()`
* Using the low-level JSruntime, this might be invoking the renderer's `.align()` method
* with the Layout and min/max X/Y values of the canvas.
*
* @returns void
*/
onCanvasHasResized?: () => void;
/**
* (Optional) Options passed to the useRive hook, including the shouldResizeCanvasToContainer option
* which prevents the canvas element from resizing to its parent container
*/
options?: Partial<UseRiveOptions>;
/**
* (Optional) AABB bounds of the artboard. If provided, the canvas will be sized to the artboard
* height if the fitCanvasToArtboardHeight option is true.
*/
artboardBounds?: Bounds;
}
/**
* Helper hook to listen for changes in the <canvas> parent container size and size the <canvas>
* to match. If a resize event has occurred, a supplied callback (onCanvasHasResized)
* will be inokved to allow for any re-calculation needed (i.e. Rive layout on the canvas).
*
* This hook is useful if you are not intending to use the `useRive` hook yourself, but still
* want to use the auto-sizing logic on the canvas/container.
*
* @param props - Object to supply necessary props to the hook
*/
export default function useResizeCanvas({
riveLoaded = false,
canvasElem,
containerRef,
options = {},
onCanvasHasResized,
artboardBounds,
}: UseResizeCanvasProps) {
const presetOptions = getOptions(options);
const [
{ height: lastContainerHeight, width: lastContainerWidth },
setLastContainerDimensions,
] = useState<Dimensions>({
height: 0,
width: 0,
});
const [
{ height: lastCanvasHeight, width: lastCanvasWidth },
setLastCanvasSize,
] = useState<Dimensions>({
height: 0,
width: 0,
});
const [isFirstSizing, setIsFirstSizing] = useState(true);
const {
fitCanvasToArtboardHeight,
shouldResizeCanvasToContainer,
useDevicePixelRatio: shouldUseDevicePixelRatio,
customDevicePixelRatio,
} = presetOptions;
const containerSize = useContainerSize(
containerRef,
shouldResizeCanvasToContainer
);
const currentDevicePixelRatio = useDevicePixelRatio(customDevicePixelRatio);
const { maxX, maxY } = artboardBounds ?? {};
const getContainerDimensions = useCallback(() => {
const boundingBox = containerRef.current?.getBoundingClientRect();
const width = Math.ceil(boundingBox?.width ?? 0);
const height = Math.ceil(boundingBox?.height ?? 0);
if (fitCanvasToArtboardHeight && artboardBounds) {
const { maxY, maxX } = artboardBounds;
return { width, height: width * (maxY / maxX) };
}
return {
width,
height,
};
}, [containerRef, fitCanvasToArtboardHeight, maxX, maxY]);
useEffect(() => {
// If Rive is not ready, the container is not ready, or the user supplies a flag
// to not resize the canvas to the container, then return early
if (
!shouldResizeCanvasToContainer ||
!containerRef.current ||
!riveLoaded
) {
return;
}
const { width, height } = getContainerDimensions();
let hasResized = false;
if (canvasElem) {
// Check if the canvas parent container bounds have changed and set
// new values accordingly
const boundsChanged =
width !== lastContainerWidth || height !== lastContainerHeight;
if (presetOptions.fitCanvasToArtboardHeight && boundsChanged) {
containerRef.current.style.height = height + 'px';
hasResized = true;
}
if (presetOptions.useDevicePixelRatio) {
// Check if devicePixelRatio may have changed and get new canvas
// width/height values to set the size
const canvasSizeChanged =
width * currentDevicePixelRatio !== lastCanvasWidth ||
height * currentDevicePixelRatio !== lastCanvasHeight;
if (boundsChanged || canvasSizeChanged) {
const newCanvasWidthProp = currentDevicePixelRatio * width;
const newCanvasHeightProp = currentDevicePixelRatio * height;
canvasElem.width = newCanvasWidthProp;
canvasElem.height = newCanvasHeightProp;
canvasElem.style.width = width + 'px';
canvasElem.style.height = height + 'px';
setLastCanvasSize({
width: newCanvasWidthProp,
height: newCanvasHeightProp,
});
hasResized = true;
}
} else if (boundsChanged) {
canvasElem.width = width;
canvasElem.height = height;
setLastCanvasSize({
width: width,
height: height,
});
hasResized = true;
}
setLastContainerDimensions({ width, height });
}
// Callback to perform any Rive-related actions after resizing the canvas
// (i.e., reset the Rive layout in the render loop)
if (onCanvasHasResized && (isFirstSizing || hasResized)) {
onCanvasHasResized && onCanvasHasResized();
}
isFirstSizing && setIsFirstSizing(false);
}, [
canvasElem,
containerRef,
containerSize,
currentDevicePixelRatio,
getContainerDimensions,
isFirstSizing,
setIsFirstSizing,
lastCanvasHeight,
lastCanvasWidth,
lastContainerHeight,
lastContainerWidth,
onCanvasHasResized,
shouldResizeCanvasToContainer,
fitCanvasToArtboardHeight,
shouldUseDevicePixelRatio,
riveLoaded,
]);
// Reset width and height values when the canvas changes
useEffect(() => {
setLastCanvasSize({
width: 0,
height: 0,
});
}, [canvasElem]);
}