Skip to content

Commit 2806b66

Browse files
committed
refactor: remove react-spring for custom useSpring
1 parent ca082d1 commit 2806b66

File tree

9 files changed

+2371
-2166
lines changed

9 files changed

+2371
-2166
lines changed

docs/src/pages/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ const Home = props => {
8080
</h3>
8181
<p className="mt-2 lg:mt-4 text-base xl:text-lg lg:leading-normal leading-6 text-gray-600">
8282
Time is short for front-end developers as it is, so having
83-
a charting system that is declarative, succinct and
84-
requires as little imperative scripting as possible not
85-
only helps you keep moving forward but lets you express
86-
your data visualization needs at the speed of your
87-
creativity.
83+
a charting system that is great out of the box,
84+
declarative, succinct and requires as little imperative
85+
scripting as possible not only helps you keep moving
86+
forward but lets you express your data visualization needs
87+
at the speed of your creativity.
8888
</p>
8989
</div>
9090
</div>

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@
124124
"dependencies": {
125125
"@babel/runtime": "^7.14.6",
126126
"@reach/observe-rect": "^1.2.0",
127-
"@react-spring/web": "^9.2.3",
128127
"@types/d3-array": "^3.0.1",
129128
"@types/d3-scale": "^4.0.1",
130129
"@types/d3-shape": "^3.0.1",

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export default inputSrcs
6666
visualizer({
6767
filename: 'stats-react.json',
6868
json: true,
69+
gzipSize: true,
6970
}),
7071
],
7172
},

src/components/Cursors.tsx

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import React from 'react'
22
import ReactDOM from 'react-dom'
33
import * as TSTB from 'ts-toolbelt'
44

5-
import { animated, config, useSpring } from '@react-spring/web'
6-
75
import usePrevious from '../hooks/usePrevious'
86
import useLatestWhen from '../hooks/useLatestWhen'
97
import usePortalElement from '../hooks/usePortalElement'
@@ -12,6 +10,7 @@ import { translate } from '../utils/Utils'
1210
//
1311
import useChartContext from '../utils/chartContext'
1412
import useRect from '../hooks/useRect'
13+
import { useSpring } from '../hooks/useSpring'
1514

