33 Line ,
44 useLineTransitions ,
55} from "./line-transitions"
6+ import { Lines } from "./lines"
67import { easing , tween } from "./tween"
78
89export { SmoothLines }
@@ -19,11 +20,10 @@ type Props = {
1920 nextFocus : number [ ]
2021 overscroll ?: boolean
2122 center ?: boolean
23+ minZoom ?: number
2224 maxZoom ?: number
2325}
2426
25- const OFF_OPACITY = 0.33
26-
2727function SmoothLines ( {
2828 progress,
2929 containerHeight,
@@ -35,9 +35,22 @@ function SmoothLines({
3535 prevFocus,
3636 nextFocus,
3737 center,
38+ minZoom = 0 ,
3839 maxZoom = 1.2 ,
3940} : Props ) {
4041 const lines = useLineTransitions ( prevLines , nextLines )
42+
43+ const focusWidth = Array . isArray ( lineWidth )
44+ ? tween (
45+ {
46+ fixed : false ,
47+ interval : [ 0 , 1 ] ,
48+ extremes : lineWidth ,
49+ } ,
50+ progress
51+ )
52+ : lineWidth
53+
4154 const prevExtremes = [
4255 Math . min ( ...prevFocus ) ,
4356 Math . max ( ...prevFocus ) ,
@@ -46,53 +59,79 @@ function SmoothLines({
4659 Math . min ( ...nextFocus ) ,
4760 Math . max ( ...nextFocus ) ,
4861 ]
49- const prevCenter =
50- ( prevExtremes [ 0 ] + prevExtremes [ 1 ] + 1 ) / 2
51- const nextCenter =
52- ( nextExtremes [ 0 ] + nextExtremes [ 1 ] + 1 ) / 2
53- const yCenter =
62+ const prevFocusHeight =
63+ prevExtremes [ 1 ] - prevExtremes [ 0 ] + 3
64+ const nextFocusHeight =
65+ nextExtremes [ 1 ] - nextExtremes [ 0 ] + 3
66+ const focusHeight =
5467 tween (
5568 {
5669 fixed : false ,
57- // TODO use verticalInterval
5870 interval : [ 0 , 1 ] ,
59- extremes : [ prevCenter , nextCenter ] ,
71+ extremes : [ prevFocusHeight , nextFocusHeight ] ,
6072 ease : easing . easeInOutCubic ,
6173 } ,
6274 progress
6375 ) * lineHeight
6476
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-
78- const lw = Array . isArray ( lineWidth )
79- ? tween (
80- {
81- fixed : false ,
82- interval : [ 0 , 1 ] ,
83- extremes : lineWidth ,
84- } ,
85- progress
86- )
87- : lineWidth
8877 const zoom = Math . min (
89- containerWidth / lw ,
78+ containerWidth / focusWidth ,
9079 containerHeight / focusHeight ,
9180 maxZoom
9281 )
9382
83+ const contentHeight =
84+ tween (
85+ {
86+ fixed : false ,
87+ interval : [ 0 , 1 ] ,
88+ extremes : [ prevLines . length , nextLines . length ] ,
89+ ease : easing . easeInOutCubic ,
90+ } ,
91+ progress
92+ ) *
93+ lineHeight *
94+ zoom
95+ const focusStart =
96+ tween (
97+ {
98+ fixed : false ,
99+ interval : [ 0 , 1 ] ,
100+ extremes : [
101+ prevExtremes [ 0 ] - 1 ,
102+ nextExtremes [ 0 ] - 1 ,
103+ ] ,
104+ ease : easing . easeInOutCubic ,
105+ } ,
106+ progress
107+ ) *
108+ lineHeight *
109+ zoom
110+ const focusEnd =
111+ tween (
112+ {
113+ fixed : false ,
114+ interval : [ 0 , 1 ] ,
115+ extremes : [
116+ prevExtremes [ 1 ] + 2 ,
117+ nextExtremes [ 1 ] + 2 ,
118+ ] ,
119+ ease : easing . easeInOutCubic ,
120+ } ,
121+ progress
122+ ) *
123+ lineHeight *
124+ zoom
125+
126+ const dy = getDY (
127+ containerHeight ,
128+ contentHeight ,
129+ focusStart ,
130+ focusEnd
131+ )
132+
94133 const left = center
95- ? containerWidth / 2 - ( lw * zoom ) / 2
134+ ? containerWidth / 2 - ( focusWidth * zoom ) / 2
96135 : 0
97136
98137 const prevFocusKeys = prevFocus . map (
@@ -102,78 +141,89 @@ function SmoothLines({
102141 index => nextLines [ index ] ?. key
103142 )
104143
144+ return (
145+ < Container
146+ width = { containerWidth }
147+ height = { containerHeight }
148+ >
149+ < Content dx = { left } dy = { dy } scale = { zoom } >
150+ < Lines
151+ lines = { lines }
152+ prevFocusKeys = { prevFocusKeys }
153+ nextFocusKeys = { nextFocusKeys }
154+ focusWidth = { focusWidth }
155+ lineHeight = { lineHeight }
156+ progress = { progress }
157+ />
158+ </ Content >
159+ </ Container >
160+ )
161+ }
162+
163+ function getDY (
164+ containerHeight : number ,
165+ contentHeight : number ,
166+ focusStart : number ,
167+ focusEnd : number
168+ ) {
169+ if ( containerHeight > contentHeight ) {
170+ return ( containerHeight - contentHeight ) / 2
171+ }
172+ const focusCenter = ( focusEnd + focusStart ) / 2
173+ return clamp (
174+ containerHeight / 2 - focusCenter ,
175+ containerHeight - contentHeight ,
176+ 0
177+ )
178+ }
179+
180+ function Container ( {
181+ width,
182+ height,
183+ children,
184+ } : {
185+ width : number
186+ height : number
187+ children : React . ReactNode
188+ } ) {
105189 return (
106190 < div
107191 style = { {
108- width : containerWidth ,
109- height : containerHeight ,
110- // background: "salmon",
192+ width,
193+ height,
111194 position : "relative" ,
112- // outline: "1px solid pink",
113195 } }
114196 >
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
197+ { children }
198+ </ div >
199+ )
200+ }
154201
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 >
202+ function Content ( {
203+ dx,
204+ dy,
205+ scale,
206+ children,
207+ } : {
208+ dx : number
209+ dy : number
210+ scale : number
211+ children : React . ReactNode
212+ } ) {
213+ return (
214+ < div
215+ style = { {
216+ position : "absolute" ,
217+ top : 0 ,
218+ left : 0 ,
219+ transform : `translateX(${ dx } px) translateY(${ dy } px) scale(${ scale } )` ,
220+ } }
221+ >
222+ { children }
177223 </ div >
178224 )
179225}
226+
227+ function clamp ( num : number , min : number , max : number ) {
228+ return num <= min ? min : num >= max ? max : num
229+ }
0 commit comments