profile picture of Olivier Larose

Olivier Larose

June 11, 2023

/

Intermediate

/

Medium

Project Gallery Mouse Hover

Build an Awwwards Project Gallery using NextJs, GSAP and Framer Motion

An awwwards winning website tutorial with a project gallery featuring a hover animation using Nextjs, GSAP and Framer Motion. Inspired by: https://dennissnellenberg.com/

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.

This project gallery will be composed of 3 different components:

  • Page Component: the parent, has the state and the data and imports the other two components.
  • Project Component: a component that represents a single project.
  • Modal Component: a component that represents the dynamic modal.

The Page component

The page component /app/page.js is the parent of the project gallery and is responsable for the data and the state.

page.js

page.mod...

1

'use client';

2

import styles from './page.module.css'

3

import { useState } from 'react';

4

import Project from '../components/project';

5

import Modal from '../components/modal';

6

7

const projects = [

8

{

9

title: "C2 Montreal",

10

src: "c2montreal.png",

11

color: "#000000"

12

},

13

{

14

title: "Office Studio",

15

src: "officestudio.png",

16

color: "#8C8C8C"

17

},

18

{

19

title: "Locomotive",

20

src: "locomotive.png",

21

color: "#EFE8D3"

22

},

23

{

24

title: "Silencio",

25

src: "silencio.png",

26

color: "#706D63"

27

}

28

]

29

30

export default function Home() {

31

32

const [modal, setModal] = useState({active: false, index: 0})

33

34

return (

35

<main className={styles.main}>

36

<div className={styles.body}>

37

{

38

projects.map( (project, index) => {

39

return <Project index={index} title={project.title} setModal={setModal} key={index}/>

40

})

41

}

42

</div>

43

<Modal modal={modal} projects={projects}/>

44

</main>

45

)

46

}

47

Note: With that code, we should still see a blank page, because the Project and Modal components are still empty.

The Project Component

The project component represents a single project and has a hover interactivity on it. When hovering it, there's a small css animation and it also sets the index inside of the modal state.

project/...

project/...

1

'use client';

2

import React from 'react'

3

import styles from './style.module.css';

4

5

export default function index({index, title, setModal}) {

6

7

return (

8

<div onMouseEnter={() => {setModal({active: true, index})}} onMouseLeave={() => {setModal({active: false, index})}} className={styles.project}>

9

<h2>{title}</h2>

10

<p>Design & Development</p>

11

</div>

12

)

13

}
  • Line 8: We set the state of the modal when hovering the project.
  • The project animations are done with pure CSS.

We should have something like this:

The Modal Component

Now we enter the nitty gritty, the modal is defintely the hard part of this tutorial. To do it, we will use the next/image, the GSAP library and Framer Motion

Let's install the required libraries:

npm i gsap framer-motion

Next let's start by doing the enter and exit animation of the modal. We basically use Framer Motion to scale it up and down. We'll also use CSS for the slide animation.

modal/in...

modal/st...

1

import { useRef } from 'react';

2

import { motion } from 'framer-motion';

3

import Image from 'next/image';

4

import styles from './style.module.css';

5

6

const scaleAnimation = {

7

initial: {scale: 0, x:"-50%", y:"-50%"},

8

enter: {scale: 1, x:"-50%", y:"-50%", transition: {duration: 0.4, ease: [0.76, 0, 0.24, 1]}},

9

closed: {scale: 0, x:"-50%", y:"-50%", transition: {duration: 0.4, ease: [0.32, 0, 0.67, 0]}}

10

}

11

12

export default function index({modal, projects}) {

13

14

const { active, index } = modal;

15

16

return (

17

<>

18

<motion.div variants={scaleAnimation} initial="initial" animate={active ? "enter" : "closed"} className={styles.modalContainer}>

19

<div style={{top: index * -100 + "%"}} className={styles.modalSlider}>

20

{

21

projects.map( (project, index) => {

22

const { src, color } = project

23

return <div className={styles.modal} style={{backgroundColor: color}} key={`modal_${index}`}>

24

<Image

25

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

26

width={300}

27

height={0}

28

alt="image"

29

/>

30

</div>

31

})

32

}

33

</div>

34

</motion.div>

35

<motion.div className={styles.cursor} variants={scaleAnimation} initial="initial" animate={active ? "enter" : "closed"}></motion.div>

36

<motion.div className={styles.cursorLabel} variants={scaleAnimation} initial="initial" animate={active ? "enter" : "closed"}>View</motion.div>

37

</>

38

)

39

}

40

Couple notes about the code:

  • The scaling is made with Framer Motion through the scaleAnimation const. When a project is hovered, the state is changed which animates in and out the modal, the cursor and the cursor label.
  • The sliding animation is made through CSS, depending on the hovered project, the top position is adjusted accordingly.

Moving the modal along the mouse

To move the modal with the mouse move event, we use the GSAP library to easily do that.

/modal/index.jsx

1

import { useRef, useEffect } from 'react';

2

import gsap from 'gsap';

3

4

...

5

const modalContainer = useRef(null);

6

const cursor = useRef(null);

7

const cursorLabel = useRef(null);

8

9

useEffect( () => {

10

//Move Container

11

let xMoveContainer = gsap.quickTo(modalContainer.current, "left", {duration: 0.8, ease: "power3"})

12

let yMoveContainer = gsap.quickTo(modalContainer.current, "top", {duration: 0.8, ease: "power3"})

13

//Move cursor

14

let xMoveCursor = gsap.quickTo(cursor.current, "left", {duration: 0.5, ease: "power3"})

15

let yMoveCursor = gsap.quickTo(cursor.current, "top", {duration: 0.5, ease: "power3"})

16

//Move cursor label

17

let xMoveCursorLabel = gsap.quickTo(cursorLabel.current, "left", {duration: 0.45, ease: "power3"})

18

let yMoveCursorLabel = gsap.quickTo(cursorLabel.current, "top", {duration: 0.45, ease: "power3"})

19

20

window.addEventListener('mousemove', (e) => {

21

const { pageX, pageY } = e;

22

xMoveContainer(pageX)

23

yMoveContainer(pageY)

24

xMoveCursor(pageX)

25

yMoveCursor(pageY)

26

xMoveCursorLabel(pageX)

27

yMoveCursorLabel(pageY)

28

})

29

}, [])

30

31

return (

32

<>

33

<motion.div ref={modalContainer} variants={scaleAnimation} initial="initial" animate={active ? "enter" : "closed"} className={styles.modalContainer}>

34

<div style={{top: index * -100 + "%"}} className={styles.modalSlider}>

35

{

36

...

37

}

38

</div>

39

</motion.div>

40

<motion.div ref={cursor} className={styles.cursor} variants={scaleAnimation} initial="initial" animate={active ? "enter" : "closed"}></motion.div>

41

<motion.div ref={cursorLabel} className={styles.cursorLabel} variants={scaleAnimation} initial="initial" animate={active ? "enter" : "closed"}>View</motion.div>

42

</>

43

)

44

}

45

Couple notes about the code:

  • 3 refs are created in order to target the modal, the cursor and the cursor label
  • the movement is created with the quickTo function from GSAP library.
  • Note that the durations vary in order to create a delay between the different moving elements.

Wrapping up

Hope you liked this tutorial, I've seen similar project galleries in a lot of awwwards winning website and so I thought it'd be intersting to know how it's possible to make something similar. Hope you learned something :)

-Oli

Related Animations

image

Jul 24, 2023

Awwwards Landing Page

An Awwwards portfolio landing page rebuild. Originally made by Dennis Snellenberg, he won an awwwards with his amazing portoflio. Remade the landing page using Next.js, Framer Motion and GSAP. See the original: https://dennissnellenberg.com/

image

May 28, 2023

Infinite Text Move On Scroll

An infinite text moving animation with scroll interaction using React and Next.js. Picture by Eric Asamoah.

image

May 13, 2023

Image slide project gallery

A project gallery animation featuring an image slide effect using the width auto animation of Framer Motion. Made with React and Next.js. Inspired by https://locomotive.ca/