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,65 +35,14 @@ function SmoothLines({
3535 prevFocus,
3636 nextFocus,
3737 center,
38+ minZoom = 0 , // TODO use minZoom
3839 maxZoom = 1.2 ,
3940} : Props ) {
4041 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- )
7742
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 )
8745 : 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
9746
9847 const prevFocusKeys = prevFocus . map (
9948 index => prevLines [ index ] ?. key
@@ -102,78 +51,170 @@ function SmoothLines({
10251 index => nextLines [ index ] ?. key
10352 )
10453
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+ } ) {
105164 return (
106165 < div
107166 style = { {
108- width : containerWidth ,
109- height : containerHeight ,
110- // background: "salmon",
167+ width,
168+ height,
111169 position : "relative" ,
112- // outline: "1px solid pink",
113170 } }
114171 >
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 }
177198 </ div >
178199 )
179200}
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