-
Notifications
You must be signed in to change notification settings - Fork 622
/
PresentationControls.tsx
81 lines (78 loc) 路 2.82 KB
/
PresentationControls.tsx
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
import * as React from 'react'
import { MathUtils } from 'three'
import { useThree } from '@react-three/fiber'
import { a, useSpring } from '@react-spring/three'
import { useGesture } from '@use-gesture/react'
import { SpringConfig } from '@react-spring/core'
export type PresentationControlProps = {
snap?: Boolean | SpringConfig
global?: boolean
cursor?: boolean
speed?: number
zoom?: number
rotation?: [number, number, number]
polar?: [number, number]
azimuth?: [number, number]
config?: any
enabled?: boolean
children?: React.ReactNode
}
export function PresentationControls({
enabled = true,
snap,
global,
cursor = true,
children,
speed = 1,
rotation = [0, 0, 0],
zoom = 1,
polar = [0, Math.PI / 2],
azimuth = [-Infinity, Infinity],
config = { mass: 1, tension: 170, friction: 26 },
}: PresentationControlProps) {
const { size, gl } = useThree()
const rPolar = React.useMemo(
() => [rotation[0] + polar[0], rotation[0] + polar[1]],
[rotation[0], polar[0], polar[1]]
) as [number, number]
const rAzimuth = React.useMemo(
() => [rotation[1] + azimuth[0], rotation[1] + azimuth[1]],
[rotation[1], azimuth[0], azimuth[1]]
) as [number, number]
const rInitial = React.useMemo(
() => [MathUtils.clamp(rotation[0], ...rPolar), MathUtils.clamp(rotation[1], ...rAzimuth), rotation[2]],
[rotation[0], rotation[1], rotation[2], rPolar, rAzimuth]
)
const [spring, api] = useSpring(() => ({ scale: 1, rotation: rInitial, config }))
React.useEffect(() => void api.start({ scale: 1, rotation: rInitial, config }), [rInitial])
React.useEffect(() => {
if (global && cursor && enabled) gl.domElement.style.cursor = 'grab'
return () => void (gl.domElement.style.cursor = 'default')
}, [global, cursor, gl.domElement, enabled])
const bind = useGesture(
{
onHover: ({ last }) => {
if (cursor && !global && enabled) gl.domElement.style.cursor = last ? 'auto' : 'grab'
},
onDrag: ({ down, delta: [x, y], memo: [oldY, oldX] = spring.rotation.animation.to || rInitial }) => {
if (!enabled) return [y, x]
if (cursor) gl.domElement.style.cursor = down ? 'grabbing' : 'grab'
x = MathUtils.clamp(oldX + (x / size.width) * Math.PI * speed, ...rAzimuth)
y = MathUtils.clamp(oldY + (y / size.height) * Math.PI * speed, ...rPolar)
const sConfig = snap && !down && typeof snap !== 'boolean' ? snap : config
api.start({
scale: down && y > rPolar[1] / 2 ? zoom : 1,
rotation: snap && !down ? rInitial : [y, x, 0],
config: (n) => (n === 'scale' ? { ...sConfig, friction: sConfig.friction * 3 } : sConfig),
})
return [y, x]
},
},
{ target: global ? gl.domElement : undefined }
)
return (
<a.group {...bind?.()} {...(spring as any)}>
{children}
</a.group>
)
}