profile picture of Olivier Larose

Olivier Larose

August 2, 2023

/

Beginner

/

Short

Smooth Parallax Scroll

Build a Smooth Parallax Scroll with Framer Motion, Lenis Scroll, Nextjs

A web animation tutorial featuring a Smooth Vertical Parallax Image Scroll Gallery made with Framer Motion, Lenis Scroll and Nextjs. Inspired by:https://mill3.studio/en/projects/gsoft/

Live DemoSource codeVideo Tutorial
background video

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:

  • Spacer
  • Gallery
  • Spacer

Two spacers are added on top and at the end of the gallery simply to visualize the parallax.

Page.js

page.mod...

1

'use client';

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'

6

7

const images = [

8

"1.jpg",

9

"2.jpg",

10

"3.jpg",

11

"4.jpg",

12

"5.jpg",

13

"6.jpg",

14

"7.jpg",

15

"8.jpg",

16

"9.jpg",

17

"10.jpg",

18

"11.jpg",

19

"12.jpg",

20

]

21

22

export default function Home() {

23

24

useEffect( () => {

25

const lenis = new Lenis()

26

27

const raf = (time) => {

28

lenis.raf(time)

29

requestAnimationFrame(raf)

30

}

31

32

requestAnimationFrame(raf)

33

}, [])

34

35

return (

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]]}/>

44

</div>

45

</div>

46

<div className={styles.spacer}></div>

47

</main>

48

)

49

}

50

51

const Column = ({images}) => {

52

return (

53

<div

54

className={styles.column}

55

>

56

{

57

images.map( (src, i) => {

58

return <div key={i} className={styles.imageContainer}>

59

<Image

60

src={`/images/${src}`}

61

alt='image'

62

fill

63

/>

64

</div>

65

})

66

}

67

</div>

68

)

69

}

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

useScroll Hook

1

const { scrollYProgress } = useScroll({

2

target: gallery,

3

offset: ['start end', 'end start']

4

})

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.

useTransform Hook

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:

page.js

page.mod...

1

'use client';

2

import { useEffect, useRef, useState } from 'react';

3

import { useTransform, useScroll, motion } from 'framer-motion';

4

...

5

6

export default function Home() {

7

8

const gallery = useRef(null);

9

const [dimension, setDimension] = useState({width:0, height:0});

10

11

const { scrollYProgress } = useScroll({

12

target: gallery,

13

offset: ['start end', 'end start']

14

})

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])

20

21

useEffect( () => {

22

...

23

24

const resize = () => {

25

setDimension({width: window.innerWidth, height: window.innerHeight})

26

}

27

28

window.addEventListener("resize", resize)

29

requestAnimationFrame(raf);

30

resize();

31

32

return () => {

33

window.removeEventListener("resize", resize);

34

}

35

}, [])

36

37

return (

38

<main className={styles.main}>

39

<div className={styles.spacer}></div>

40

<div ref={gallery} className={styles.gallery}>

41

<div className={styles.galleryWrapper}>

42

<Column ... y={y}/>

43

<Column ... y={y2}/>

44

<Column ... y={y3}/>

45

<Column ... y={y4}/>

46

</div>

47

</div>

48

<div className={styles.spacer}></div>

49

</main>

50

)

51

}

52

53

const Column = ({images, y}) => {

54

return (

55

<motion.div

56

className={styles.column}

57

style={{y}}

58

>

59

...

60

</motion.div>

61

)

62

}

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

Related Animations

image

June 2, 2024

Mask Section Transition

A website tutorial featuring a scroll animation using an SVG Mask to create a section transition, made with React, Framer Motion. Inspired by: https://axelvanhessche.com/. Pictures by Eric Asamoah, Inka and Niclas Lindergård, Daniel Ribar

image

May 25, 2024

Background Image Parallax

A website animation featuring a background image moving on scroll in a parallax motion, made with Framer Motion and React, inside a Next.js app. Inspired by: https://inkfishnyc.com/. Pictures by Matthias Leidinger

image

May 25, 2024

Text Parallax

A website animation featuring a Text Parallax with sliding text on scroll, made with Framer Motion and React, inside a Next.js app