<div id="root"></div>
$parallax-offset: 30vh;
$content-offset: 40vh;
$transition-speed: 1.2s;
$slide-number: 3;
@mixin transition($time, $property: all, $easing: ease-in) {
transition: $property $time $easing !important;
}
*,
*::before,
*::after {
box-sizing: border-box;
text-rendering: optimizeLegibility;
}
html,
body {
font-family: "Montserrat", Arial, Helvetica, sans-serif;
font-size: 10px;
margin: 0;
padding: 0;
}
body {
font-size: 1.5rem;
}
.app {
justify-content: center;
display: flex;
}
.section {
@include transition($transition-speed, all, cubic-bezier(0.22, 0.44, 0, 1));
backface-visibility: hidden;
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
height: 100vh + $parallax-offset;
overflow: hidden;
position: fixed;
transform: translateY($parallax-offset);
width: 100%;
will-change: transform;
&::before {
background-color: rgba(0, 0, 0, 0.3);
bottom: 0;
content: "";
height: 100%;
left: 0;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
&:first-child {
background: url(https://images.unsplash.com/photo-1418065460487-3e41a6c84dc5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2200&q=80);
transform: translateY(-$parallax-offset / 2);
.parallax-wrapper {
transform: translateY($parallax-offset / 2);
}
}
&:nth-child(2) {
background: url(https://images.unsplash.com/photo-1526749837599-b4eba9fd855e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80);
}
&:last-child {
background: url(https://images.unsplash.com/photo-1465189684280-6a8fa9b19a7a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=80);
}
}
@for $i from 1 to ($slide-number + 1) {
.section:nth-child(#{$i}) {
z-index: ($slide-number + 1) - $i;
}
}
.parallax {
&-wrapper {
@include transition(
$transition-speed + 0.5,
all,
cubic-bezier(0.22, 0.44, 0, 1)
);
backface-visibility: hidden;
color: #fff;
display: flex;
flex-flow: column nowrap;
height: 100vh;
justify-content: center;
position: relative;
text-align: center;
text-transform: uppercase;
transform: translateY($content-offset);
will-change: transform;
}
}
.section.up-scroll {
transform: translate3d(0, -$parallax-offset / 2, 0);
.parallax-wrapper {
transform: translateY($parallax-offset / 2);
}
+ .section {
transform: translate3d(0, $parallax-offset, 0);
.parallax-wrapper {
transform: translateY($parallax-offset);
}
}
}
.section.down-scroll {
transform: translate3d(0, -(100vh + $parallax-offset), 0);
.parallax-wrapper {
transform: translateY($content-offset);
}
+ .section:not(.down-scroll) {
transform: translate3d(0, -$parallax-offset / 2, 0);
.parallax-wrapper {
transform: translateY($parallax-offset / 2);
}
}
}
View Compiled
const content = [
{
title: "Page One",
subtitle: "Scroll down ⬇"
},
{
title: "Page Two",
subtitle: "Hi I'm a full page parallax scroller"
},
{
title: "Page Three",
subtitle: "And you're beautiful"
}
];
const transitionDuration: number = 600;
const App: React.FC = () => {
const [isBusy, setIsBusy] = React.useState(false);
const [slideIdx, setSlideIdx] = React.useState<number>(0);
const totalSlideNumber = content.length;
const slideDurationTimeout = (slideDuration: number) => {
setTimeout(() => {
setIsBusy(false);
}, slideDuration);
};
const parallaxScroll = _.throttle((e: React.WheelEvent<HTMLDivElement>) => {
const isWheelingDown = -e.deltaY <= 0;
if (isWheelingDown && !isBusy) {
setIsBusy(true);
if (slideIdx !== totalSlideNumber - 1) {
scrollDown();
}
slideDurationTimeout(transitionDuration);
}
if (!isWheelingDown && !isBusy) {
setIsBusy(true);
if (slideIdx !== 0) {
scrollUp();
}
slideDurationTimeout(transitionDuration);
}
});
const scrollDown = (): void => setSlideIdx((prevIdx) => prevIdx + 1);
const scrollUp = (): void => setSlideIdx((prevIdx) => prevIdx - 1);
return (
<div className="app" onWheel={parallaxScroll}>
{content.map((c, i) => {
const classNames = [
"section",
i <= slideIdx - 1 ? "down-scroll" : "",
i !== totalSlideNumber - 1 && i >= slideIdx ? "up-scroll" : ""
]
.join(" ")
.trim();
return (
<section className={classNames}>
<div className="parallax-wrapper">
<div className="content">
<h1 className="title">{c.title}</h1>
<h3 className="subtitle">{c.subtitle}</h3>
</div>
</div>
</section>
);
})}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
View Compiled
This Pen doesn't use any external CSS resources.