profile picture of Olivier Larose

Olivier Larose

August 9, 2023

/

Beginner

/

Short

Magnetic Button

2 Ways to Make a Magnetic Buttons using React, GSAP, Framer Motion

2 Ways to Make a Magnetic Buttons using React, GSAP, Framer Motion. See the difference in terms of implementation for a magnetic effect between 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.

  • We will use Sass for the stylesheets, so we can run npm i sass.
  • We will use Framer Motion for the animation, so we can run npm i framer-motion.
  • We will also use GSAP for the animation, so we can run npm i gsap.

There's a lot of questions about using GSAP vs Framer Motion, so in this tutorial, we'll see the difference between imperative and declarative animations.

Then we can adding the basic layout:

page.js

page.mod...

1

'use client';

2

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

3

4

export default function Home() {

5

return (

6

<main className={styles.main}>

7

<p>Made with GSAP:</p>

8

<div className={styles.container}>

9

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.5 58">

10

<path d="m20.72,22.16c2.77,0,5.55.02,8.32.03.4,0,.8.02,1.2.03.07.06.14.13.21.19-.28,1.58-.56,3.16-.83,4.75-.32,1.87-.64,3.74-.99,5.76-1.37.13-2.76-.07-4.14-.04-1.36.03-2.72,0-4.2,0-.13,8.38.12,16.72.11,25.11h-11.17v-24.91H0v-10.81h9.16c.04-.39.11-.71.11-1.02-.01-1.58-.05-3.17-.06-4.75-.01-1.62-.16-3.26.02-4.85.19-1.69.64-3.35,1.52-4.86,1.36-2.33,3.28-4.06,5.58-5.4,1.39-.81,2.94-1.25,4.52-1.3C24.28-.03,27.71.02,31.15,0c.07,0,.13.05.35.14.04,3.3-.29,6.66-.18,10.11-1.13,0-2.15.03-3.17,0-1.57-.07-3.15-.06-4.65.46-1.42.49-2.46,1.4-2.89,2.95-.3,1.08-.36,2.16-.34,3.25.04,1.69.13,3.38.2,5.07.08.06.16.13.24.19Z"/>

11

</svg>

12

13

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.13 46.08">

14

<path d="m0,41.47c3.69-.34,7.11-.82,10.38-1.89,2.05-.67,4.03-1.62,5.71-3.35-.55-.18-1.03-.38-1.53-.48-2.77-.58-5.26-1.64-7-4-.72-.98-1.33-2.03-2.03-3.1,1.23-.3,2.43-.59,3.81-.93-.91-.56-1.7-1.08-2.52-1.53-2.08-1.13-3.73-2.68-4.6-4.91-.37-.95-.52-1.98-.77-2.98-.11-.43-.19-.86-.3-1.33,1.42.02,2.71.63,4.2.28-.72-1.12-1.37-2.17-2.06-3.2-1.78-2.65-2.05-5.51-1.29-8.54.23-.91.43-1.84.69-2.95.78.63,1.4,1.05,1.94,1.56,2.42,2.28,5.16,4.11,8.01,5.83,2.36,1.43,4.89,2.38,7.49,3.17,1.99.61,4.06.96,6.33.91.02-.67.06-1.3.07-1.92.05-2.38.81-4.54,1.99-6.57,1.71-2.92,4.43-4.39,7.56-5.26,1.39-.39,2.76-.3,4.22-.14,2.64.29,4.8,1.52,6.96,2.82.49.3.86.38,1.4.19,1.21-.43,2.43-.83,3.67-1.19.49-.15,1.02-.18,1.84-.31-1.11,1.79-2.05,3.3-3,4.82,1.65.08,3.16-.84,4.97-.57-.86,1.35-1.83,2.42-2.78,3.52-.78.91-1.81,1.7-2.04,2.98-.25,1.43-.63,2.83-.75,4.31-.14,1.7-.6,3.37-.96,5.05-.14.64-.37,1.25-.58,1.87-1.35,3.9-3.27,7.44-5.93,10.64-2.96,3.55-6.42,6.42-10.55,8.47-2.84,1.4-5.83,2.42-8.95,2.97-2.6.45-5.24.39-7.87.33-2.95-.07-5.78-.69-8.6-1.52-2.27-.67-4.39-1.61-6.51-2.61-.14-.07-.26-.18-.61-.44Z"/>

15

</svg>

16

17

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 51.1 36.69">

18

<path d="m26.22,36.56c-4.74,0-9.49,0-14.23,0-3.93,0-7.19-1.49-9.63-4.58-1.02-1.29-1.66-2.82-2.03-4.45-.28-1.25-.28-2.52-.29-3.78C.03,19.98,0,16.21,0,12.44c0-1.06,0-2.13.18-3.17.2-1.09.59-2.15,1.16-3.13C2.75,3.73,4.77,1.97,7.28.79c.85-.4,1.78-.61,2.74-.63,2.02-.04,4.04-.21,6.06-.14,7.73.27,15.47.02,23.2.14,1.97.03,3.89.43,5.62,1.33,2.23,1.16,3.98,2.81,4.91,5.25.52,1.36.76,2.72.93,4.16.56,4.65.3,9.32.27,13.98-.02,2.7-.92,5.16-2.63,7.27-1.73,2.15-3.95,3.62-6.68,4.08-1.8.31-3.64.39-5.47.43-3.34.06-6.68.02-10.02.02,0-.04,0-.07,0-.11Zm7.94-18.51c-4.62-2.62-9.12-5.17-13.62-7.71-.12-.07-.29-.07-.4-.1v16.33c4.78-2.84,9.41-5.56,14.03-8.52Z"/>

19

</svg>

20

21

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.26 53.87">

22

<path d="m34.27,18.6c0,4.82,0,9.64.03,14.47.01,2.53-.24,5.01-.67,7.5-.68,3.88-2.69,6.84-5.66,9.33-2.21,1.86-4.72,2.98-7.48,3.6-1.02.23-2.09.29-3.14.34-3.54.18-6.73-.93-9.64-2.84-2.68-1.76-4.74-4.12-6.12-7.05-.74-1.58-1.16-3.26-1.51-4.94-.2-.98,0-2.04-.02-3.06-.07-3.01,1.1-5.62,2.62-8.11,2.19-3.6,5.35-5.98,9.35-7.26,1.24-.4,2.51-.7,3.84-.66.33.01.66-.13.99-.14.83-.02,1.65,0,2.58,0v9.31c-.27,0-.55-.01-.84,0-1.06.05-2.13.05-3.18.18-2.4.29-3.92,1.83-5.15,3.74-1.77,2.78-.84,7.04,1.38,9.35,2.28,2.38,6.05,2.67,8.72,1.48.6-.27,1.18-.6,1.73-.98,1.45-1.01,2.12-2.51,2.39-4.17.62-3.94.33-7.92.36-11.89.05-7.97,0-15.95,0-23.92,0-.87,0-1.74,0-2.72,3.08-.25,6.11-.14,9.16-.19.91,7.84,5.41,12.08,13.22,13.34-.07,2.84.22,5.85-.25,8.97-4.44-.24-8.48-1.55-12.25-3.76-.11-.11-.22-.22-.33-.33-.05.13-.09.25-.14.38Z"/>

23

</svg>

24

</div>

25

<p>Made with Framer Motion:</p>

26

<div className={styles.container}>

27

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.5 58">

28

<path d="m20.72,22.16c2.77,0,5.55.02,8.32.03.4,0,.8.02,1.2.03.07.06.14.13.21.19-.28,1.58-.56,3.16-.83,4.75-.32,1.87-.64,3.74-.99,5.76-1.37.13-2.76-.07-4.14-.04-1.36.03-2.72,0-4.2,0-.13,8.38.12,16.72.11,25.11h-11.17v-24.91H0v-10.81h9.16c.04-.39.11-.71.11-1.02-.01-1.58-.05-3.17-.06-4.75-.01-1.62-.16-3.26.02-4.85.19-1.69.64-3.35,1.52-4.86,1.36-2.33,3.28-4.06,5.58-5.4,1.39-.81,2.94-1.25,4.52-1.3C24.28-.03,27.71.02,31.15,0c.07,0,.13.05.35.14.04,3.3-.29,6.66-.18,10.11-1.13,0-2.15.03-3.17,0-1.57-.07-3.15-.06-4.65.46-1.42.49-2.46,1.4-2.89,2.95-.3,1.08-.36,2.16-.34,3.25.04,1.69.13,3.38.2,5.07.08.06.16.13.24.19Z"/>

29

</svg>

30

31

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.13 46.08">

32

<path d="m0,41.47c3.69-.34,7.11-.82,10.38-1.89,2.05-.67,4.03-1.62,5.71-3.35-.55-.18-1.03-.38-1.53-.48-2.77-.58-5.26-1.64-7-4-.72-.98-1.33-2.03-2.03-3.1,1.23-.3,2.43-.59,3.81-.93-.91-.56-1.7-1.08-2.52-1.53-2.08-1.13-3.73-2.68-4.6-4.91-.37-.95-.52-1.98-.77-2.98-.11-.43-.19-.86-.3-1.33,1.42.02,2.71.63,4.2.28-.72-1.12-1.37-2.17-2.06-3.2-1.78-2.65-2.05-5.51-1.29-8.54.23-.91.43-1.84.69-2.95.78.63,1.4,1.05,1.94,1.56,2.42,2.28,5.16,4.11,8.01,5.83,2.36,1.43,4.89,2.38,7.49,3.17,1.99.61,4.06.96,6.33.91.02-.67.06-1.3.07-1.92.05-2.38.81-4.54,1.99-6.57,1.71-2.92,4.43-4.39,7.56-5.26,1.39-.39,2.76-.3,4.22-.14,2.64.29,4.8,1.52,6.96,2.82.49.3.86.38,1.4.19,1.21-.43,2.43-.83,3.67-1.19.49-.15,1.02-.18,1.84-.31-1.11,1.79-2.05,3.3-3,4.82,1.65.08,3.16-.84,4.97-.57-.86,1.35-1.83,2.42-2.78,3.52-.78.91-1.81,1.7-2.04,2.98-.25,1.43-.63,2.83-.75,4.31-.14,1.7-.6,3.37-.96,5.05-.14.64-.37,1.25-.58,1.87-1.35,3.9-3.27,7.44-5.93,10.64-2.96,3.55-6.42,6.42-10.55,8.47-2.84,1.4-5.83,2.42-8.95,2.97-2.6.45-5.24.39-7.87.33-2.95-.07-5.78-.69-8.6-1.52-2.27-.67-4.39-1.61-6.51-2.61-.14-.07-.26-.18-.61-.44Z"/>

33

</svg>

34

35

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 51.1 36.69">

36

<path d="m26.22,36.56c-4.74,0-9.49,0-14.23,0-3.93,0-7.19-1.49-9.63-4.58-1.02-1.29-1.66-2.82-2.03-4.45-.28-1.25-.28-2.52-.29-3.78C.03,19.98,0,16.21,0,12.44c0-1.06,0-2.13.18-3.17.2-1.09.59-2.15,1.16-3.13C2.75,3.73,4.77,1.97,7.28.79c.85-.4,1.78-.61,2.74-.63,2.02-.04,4.04-.21,6.06-.14,7.73.27,15.47.02,23.2.14,1.97.03,3.89.43,5.62,1.33,2.23,1.16,3.98,2.81,4.91,5.25.52,1.36.76,2.72.93,4.16.56,4.65.3,9.32.27,13.98-.02,2.7-.92,5.16-2.63,7.27-1.73,2.15-3.95,3.62-6.68,4.08-1.8.31-3.64.39-5.47.43-3.34.06-6.68.02-10.02.02,0-.04,0-.07,0-.11Zm7.94-18.51c-4.62-2.62-9.12-5.17-13.62-7.71-.12-.07-.29-.07-.4-.1v16.33c4.78-2.84,9.41-5.56,14.03-8.52Z"/>

37

</svg>

38

39

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.26 53.87">

40

<path d="m34.27,18.6c0,4.82,0,9.64.03,14.47.01,2.53-.24,5.01-.67,7.5-.68,3.88-2.69,6.84-5.66,9.33-2.21,1.86-4.72,2.98-7.48,3.6-1.02.23-2.09.29-3.14.34-3.54.18-6.73-.93-9.64-2.84-2.68-1.76-4.74-4.12-6.12-7.05-.74-1.58-1.16-3.26-1.51-4.94-.2-.98,0-2.04-.02-3.06-.07-3.01,1.1-5.62,2.62-8.11,2.19-3.6,5.35-5.98,9.35-7.26,1.24-.4,2.51-.7,3.84-.66.33.01.66-.13.99-.14.83-.02,1.65,0,2.58,0v9.31c-.27,0-.55-.01-.84,0-1.06.05-2.13.05-3.18.18-2.4.29-3.92,1.83-5.15,3.74-1.77,2.78-.84,7.04,1.38,9.35,2.28,2.38,6.05,2.67,8.72,1.48.6-.27,1.18-.6,1.73-.98,1.45-1.01,2.12-2.51,2.39-4.17.62-3.94.33-7.92.36-11.89.05-7.97,0-15.95,0-23.92,0-.87,0-1.74,0-2.72,3.08-.25,6.11-.14,9.16-.19.91,7.84,5.41,12.08,13.22,13.34-.07,2.84.22,5.85-.25,8.97-4.44-.24-8.48-1.55-12.25-3.76-.11-.11-.22-.22-.33-.33-.05.13-.09.25-.14.38Z"/>

41

</svg>

42

</div>

43

</main>

44

)

45

}