1615
type ResolvedCursorOptions = TSTB.Object.Required<
1716
CursorOptions,
@@ -204,19 +203,52 @@ function Cursor<TDatum>(props: {
204203
const immediatePos = !axis.isVertical ? lineStartX : lineStartY
205204
const immediate = usePrevious(immediatePos) === -1 && immediatePos > -1
206205

207-
const lineSpring = useSpring({
208-
transform: translate(lineStartX, lineStartY),
209-
width: `${lineWidth}px`,
210-
height: `${lineHeight}px`,
211-
config: config.stiff,
212-
immediate: key => (key === 'transform' ? immediate : false),
213-
})
206+
const lineRef = React.useRef<HTMLDivElement | null>(null)
207+
const bubbleRef = React.useRef<HTMLDivElement | null>(null)
208+
209+
const lineXSpring = useSpring(
210+
lineStartX,
211+
[1, 210, 20],
212+
() => {
213+
if (lineRef.current) {
214+
lineRef.current.style.transform = `translate(${lineXSpring.x()}px, ${lineYSpring.x()}px)`
215+
}
216+
},
217+
immediate
218+
)
219+
220+
const lineYSpring = useSpring(
221+
lineStartY,
222+
[1, 210, 20],
223+
() => {
224+
if (lineRef.current) {
225+
lineRef.current.style.transform = `translate(${lineXSpring.x()}px, ${lineYSpring.x()}px)`
226+
}
227+
},
228+
immediate
229+
)
214230

215-
const bubbleSpring = useSpring({
216-
transform: translate(bubbleX, bubbleY),
217-
config: config.stiff,
218-
immediate: key => (key === 'transform' ? immediate : false),
219-
})
231+
const bubbleXSpring = useSpring(
232+
bubbleX,
233+
[1, 210, 20],
234+
() => {
235+
if (bubbleRef.current) {
236+
bubbleRef.current.style.transform = `translate(${bubbleXSpring.x()}px, ${bubbleYSpring.x()}px)`
237+
}
238+
},
239+
immediate
240+
)
241+
242+
const bubbleYSpring = useSpring(
243+
bubbleY,
244+
[1, 210, 20],
245+
() => {
246+
if (bubbleRef.current) {
247+
bubbleRef.current.style.transform = `translate(${bubbleXSpring.x()}px, ${bubbleYSpring.x()}px)`
248+
}
249+
},
250+
immediate
251+
)
220252

221253
const portalEl = usePortalElement()
222254

@@ -240,9 +272,11 @@ function Cursor<TDatum>(props: {
240272
>
241273
{/* Render the cursor line */}
242274
{props.options.showLine ? (
243-
<animated.div
275+
<div
276+
ref={lineRef}
244277
style={{
245-
...lineSpring,
278+
width: `${lineWidth}px`,
279+
height: `${lineHeight}px`,
246280
position: 'absolute',
247281
top: 0,
248282
left: 0,
@@ -252,9 +286,9 @@ function Cursor<TDatum>(props: {
252286
) : null}
253287
{/* Render the cursor bubble */}
254288
{props.options.showLabel ? (
255-
<animated.div
289+
<div
290+
ref={bubbleRef}
256291
style={{
257-
...bubbleSpring,
258292
position: 'absolute',
259293
top: 0,
260294
left: 0,
@@ -275,7 +309,7 @@ function Cursor<TDatum>(props: {
275309
>
276310
{formattedValue}
277311
</div>
278-
</animated.div>
312+
</div>
279313
) : null}
280314
</div>,
281315
portalEl

src/components/Tooltip.tsx

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from 'react'
22
import ReactDOM from 'react-dom'
33

4-
import { useSpring, animated } from '@react-spring/web'
5-
64
import { useAnchor } from '../hooks/useAnchor'
75
import useLatestWhen from '../hooks/useLatestWhen'
86
import usePortalElement from '../hooks/usePortalElement'
@@ -12,6 +10,7 @@ import { Datum, ResolvedTooltipOptions, TooltipOptions } from '../types'
1210
import useChartContext from '../utils/chartContext'
1311
import TooltipRenderer from './TooltipRenderer'
1412
import useRect from '../hooks/useRect'
13+
import { useSpring } from '../hooks/useSpring'
1514

1615
//
1716

@@ -120,28 +119,60 @@ export default function Tooltip<TDatum>(): React.ReactPortal | null {
120119

121120
const { visibility, ...anchorStyle } = latestStableAnchor.style
122121

123-
const springProps = useSpring({
124-
...anchorStyle,
125-
left: anchorStyle.left || 0,
126-
top: anchorStyle.top || 0,
127-
opacity: !!focusedDatum ? 1 : 0,
128-
config: { mass: 1, tension: 210, friction: 30 },
129-
immediate: key => {
130-
if (['left', 'top'].includes(key)) {
131-
return Number.isNaN(previousAnchor?.style.left)
122+
const tooltipRef = React.useRef<HTMLDivElement | null>(null)
123+
124+
const immediate = Number.isNaN(previousAnchor?.style.left)
125+
126+
const tooltipXSpring = useSpring(
127+
anchorStyle.left || 0,
128+
[1, 210, 30],
129+
() => {
130+
if (tooltipRef.current) {
131+
tooltipRef.current.style.transform = `translate(${tooltipXSpring.x()}px, ${tooltipYSpring.x()}px)`
132132
}
133+
},
134+
immediate
135+
)
133136

134-
return false
137+
const tooltipYSpring = useSpring(
138+
anchorStyle.top || 0,
139+
[1, 210, 30],
140+
() => {
141+
if (tooltipRef.current) {
142+
tooltipRef.current.style.transform = `translate(${tooltipXSpring.x()}px, ${tooltipYSpring.x()}px)`
143+
}
135144
},
136-
})
145+
immediate
146+
)
147+
148+
// const springProps = useSpring({
149+
// ...anchorStyle,
150+
// left: anchorStyle.left || 0,
151+
// top: anchorStyle.top || 0,
152+
// config: { mass: 1, tension: 210, friction: 30 },
153+
// immediate: key => {
154+
// if (['left', 'top'].includes(key)) {
155+
// return Number.isNaN(previousAnchor?.style.left)
156+
// }
157+
158+
// return false
159+
// },
160+
// })
137161

138162
const show = !!preTooltipOptions
139163

140164
const latestFit = useLatestWhen(anchor.fit, !!anchor.fit)
141165

142166
return show && portalEl
143167
? ReactDOM.createPortal(
144-
<animated.div style={springProps}>
168+
<div
169+
ref={tooltipRef}
170+
style={{
171+
position: anchorStyle.position,
172+
opacity: !!focusedDatum ? 1 : 0,
173+
transition: 'opacity .3s ease',
174+
}}
175+
>
145176
<div
146177
ref={el => setTooltipEl(el)}
147178
style={{
@@ -165,7 +196,7 @@ export default function Tooltip<TDatum>(): React.ReactPortal | null {
165196
anchor,
166197
})}
167198
</div>
168-
</animated.div>,
199+
</div>,
169200
portalEl
170201
)
171202
: null

src/hooks/useSpring.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react'
2+
import { Spring } from '../utils/spring'
3+
import useGetLatest from './useGetLatest'
4+
5+
export function useSpring(
6+
value: number,
7+
config: [number, number, number],
8+
cb: (x: number) => void,
9+
immediate?: boolean
10+
) {
11+
const springRef = React.useRef(new Spring(value, ...config))
12+
const getImmediate = useGetLatest(immediate)
13+
14+
const [startRaf, stopRaf] = useRaf(() => {
15+
cb(springRef.current.x())
16+
return springRef.current.done()
17+
})
18+
19+
React.useEffect(() => {
20+
if (springRef.current.endPosition !== value) {
21+
springRef.current.setEnd(value, getImmediate())
22+
startRaf()
23+
}
24+
}, [getImmediate, startRaf, value])
25+
26+
React.useEffect(() => {
27+
return () => {
28+
stopRaf()
29+
}
30+
}, [stopRaf])
31+
32+
return springRef.current
33+
}
34+
35+
export function useRaf(callback: () => any) {
36+
const raf = React.useRef<number | null>(null)
37+
const rafCallback = React.useRef(callback)
38+
rafCallback.current = callback
39+
const tick = React.useCallback(() => {
40+
if (rafCallback.current()) return
41+
raf.current = requestAnimationFrame(tick)
42+
}, [])
43+
44+
return [
45+
React.useMemo(() => tick, [tick]),
46+
React.useMemo(
47+
() => () => raf.current && cancelAnimationFrame(raf.current),
48+
[]
49+
),
50+
]
51+
}

0 commit comments

Comments
 (0)