Skip to content

Commit 1d042cd

Browse files
committed
feat: implement enhanced section scroll snapping with improved navigation and smooth transitions
1 parent 78970c1 commit 1d042cd

3 files changed

Lines changed: 117 additions & 63 deletions

File tree

src/components/SectionNavigation.tsx

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,17 @@ interface SectionNavigationProps {
77
}
88

99
export function SectionNavigation({ sections }: SectionNavigationProps) {
10-
const [activeSection, setActiveSection] = useState(0);
11-
const { locomotiveScroll } = useSmoothScroll();
10+
const {
11+
locomotiveScroll,
12+
currentSection,
13+
totalSections,
14+
scrollToSection
15+
} = useSmoothScroll();
16+
17+
// Use the context's currentSection instead of local state
18+
const activeSection = currentSection;
1219

13-
useEffect(() => {
14-
if (!locomotiveScroll) return;
15-
16-
const handleScroll = () => {
17-
const scrollY = locomotiveScroll.scroll.y;
18-
const windowHeight = window.innerHeight;
19-
const currentSection = Math.round(scrollY / windowHeight);
20-
setActiveSection(Math.max(0, Math.min(currentSection, sections.length - 1)));
21-
};
22-
23-
locomotiveScroll.on('scroll', handleScroll);
24-
return () => {
25-
locomotiveScroll.off('scroll', handleScroll);
26-
};
27-
}, [locomotiveScroll, sections.length]);
28-
29-
const scrollToSection = (index: number) => {
30-
if (locomotiveScroll) {
31-
const targetY = index * window.innerHeight;
32-
locomotiveScroll.scrollTo(targetY, {
33-
duration: 1.2,
34-
easing: [0.25, 0.1, 0.25, 1],
35-
});
36-
}
37-
};
20+
// Use the context's scrollToSection function for better section snapping
3821

3922
return (
4023
<>

src/components/SmoothScrollContext.tsx

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { createContext, useContext, useEffect, useRef, ReactNode } from 'react';
1+
import { createContext, useContext, useEffect, useRef, ReactNode, useState } from 'react';
22
import LocomotiveScroll from 'locomotive-scroll';
33
import 'locomotive-scroll/dist/locomotive-scroll.css';
44

55
interface SmoothScrollContextType {
66
locomotiveScroll: LocomotiveScroll | null;
7+
currentSection: number;
8+
totalSections: number;
9+
scrollToSection: (index: number) => void;
10+
scrollToNextSection: () => void;
11+
scrollToPrevSection: () => void;
712
}
813

914
const SmoothScrollContext = createContext<SmoothScrollContextType>({
@@ -18,96 +23,138 @@ interface SmoothScrollProviderProps {
1823

1924
export function SmoothScrollProvider({ children }: SmoothScrollProviderProps) {
2025
const locomotiveScrollRef = useRef<LocomotiveScroll | null>(null);
26+
const [currentSection, setCurrentSection] = useState(0);
27+
const [totalSections, setTotalSections] = useState(0);
28+
29+
2130

2231
useEffect(() => {
2332
// Initialize Locomotive Scroll
2433
locomotiveScrollRef.current = new LocomotiveScroll({
2534
el: document.querySelector('[data-scroll-container]') as HTMLElement,
2635
smooth: true,
27-
lerp: 0.1,
36+
lerp: 0.08, // Slightly faster for better responsiveness
2837
multiplier: 0.8,
29-
// Enable snap scrolling to sections
38+
// Enhanced snap scrolling
3039
scrollFromAnywhere: true,
31-
// Snap to sections
3240
snap: true,
33-
// Snap duration
34-
snapDuration: 1.2,
35-
// Snap easing
41+
snapDuration: 1.5, // Slightly longer for smoother transitions
3642
snapEasing: [0.25, 0.1, 0.25, 1],
43+
// Better section detection
44+
offset: ['0%', '0%'],
3745
smartphone: {
3846
smooth: true,
39-
lerp: 0.1,
47+
lerp: 0.08,
4048
multiplier: 0.8,
4149
snap: true,
42-
snapDuration: 1.2,
50+
snapDuration: 1.5,
4351
},
4452
tablet: {
4553
smooth: true,
46-
lerp: 0.1,
54+
lerp: 0.08,
4755
multiplier: 0.8,
4856
snap: true,
49-
snapDuration: 1.2,
57+
snapDuration: 1.5,
5058
},
5159
});
5260

53-
// Add keyboard navigation
61+
// Get all sections for better navigation
62+
const sections = document.querySelectorAll('[data-scroll-section]');
63+
setTotalSections(sections.length);
64+
65+
// Enhanced keyboard navigation with section snapping
5466
const handleKeyDown = (e: KeyboardEvent) => {
5567
if (!locomotiveScrollRef.current) return;
5668

57-
const windowHeight = window.innerHeight;
58-
const currentScrollY = locomotiveScrollRef.current.scroll.y;
59-
const currentSection = Math.round(currentScrollY / windowHeight);
60-
6169
switch (e.key) {
6270
case 'ArrowDown':
6371
case 'PageDown':
6472
case ' ':
6573
e.preventDefault();
66-
const nextSection = Math.min(currentSection + 1, Math.floor(document.body.scrollHeight / windowHeight) - 1);
67-
locomotiveScrollRef.current.scrollTo(nextSection * windowHeight, {
68-
duration: 1.2,
69-
easing: [0.25, 0.1, 0.25, 1],
70-
});
74+
scrollToNextSection();
7175
break;
7276
case 'ArrowUp':
7377
case 'PageUp':
7478
e.preventDefault();
75-
const prevSection = Math.max(currentSection - 1, 0);
76-
locomotiveScrollRef.current.scrollTo(prevSection * windowHeight, {
77-
duration: 1.2,
78-
easing: [0.25, 0.1, 0.25, 1],
79-
});
79+
scrollToPrevSection();
8080
break;
8181
case 'Home':
8282
e.preventDefault();
83-
locomotiveScrollRef.current.scrollTo(0, {
84-
duration: 1.2,
85-
easing: [0.25, 0.1, 0.25, 1],
86-
});
83+
scrollToSection(0);
8784
break;
8885
case 'End':
8986
e.preventDefault();
90-
locomotiveScrollRef.current.scrollTo(document.body.scrollHeight - windowHeight, {
91-
duration: 1.2,
92-
easing: [0.25, 0.1, 0.25, 1],
93-
});
87+
scrollToSection(totalSections - 1);
9488
break;
9589
}
9690
};
9791

92+
// Section navigation functions
93+
const scrollToSection = (index: number) => {
94+
if (!locomotiveScrollRef.current || index < 0 || index >= totalSections) return;
95+
96+
const sections = document.querySelectorAll('[data-scroll-section]');
97+
const targetSection = sections[index] as HTMLElement;
98+
99+
if (targetSection) {
100+
locomotiveScrollRef.current.scrollTo(targetSection, {
101+
duration: 1.5,
102+
easing: [0.25, 0.1, 0.25, 1],
103+
});
104+
setCurrentSection(index);
105+
}
106+
};
107+
108+
const scrollToNextSection = () => {
109+
const nextSection = Math.min(currentSection + 1, totalSections - 1);
110+
scrollToSection(nextSection);
111+
};
112+
113+
const scrollToPrevSection = () => {
114+
const prevSection = Math.max(currentSection - 1, 0);
115+
scrollToSection(prevSection);
116+
};
117+
118+
// Track current section on scroll
119+
const handleScroll = () => {
120+
if (!locomotiveScrollRef.current) return;
121+
122+
const scrollY = locomotiveScrollRef.current.scroll.y;
123+
const windowHeight = window.innerHeight;
124+
125+
// Find which section is currently in view
126+
sections.forEach((section, index) => {
127+
const rect = section.getBoundingClientRect();
128+
if (rect.top <= windowHeight / 2 && rect.bottom >= windowHeight / 2) {
129+
setCurrentSection(index);
130+
}
131+
});
132+
};
133+
134+
// Add scroll event listener
135+
locomotiveScrollRef.current.on('scroll', handleScroll);
136+
98137
document.addEventListener('keydown', handleKeyDown);
99138

100139
// Cleanup
101140
return () => {
102141
if (locomotiveScrollRef.current) {
142+
locomotiveScrollRef.current.off('scroll', handleScroll);
103143
locomotiveScrollRef.current.destroy();
104144
}
105145
document.removeEventListener('keydown', handleKeyDown);
106146
};
107-
}, []);
147+
}, [currentSection, totalSections]);
108148

109149
return (
110-
<SmoothScrollContext.Provider value={{ locomotiveScroll: locomotiveScrollRef.current }}>
150+
<SmoothScrollContext.Provider value={{
151+
locomotiveScroll: locomotiveScrollRef.current,
152+
currentSection,
153+
totalSections,
154+
scrollToSection,
155+
scrollToNextSection,
156+
scrollToPrevSection
157+
}}>
111158
{children}
112159
</SmoothScrollContext.Provider>
113160
);

src/styles/globals.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,30 @@ html.has-scroll-smooth {
295295
scroll-behavior: smooth;
296296
}
297297

298+
/* Section snapping enhancements */
299+
[data-scroll-section] {
300+
min-height: 100vh;
301+
scroll-snap-align: start;
302+
scroll-snap-stop: always;
303+
}
304+
305+
/* Ensure smooth transitions between sections */
306+
[data-scroll-container] {
307+
scroll-snap-type: y mandatory;
308+
scroll-behavior: smooth;
309+
}
310+
311+
/* Enhanced section transitions */
312+
[data-scroll-section] {
313+
transition: transform 0.3s ease-out;
314+
}
315+
316+
/* Prevent content jumping during snap */
317+
[data-scroll-section] * {
318+
backface-visibility: hidden;
319+
transform-style: preserve-3d;
320+
}
321+
298322
/* Custom scrollbar for smooth scrolling */
299323
.c-scrollbar {
300324
position: fixed;

0 commit comments

Comments
 (0)