We should have something like this:

GSAP Implementation

For the GSAP implementation, we use the quickTo method to move the cursor around. We also use the React.cloneElement() in order to return the children with a ref attached to it.

gsap.jsx

1

import React, { useEffect, useRef } from 'react'

2

import gsap from 'gsap';

3

4

export default function index({children}) {

5

const magnetic = useRef(null);

6

7

useEffect( () => {

8

const xTo = gsap.quickTo(magnetic.current, "x", {duration: 1, ease: "elastic.out(1, 0.3)"})

9

const yTo = gsap.quickTo(magnetic.current, "y", {duration: 1, ease: "elastic.out(1, 0.3)"})

10

11

const mouseMove = (e) => {

12

const { clientX, clientY } = e;

13

const {height, width, left, top} = magnetic.current.getBoundingClientRect();

14

const x = clientX - (left + width/2)

15

const y = clientY - (top + height/2)

16

xTo(x);

17

yTo(y)

18

}

19

20

const mouseLeave = (e) => {

21

gsap.to(magnetic.current, {x: 0, duration: 1})

22

gsap.to(magnetic.current, {y: 0, duration: 1})

23

xTo(0);

24

yTo(0)

25

}

26

27

magnetic.current.addEventListener("mousemove", mouseMove)

28

magnetic.current.addEventListener("mouseleave", mouseLeave)

29

30

return () => {

31

magnetic.current.removeEventListener("mousemove", mouseMove)

32

magnetic.current.removeEventListener("mouseleave", mouseLeave)

33

}

34

}, [])

35

36

return (

37

React.cloneElement(children, {ref:magnetic})

38

)

39

}

