profile picture of Olivier Larose

Olivier Larose

June 4, 2023

/

Intermediate

/

Short

Text Disperse Effect

How to Make a Dispel Hover Effect with NextJs, GSAP and Framer Motion

A text hover animation featuring a dispel effect using NextJs, GSAP and Framer Motion

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.

page.js

page.mod...

global.c...

1

'use client';

2

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

3

4

export default function Home() {

5

6

return (

7

<main className={styles.main}>

8

<div className={styles.body}>

9

10

<div className='introLine'>

11

<p>Nathan</p>

12

<p>Smith</p>

13

</div>

14

15

<div className='introLine'>

16

<p>Design</p>

17

<p>&</p>

18

</div>

19

20

<div className='introLine'>

21

<p>Art</p>

22

<p>Direction</p>

23

</div>

24

25

<div className='introLine'>

26

<p>+447533063596</p>

27

</div>

28

29

<div className='introLine'>

30

<p>→Email</p>

31

</div>

32

33

<div className='introLine'>

34

<p>→Insta</p>

35

</div>

36

37

</div>

38

</main>

39

)

40

}

We should have something like this:

Screenshot of an SVG

Replace the Text by a Component

For the texts that we need to animate, we'll create a component inside /src/components/TextDisperse/ for them. We can then import and render that component inside the page.js.

TextDisp...

page.js

1

export default function TextDipserse({children}) {

2

3

const getChars = (element) => {

4

let chars = [];

5

const word = element.props.children

6

word.split("").forEach( (char, i) => {

7

chars.push(<span key={char + i}>{char}</span>)

8

})

9

return chars;

10

}

11

12

return (

13

<div className='introLine'>

14

{ getChars(children) }

15

</div>

16

)

17

}

Create the Animation File

For the animations, we will create an array of keys with the properties that we want to modify:

Here are the properties that will change:

  • x
  • y
  • rotationZ

TextDisperse/animation.js

1

export const transforms = [

2

{

3

x: -0.8,

4

y: -0.6,

5

rotationZ: -29

6

},

7

{

8

x: -0.2,

9

y: -0.4,

10

rotationZ: -6

11

},

12

{

13

x: -0.05,

14

y: 0.1,

15

rotationZ: 12

16

},

17

{

18

x: -0.05,

19

y: -0.1,

20

rotationZ: -9

21

},

22

{

23

x: -0.1,

24

y: 0.55,

25

rotationZ: 3

26

},

27

{

28

x: 0,

29

y: -0.1,

30

rotationZ: 9

31

},

32

{

33

x: 0,

34

y: 0.15,

35

rotationZ: -12

36

},

37

{

38

x: 0,

39

y: 0.15,

40

rotationZ: -17

41

},

42

{

43

x: 0,

44

y: -0.65,

45

rotationZ: 9

46

},

47

{

48

x: 0.1,

49

y: 0.4,

50

rotationZ: 12

51

},

52

{

53

x: 0,

54

y: -0.15,

55

rotationZ: -9

56

},

57

{

58

x: 0.2,

59

y: 0.15,

60

rotationZ: 12

61

},

62

{

63

x: 0.8,

64

y: 0.6,

65

rotationZ: 20

66

}

67

]

68

69

export const disperse = {

70

open: (i) => ({

71

x: transforms[i].x + "em",

72

y: transforms[i].y + "em",

73

rotateZ: transforms[i].rotationZ,

74

transition: {duration: 0.75, ease: [0.33, 1, 0.68, 1]},

75

zIndex: 1

76

}),

77

closed: {

78

x: 0,

79

y: 0,

80

rotateZ: 0,

81

transition: {duration: 0.75, ease: [0.33, 1, 0.68, 1]},

82

zIndex: 0

83

}

84

}

Integrating the Animations

For the animations, we will use Framer Motion and use the animation from the animation.js file:

TextDisperse/index.jsx

1

import { useState } from 'react';

2

import { motion } from 'framer-motion';

3

import { disperse } from './anim';

4

5

export default function TextDipserse({children, setBackground}) {

6

7

const [isAnimated, setIsAnimated] = useState(false);

8

9

const getChars = (element) => {

10

let chars = [];

11

const word = element.props.children

12

word.split("").forEach( (char, i) => {

13

chars.push(<motion.span custom={i} variants={disperse} animate={isAnimated ? "open" : "closed"} key={char + i}>{char}</motion.span>)

14

})

15

return chars;

16

}

17

18

const manageMouseEnter = () => {

19

setIsAnimated(true);

20

}

21

const manageMouseLeave = () => {

22

setIsAnimated(false);

23

}

24

25

return (

26

<div style={{cursor: "pointer"}} onMouseEnter={() => {manageMouseEnter()}} onMouseLeave={() => {manageMouseLeave(false)}} className='introLine'>

27

{ getChars(children) }

28

</div>

29

)

30

}

We should have something like this:

Darkening the background

To darken the background, we can create a state in the page.js file and give the responsibility of setting it to the TextDisperse component.

TextDisp...

page.js

page.mod...

1

...

2

export default function TextDipserse({children, setBackground}) {

3

...

4

const manageMouseEnter = () => {

5

setBackground(true)

6

setIsAnimated(true);

7

}

8

const manageMouseLeave = () => {

9

setBackground(false)

10

setIsAnimated(false);

11

}

12

13

return (

14

<div style={{cursor: "pointer"}} onMouseEnter={() => {manageMouseEnter()}} onMouseLeave={() => {manageMouseLeave(false)}} className='introLine'>

15

{ getChars(children) }

16

</div>

17

)

18

}

We should have something like this:

Wrapping up

That was it for this animation, very stylish animation and surprisingly easy to do as well!

Hope you learned a lot :)

-Oli

Related Animations

image

June 2, 2024

Mouse Image Distortion

A website animation featuring an image distortion in a curved, using the sin function, React, React Three Fiber and Framer Motion

image

May 4, 2024

Paint Reveal

A website tutorial on making a paint reveal / erasing effect using the destination out blend mode of the canvas API, made with React and Next.js

image

March 8, 2024

Blend Mode Cursor

A website tutorial featuring a moving cursor on mouse move, colored with CSS blend mode difference, made with React and GSAP. Inspired by https://trionn.com/