Skip to content

Instantly share code, notes, and snippets.

@followdeKlerk
Created April 29, 2024 07:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save followdeKlerk/8d5453910106b979964318c91166f9dd to your computer and use it in GitHub Desktop.
Save followdeKlerk/8d5453910106b979964318c91166f9dd to your computer and use it in GitHub Desktop.
React Smooth Section Navigator
<div id="root"></div>

React Smooth Section Navigator

An interactive React application that provides seamless navigation between sections of content using the wheel or touch gestures. It employs GSAP's (GreenSock Animation Platform) animations to ensure smooth transitions and ScrollTrigger behaviors, enhancing the overall user experience.

A Pen by Phillip Gimmi on CodePen.

License.

const App = () => {
React.useEffect(() => {
let sections = document.querySelectorAll(".section"),
images = document.querySelectorAll(".background"),
headings = document.querySelectorAll(".section-title"),
outerWrappers = document.querySelectorAll(".wrapper-outer"),
innerWrappers = document.querySelectorAll(".wrapper-inner"),
currentIndex = -1,
wrap = (index, max) => (index + max) % max,
animating;
gsap.set(outerWrappers, { yPercent: 100 });
gsap.set(innerWrappers, { yPercent: -100 });
function gotoSection(index, direction) {
index = wrap(index, sections.length);
animating = true;
let fromTop = direction === -1;
let dFactor = fromTop ? -1 : 1;
let tl = gsap.timeline({ defaults: { duration: 1.25, ease: "power1.inOut" }, onComplete: () => (animating = false) });
if (currentIndex >= 0) {
gsap.set(sections[currentIndex], { zIndex: 0 });
tl.to(images[currentIndex], { yPercent: -15 * dFactor })
.set(sections[currentIndex], { autoAlpha: 0 });
}
gsap.set(sections[index], { autoAlpha: 1, zIndex: 1 });
tl.fromTo([outerWrappers[index], innerWrappers[index]], { yPercent: (i) => (i ? -100 * dFactor : 100 * dFactor) }, { yPercent: 0 }, 0)
.fromTo(images[index], { yPercent: 15 * dFactor }, { yPercent: 0 }, 0)
.fromTo(headings[index], { autoAlpha: 0, yPercent: 150 * dFactor }, {
autoAlpha: 1,
yPercent: 0,
duration: 1,
ease: "power2",
stagger: { each: 0.02, from: "random" },
}, 0.2);
currentIndex = index;
}
function navigateSectionById(id) {
let index = Array.from(sections).findIndex(section => section.id === id);
if (index !== -1 && index !== currentIndex) {
gotoSection(index, index > currentIndex ? 1 : -1);
}
}
let lastTap = 0;
document.addEventListener("touchend", function (event) {
let currentTime = new Date().getTime();
let tapLength = currentTime - lastTap;
if (tapLength < 500 && tapLength > 0) {
gotoSection(currentIndex + 1, 1);
event.preventDefault();
}
lastTap = currentTime;
});
window.addEventListener("wheel", (event) => {
if (event.deltaY < 0 && !animating) {
gotoSection(currentIndex - 1, -1);
} else if (event.deltaY > 0 && !animating) {
gotoSection(currentIndex + 1, 1);
}
});
document.querySelectorAll("nav a").forEach(a => {
a.addEventListener("click", e => {
e.preventDefault();
navigateSectionById(e.currentTarget.getAttribute("href").slice(1));
});
});
gotoSection(0, 1);
}, []);
return (
<div className="app-container">
<header className="header">
<nav>
<a href="#first">One </a>
<a href="#second">Two </a>
<a href="#third">Three </a>
<a href="#fourth">Four </a>
<a href="#fifth">Five</a>
</nav>
</header>
<Section id="first" title="City Skyline" className="first" bgUrl="https://images.unsplash.com/photo-1605629713998-167cdc70afa2?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2OTM1NTR8&ixlib=rb-4.0.3&q=85" />
<Section id="second" title="Flowers of friendship" className="second" bgUrl="https://images.unsplash.com/photo-1503796964332-e25e282e390f?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2OTM1NTR8&ixlib=rb-4.0.3&q=85" />
<Section id="third" title="Waves in the Ocean" className="third" bgUrl="https://images.unsplash.com/photo-1518156677180-95a2893f3e9f?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2OTM1OTh8&ixlib=rb-4.0.3&q=85" />
<Section id="fourth" title="New York City" className="fourth" bgUrl="https://images.unsplash.com/photo-1584351583369-6baf055b51a7?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2OTM4MTB8&ixlib=rb-4.0.3&q=85" />
<Section id="fifth" title="dark side of the moon" className="fifth" bgUrl="https://images.unsplash.com/photo-1516663713099-37eb6d60c825?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2OTM4MTB8&ixlib=rb-4.0.3&q=85" />
</div>
);
};
const Section = ({ id, title, className, bgUrl }) => {
return (
<section id={id} className={`section ${className}`}>
<div className="wrapper-outer">
<div className="wrapper-inner">
<div className="background" style={{ backgroundImage: `url(${bgUrl})` }}>
<h2 className="section-title">{title}</h2>
</div>
</div>
</div>
</section>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&display=swap');
$bg-gradient: linear-gradient(
180deg,
rgba(0, 0, 0, 0.6) 0%,
rgba(0, 0, 0, 0.3) 100%
);
* {
box-sizing: border-box;
user-select: none;
}
a {
color: #fff;
text-decoration: none;
font-family: "Nunito Sans", sans-serif;
transition: color 0.3s, text-shadow 0.3s;
}
a:hover {
text-shadow: 0 0 10px currentColor;
}
a:nth-child(1):hover {
color: #e57373;
}
a:nth-child(2):hover {
color: #81c784;
}
a:nth-child(3):hover {
color: #64b5f6;
}
a:nth-child(4):hover {
color: #ffb74d;
}
a:nth-child(5):hover {
color: #ba68c8;
}
body {
margin: 0;
padding: 0;
height: 100vh;
color: white;
background: black;
font-family: "Inter", sans-serif;
text-transform: uppercase;
}
h2 {
font-size: clamp(1rem, 5vw, 5rem);
font-family: "Playfair Display", serif;
font-weight: 400;
text-align: center;
letter-spacing: 0.5em;
margin-right: -0.5em;
color: hsl(0, 0, 80%);
width: 90vw;
max-width: 1200px;
}
h2:hover {
transform: scale(1.05);
text-shadow: 0 0 10px hsl(0, 0, 80%);
}
header {
position: fixed;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 5%;
width: 100%;
z-index: 3;
height: 7em;
font-family: "Inter", sans-serif;
font-size: clamp(0.66rem, 2vw, 1rem);
letter-spacing: 0.5em;
}
.section {
height: 100%;
width: 100%;
top: 0;
position: fixed;
visibility: hidden;
.wrapper-outer,
.wrapper-inner {
width: 100%;
height: 100%;
overflow-y: hidden;
}
.background {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
height: 100%;
width: 100%;
top: 0;
background-size: cover;
background-position: center;
h2 {
z-index: 2;
}
.clip-text {
overflow: hidden;
}
}
}
.first .background {
background-image: $bg-gradient,
url("https://images.unsplash.com/photo-1695634930904-e20e04b7be24?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2ODg1Mzh8&ixlib=rb-4.0.3&q=85");
}
.second .background {
background-image: $bg-gradient,
url("https://images.unsplash.com/photo-1697369574152-58c8e59e35d4?crop=entropy&cs=srgb&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2OTg2ODg1OTJ8&ixlib=rb-4.0.3&q=85");
}
.third .background {
background-image: $bg-gradient,
url("https://images.unsplash.com/photo-1617438817509-70e91ad264a5?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYxNzU2MDk4Mg&ixlib=rb-1.2.1&q=75&w=1920");
}
.fourth .background {
background-image: $bg-gradient,
url("https://images.unsplash.com/photo-1617412327653-c29093585207?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYxNzU2MDgzMQ&ixlib=rb-1.2.1&q=75&w=1920");
}
.fifth .background {
background-image: $bg-gradient,
url("https://images.unsplash.com/photo-1617141636403-f511e2d5dc17?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYxODAzMjc4Mw&ixlib=rb-1.2.1&q=75w=1920");
background-position: 50% 45%;
}
h2 * {
will-change: transform;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment