Skip to content

Commit d0d38f0

Browse files
authored
Merge pull request #45 from code-hike/change-vertical-center
Better centering logic
2 parents 0942082 + 6c84da7 commit d0d38f0

File tree

5 files changed

+346
-170
lines changed

5 files changed

+346
-170
lines changed

packages/smooth-lines/src/index.tsx

+163-122
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Line,
44
useLineTransitions,
55
} from "./line-transitions"
6+
import { Lines } from "./lines"
67
import { easing, tween } from "./tween"
78

89
export { 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-
2727
function 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+
}

packages/smooth-lines/src/line-transitions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type LineData = {
2424
exitIndex: number | null
2525
}
2626

27-
type LineTransition = {
27+
export type LineTransition = {
2828
element: React.ReactNode
2929
elementWithProgress?: (progress: number) => Element
3030
key: number

0 commit comments

Comments
 (0)