Then we can wrap the SVG's inside the component:

page.js

1

<MagneticGSAP>

2

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.5 58">

3

<path d="m20.72,22.16c2.77,0,5.55.02,8.32.03.4,0,.8.02,1.2.03.07.06.14.13.21.19-.28,1.58-.56,3.16-.83,4.75-.32,1.87-.64,3.74-.99,5.76-1.37.13-2.76-.07-4.14-.04-1.36.03-2.72,0-4.2,0-.13,8.38.12,16.72.11,25.11h-11.17v-24.91H0v-10.81h9.16c.04-.39.11-.71.11-1.02-.01-1.58-.05-3.17-.06-4.75-.01-1.62-.16-3.26.02-4.85.19-1.69.64-3.35,1.52-4.86,1.36-2.33,3.28-4.06,5.58-5.4,1.39-.81,2.94-1.25,4.52-1.3C24.28-.03,27.71.02,31.15,0c.07,0,.13.05.35.14.04,3.3-.29,6.66-.18,10.11-1.13,0-2.15.03-3.17,0-1.57-.07-3.15-.06-4.65.46-1.42.49-2.46,1.4-2.89,2.95-.3,1.08-.36,2.16-.34,3.25.04,1.69.13,3.38.2,5.07.08.06.16.13.24.19Z"/>

