Initializing the project
Let's start the project by creating a Next.js application. We can do that by running npx create-next-app@latest client
inside of a terminal.
Adding the HTML and CSS
We can delete everything in the page.js
, global.css
and page.module.css
and add our own HTML and CSS, to start with a nice blank application.
- We will use Sass for the stylesheets, so we can run
npm i sass
. - We will use Lenis Scroll for the smooth scroll, so we can run
npm i @studio-freight/lenis
. - We will use Framer Motion for the animations, so we can run
npm i framer-motion
.
The first thing we can do is do the overall layout. The Layout be structured as such:
Two spacers are added on top and at the end of the gallery simply to visualize the parallax.
2
import { useEffect } from 'react';
3
import styles from './page.module.scss'
4
import Image from 'next/image';
5
import Lenis from '@studio-freight/lenis'
22
export default function Home() {
25
const lenis = new Lenis()
27
const raf = (time) => {
29
requestAnimationFrame(raf)
32
requestAnimationFrame(raf)
36
<main className={styles.main}>
37
<div className={styles.spacer}></div>
38
<div className={styles.gallery}>
39
<div className={styles.galleryWrapper}>
40
<Column images={[images[0], images[1], images[2]]}/>
41
<Column images={[images[3], images[4], images[5]]}/>
42
<Column images={[images[6], images[7], images[8]]}/>
43
<Column images={[images[9], images[10], images[11]]}/>
46
<div className={styles.spacer}></div>
51
const Column = ({images}) => {
54
className={styles.column}
57
images.map( (src, i) => {
58
return <div key={i} className={styles.imageContainer}>
60
src={`/images/${src}`}
Couple notes about the above code:
- Line 24: We set the smooth scroll with Lenis Scroll
- Line 7-8 (CSS) We make the gallery wrapper bigger than it's parent to have an initial overlap on the images.
We should have something like this:
Adding the Parallax
To create the parallax, we will use two hooks from the Framer Motion library:
- useScroll: is used to track the progression of the scroll
- useTransform: is used to transform the value of the progression of the scroll [0,1] into new values that will be used for the parallax
1
const { scrollYProgress } = useScroll({
3
offset: ['start end', 'end start']
Offset: The tracker starts at ['start end']
the start of the target and the end of the window and ends at ['end start']
the end of the target and the start of the window.
1
const y = useTransform(scrollYProgress, [0, 1], [0, height * 2])
2
const y2 = useTransform(scrollYProgress, [0, 1], [0, height * 3.3])
3
const y3 = useTransform(scrollYProgress, [0, 1], [0, height * 1.25])
4
const y4 = useTransform(scrollYProgress, [0, 1], [0, height * 3])
- Height is the height of the window.
- Line 1: The range
[0, 1]
is transformed into [0, height * 2]
and stored into a const y
- Four
y
values are created (for the 4 columns of images). These values will be added to the style of the columns in order to translate them on the Y axis depending the progress of the scroll
Putting everything together:
2
import { useEffect, useRef, useState } from 'react';
3
import { useTransform, useScroll, motion } from 'framer-motion';
6
export default function Home() {
8
const gallery = useRef(null);
9
const [dimension, setDimension] = useState({width:0, height:0});
11
const { scrollYProgress } = useScroll({
13
offset: ['start end', 'end start']
15
const { height } = dimension;
16
const y = useTransform(scrollYProgress, [0, 1], [0, height * 2])
17
const y2 = useTransform(scrollYProgress, [0, 1], [0, height * 3.3])
18
const y3 = useTransform(scrollYProgress, [0, 1], [0, height * 1.25])
19
const y4 = useTransform(scrollYProgress, [0, 1], [0, height * 3])
24
const resize = () => {
25
setDimension({width: window.innerWidth, height: window.innerHeight})
28
window.addEventListener("resize", resize)
29
requestAnimationFrame(raf);
33
window.removeEventListener("resize", resize);
38
<main className={styles.main}>
39
<div className={styles.spacer}></div>
40
<div ref={gallery} className={styles.gallery}>
41
<div className={styles.galleryWrapper}>
48
<div className={styles.spacer}></div>
53
const Column = ({images, y}) => {
56
className={styles.column}
Couple notes about the code above:
- Line 42: The y values are given to the Column components to create the parallax effect.
- CSS: The .column are moved to have the desired layout combined with the y movement.
We should have the final result:
Wrapping up
We're offically done with this animation!
Insane animation that's actually quite nice for a project presentation. Relatively easy to do with Framer Motion with the hooks provided by the library. Hope you learned something.
-Oli