Mouse Image Distortion
A website animation featuring an image distortion in a curved, using the sin function, React, React Three Fiber and Framer Motion
Olivier Larose
June 4, 2023
/
Intermediate
/
Short
A text hover animation featuring a dispel effect using NextJs, GSAP and Framer Motion
Live DemoSource codeVideo TutorialLet'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.
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
}
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.children6
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
}
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:
TextDisperse/animation.js
1
export const transforms = [2
{3
x: -0.8,4
y: -0.6,5
rotationZ: -296
},7
{8
x: -0.2,9
y: -0.4,10
rotationZ: -611
},12
{13
x: -0.05,14
y: 0.1,15
rotationZ: 1216
},17
{18
x: -0.05,19
y: -0.1,20
rotationZ: -921
},22
{23
x: -0.1,24
y: 0.55,25
rotationZ: 326
},27
{28
x: 0,29
y: -0.1,30
rotationZ: 931
},32
{33
x: 0,34
y: 0.15,35
rotationZ: -1236
},37
{38
x: 0,39
y: 0.15,40
rotationZ: -1741
},42
{43
x: 0,44
y: -0.65,45
rotationZ: 946
},47
{48
x: 0.1,49
y: 0.4,50
rotationZ: 1251
},52
{53
x: 0,54
y: -0.15,55
rotationZ: -956
},57
{58
x: 0.2,59
y: 0.15,60
rotationZ: 1261
},62
{63
x: 0.8,64
y: 0.6,65
rotationZ: 2066
}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: 176
}),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: 083
}84
}
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.children12
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
}
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
}
That was it for this animation, very stylish animation and surprisingly easy to do as well!
Hope you learned a lot :)
-Oli