4

</svg>

5

</MagneticGSAP>

We should have something like this:

Framer Motion Implementation

For Framer Motion, I use the setState() hook with the motion tag from Framer Motion.

framer.jsx

1

import { useRef, useState } from 'react'

2

import { motion } from 'framer-motion';

3

4

export default function Framer({children}) {

5

const ref = useRef(null);

6

const [position, setPosition] = useState({x:0,y:0});

7

8

const handleMouse = (e) => {

9

const { clientX, clientY } = e;

10

const {height, width, left, top} = ref.current.getBoundingClientRect();

11

const middleX = clientX - (left + width/2)

12

const middleY = clientY - (top + height/2)

13

setPosition({x: middleX, y: middleY})

14

}

15

16

const reset = () => {

17

setPosition({x:0, y:0})

18

}

19

20

const { x, y } = position;

21

return (

22

<motion.div

23

style={{position: "relative"}}

24

ref={ref}

25

onMouseMove={handleMouse}

26

onMouseLeave={reset}

27

animate={{x, y}}

28

transition={{type: "spring", stiffness: 150, damping: 15, mass: 0.1}}

29

>

30

{children}

31

</motion.div>

32

)

33

}

Then we can wrap the SVG's inside the component:

page.js

1

<MagneticFramer>

2

<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.5 58">

