3
3
Line ,
4
4
useLineTransitions ,
5
5
} from "./line-transitions"
6
+ import { Lines } from "./lines"
6
7
import { easing , tween } from "./tween"
7
8
8
9
export { SmoothLines }
@@ -19,11 +20,10 @@ type Props = {
19
20
nextFocus : number [ ]
20
21
overscroll ?: boolean
21
22
center ?: boolean
23
+ minZoom ?: number
22
24
maxZoom ?: number
23
25
}
24
26
25
- const OFF_OPACITY = 0.33
26
-
27
27
function SmoothLines ( {
28
28
progress,
29
29
containerHeight,
@@ -35,65 +35,14 @@ function SmoothLines({
35
35
prevFocus,
36
36
nextFocus,
37
37
center,
38
+ minZoom = 0 , // TODO use minZoom
38
39
maxZoom = 1.2 ,
39
40
} : Props ) {
40
41
const lines = useLineTransitions ( prevLines , nextLines )
41
- const prevExtremes = [
42
- Math . min ( ...prevFocus ) ,
43
- Math . max ( ...prevFocus ) ,
44
- ]
45
- const nextExtremes = [
46
- Math . min ( ...nextFocus ) ,
47
- Math . max ( ...nextFocus ) ,
48
- ]
49
- const prevCenter =
50
- ( prevExtremes [ 0 ] + prevExtremes [ 1 ] + 1 ) / 2
51
- const nextCenter =
52
- ( nextExtremes [ 0 ] + nextExtremes [ 1 ] + 1 ) / 2
53
- const yCenter =
54
- tween (
55
- {
56
- fixed : false ,
57
- // TODO use verticalInterval
58
- interval : [ 0 , 1 ] ,
59
- extremes : [ prevCenter , nextCenter ] ,
60
- ease : easing . easeInOutCubic ,
61
- } ,
62
- progress
63
- ) * lineHeight
64
-
65
- const prevFocusHeight =
66
- ( prevExtremes [ 1 ] - prevExtremes [ 0 ] + 3 ) * lineHeight
67
- const nextFocusHeight =
68
- ( nextExtremes [ 1 ] - nextExtremes [ 0 ] + 3 ) * lineHeight
69
- const focusHeight = tween (
70
- {
71
- fixed : false ,
72
- interval : [ 0 , 1 ] ,
73
- extremes : [ prevFocusHeight , nextFocusHeight ] ,
74
- } ,
75
- progress
76
- )
77
42
78
- const lw = Array . isArray ( lineWidth )
79
- ? tween (
80
- {
81
- fixed : false ,
82
- interval : [ 0 , 1 ] ,
83
- extremes : lineWidth ,
84
- } ,
85
- progress
86
- )
43
+ const focusWidth = Array . isArray ( lineWidth )
44
+ ? tweenProp ( lineWidth [ 0 ] , lineWidth [ 1 ] , progress )
87
45
: lineWidth
88
- const zoom = Math . min (
89
- containerWidth / lw ,
90
- containerHeight / focusHeight ,
91
- maxZoom
92
- )
93
-
94
- const left = center
95
- ? containerWidth / 2 - ( lw * zoom ) / 2
96
- : 0
97
46
98
47
const prevFocusKeys = prevFocus . map (
99
48
index => prevLines [ index ] ?. key
@@ -102,78 +51,170 @@ function SmoothLines({
102
51
index => nextLines [ index ] ?. key
103
52
)
104
53
54
+ const [ prevZoom , prevDX , prevDY ] = getContentProps ( {
55
+ containerWidth,
56
+ containerHeight,
57
+ lineWidth : Array . isArray ( lineWidth )
58
+ ? lineWidth [ 0 ]
59
+ : lineWidth ,
60
+ lineHeight,
61
+ maxZoom,
62
+ horizontalCenter : ! ! center ,
63
+ focusLineIndexList : prevFocus ,
64
+ originalContentHeight : prevLines . length * lineHeight ,
65
+ } )
66
+ const [ nextZoom , nextDX , nextDY ] = getContentProps ( {
67
+ containerWidth,
68
+ containerHeight,
69
+ lineWidth : Array . isArray ( lineWidth )
70
+ ? lineWidth [ 1 ]
71
+ : lineWidth ,
72
+ lineHeight,
73
+ maxZoom,
74
+ horizontalCenter : ! ! center ,
75
+ focusLineIndexList : nextFocus ,
76
+ originalContentHeight : nextLines . length * lineHeight ,
77
+ } )
78
+
79
+ const zoom = tweenProp ( prevZoom , nextZoom , progress )
80
+ const dx = tweenProp ( prevDX , nextDX , progress )
81
+ const dy = tweenProp ( prevDY , nextDY , progress )
82
+
83
+ return (
84
+ < Container
85
+ width = { containerWidth }
86
+ height = { containerHeight }
87
+ >
88
+ < Content dx = { dx } dy = { dy } scale = { zoom } >
89
+ < Lines
90
+ lines = { lines }
91
+ prevFocusKeys = { prevFocusKeys }
92
+ nextFocusKeys = { nextFocusKeys }
93
+ focusWidth = { focusWidth }
94
+ lineHeight = { lineHeight }
95
+ progress = { progress }
96
+ />
97
+ </ Content >
98
+ </ Container >
99
+ )
100
+ }
101
+
102
+ function getContentProps ( {
103
+ containerWidth,
104
+ containerHeight,
105
+ lineWidth,
106
+ lineHeight,
107
+ maxZoom,
108
+ focusLineIndexList,
109
+ originalContentHeight,
110
+ horizontalCenter,
111
+ } : {
112
+ containerWidth : number
113
+ containerHeight : number
114
+ lineWidth : number
115
+ lineHeight : number
116
+ maxZoom : number
117
+ focusLineIndexList : number [ ]
118
+ originalContentHeight : number
119
+ horizontalCenter : boolean
120
+ } ) {
121
+ const extremes = [
122
+ Math . min ( ...focusLineIndexList ) ,
123
+ Math . max ( ...focusLineIndexList ) ,
124
+ ]
125
+ const originalFocusHeight =
126
+ ( extremes [ 1 ] - extremes [ 0 ] + 3 ) * lineHeight
127
+ const zoom = Math . min (
128
+ containerWidth / lineWidth ,
129
+ containerHeight / originalFocusHeight ,
130
+ maxZoom
131
+ )
132
+
133
+ const contentHeight = originalContentHeight * zoom
134
+
135
+ const focusStart = ( extremes [ 0 ] - 1 ) * lineHeight * zoom
136
+ const focusEnd = ( extremes [ 1 ] + 2 ) * lineHeight * zoom
137
+ const focusCenter = ( focusEnd + focusStart ) / 2
138
+
139
+ const dy =
140
+ containerHeight > contentHeight
141
+ ? ( containerHeight - contentHeight ) / 2
142
+ : clamp (
143
+ containerHeight / 2 - focusCenter ,
144
+ containerHeight - contentHeight ,
145
+ 0
146
+ )
147
+
148
+ const dx = horizontalCenter
149
+ ? containerWidth / 2 - ( lineWidth * zoom ) / 2
150
+ : 0
151
+
152
+ return [ zoom , dx , dy ] as const
153
+ }
154
+
155
+ function Container ( {
156
+ width,
157
+ height,
158
+ children,
159
+ } : {
160
+ width : number
161
+ height : number
162
+ children : React . ReactNode
163
+ } ) {
105
164
return (
106
165
< div
107
166
style = { {
108
- width : containerWidth ,
109
- height : containerHeight ,
110
- // background: "salmon",
167
+ width,
168
+ height,
111
169
position : "relative" ,
112
- // outline: "1px solid pink",
113
170
} }
114
171
>
115
- < div
116
- style = { {
117
- position : "absolute" ,
118
- top : 0 ,
119
- left : 0 ,
120
- transform : `translateY(${
121
- containerHeight / 2 - yCenter * zoom
122
- } px) translateX(${ left } px) scale(${ zoom } )`,
123
- // outline: "5px solid green",
124
- } }
125
- >
126
- { lines . map (
127
- ( {
128
- element,
129
- key,
130
- tweenX,
131
- tweenY,
132
- elementWithProgress,
133
- } ) => {
134
- const dx = tween ( tweenX , progress )
135
- const dy = tween ( tweenY , progress )
136
-
137
- const opacity =
138
- tween (
139
- {
140
- fixed : false ,
141
- extremes : [
142
- prevFocusKeys . includes ( key )
143
- ? 0.99
144
- : OFF_OPACITY ,
145
- nextFocusKeys . includes ( key )
146
- ? 0.99
147
- : OFF_OPACITY ,
148
- ] ,
149
- interval : [ 0 , 1 ] ,
150
- } ,
151
- progress
152
- ) -
153
- Math . abs ( dx ) * 1
154
-
155
- return (
156
- < div
157
- key = { key }
158
- style = { {
159
- position : "absolute" ,
160
- top : 0 ,
161
- left : 0 ,
162
- transform : `translate(${ dx * lw } px, ${
163
- dy * lineHeight
164
- } px)`,
165
- opacity,
166
- width : lw ,
167
- } }
168
- >
169
- { elementWithProgress
170
- ? elementWithProgress ( progress )
171
- : element }
172
- </ div >
173
- )
174
- }
175
- ) }
176
- </ div >
172
+ { children }
173
+ </ div >
174
+ )
175
+ }
176
+
177
+ function Content ( {
178
+ dx,
179
+ dy,
180
+ scale,
181
+ children,
182
+ } : {
183
+ dx : number
184
+ dy : number
185
+ scale : number
186
+ children : React . ReactNode
187
+ } ) {
188
+ return (
189
+ < div
190
+ style = { {
191
+ position : "absolute" ,
192
+ top : 0 ,
193
+ left : 0 ,
194
+ transform : `translateX(${ dx } px) translateY(${ dy } px) scale(${ scale } )` ,
195
+ } }
196
+ >
197
+ { children }
177
198
</ div >
178
199
)
179
200
}
201
+
202
+ function tweenProp (
203
+ start : number ,
204
+ end : number ,
205
+ progress : number
206
+ ) {
207
+ return tween (
208
+ {
209
+ fixed : false ,
210
+ interval : [ 0 , 1 ] ,
211
+ extremes : [ start , end ] ,
212
+ ease : easing . easeInOutCubic ,
213
+ } ,
214
+ progress
215
+ )
216
+ }
217
+
218
+ function clamp ( num : number , min : number , max : number ) {
219
+ return num <= min ? min : num >= max ? max : num
220
+ }
0 commit comments