3

<path d="m20.72,22.16c2.77,0,5.55.02,8.32.03.4,0,.8.02,1.2.03.07.06.14.13.21.19-.28,1.58-.56,3.16-.83,4.75-.32,1.87-.64,3.74-.99,5.76-1.37.13-2.76-.07-4.14-.04-1.36.03-2.72,0-4.2,0-.13,8.38.12,16.72.11,25.11h-11.17v-24.91H0v-10.81h9.16c.04-.39.11-.71.11-1.02-.01-1.58-.05-3.17-.06-4.75-.01-1.62-.16-3.26.02-4.85.19-1.69.64-3.35,1.52-4.86,1.36-2.33,3.28-4.06,5.58-5.4,1.39-.81,2.94-1.25,4.52-1.3C24.28-.03,27.71.02,31.15,0c.07,0,.13.05.35.14.04,3.3-.29,6.66-.18,10.11-1.13,0-2.15.03-3.17,0-1.57-.07-3.15-.06-4.65.46-1.42.49-2.46,1.4-2.89,2.95-.3,1.08-.36,2.16-.34,3.25.04,1.69.13,3.38.2,5.07.08.06.16.13.24.19Z"/>

4

</svg>

5

</MagneticFramer>

We should have something like this:

Wrapping up

That's it for this animation!

We saw how we can create a Magnetic Effect using two distinct animation library. Hope you learned more about the difference between imperative and declarative animations.

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