Effect
Magic Cursor Override for Framer
This is a code override in Framer that you can apply to any frame. So, whenever that element is hovered over on the website, a magic cursor animation gets triggered. Feel free to remix the project and have fun playing with the effect.
Props to Hyperplexed for creating the CodePen, and to Canva for the original concept.
About the resource
This code override is super easy to use. You can just copy and paste the code into your Framer project and create the code override yourself from the assets panel.
It's also customizable. You can change the color of the stars by tweaking the RGB values here:
About the resource
This code override is super easy to use. You can just copy and paste the code into your Framer project and create the code override yourself from the assets panel.
It's also customizable. You can change the color of the stars by tweaking the RGB values here:
About the resource
This code override is super easy to use. You can just copy and paste the code into your Framer project and create the code override yourself from the assets panel.
It's also customizable. You can change the color of the stars by tweaking the RGB values here:
Below the RGB values, you can also specify three different sizes for the stars.
And here, you can specify the color of the glow, once more using an RGB value:
Below the RGB values, you can also specify three different sizes for the stars.
And here, you can specify the color of the glow, once more using an RGB value:
Below the RGB values, you can also specify three different sizes for the stars.
And here, you can specify the color of the glow, once more using an RGB value:
You can find and copy the code override below.
For the 3D effect, I used the 3D hover override that you can find on this link.
Code override for the magic cursor
Feel free to copy the override code from below and create the override yourself in your Framer project.
You can find and copy the code override below.
For the 3D effect, I used the 3D hover override that you can find on this link.
Code override for the magic cursor
Feel free to copy the override code from below and create the override yourself in your Framer project.
You can find and copy the code override below.
For the 3D effect, I used the 3D hover override that you can find on this link.
Code override for the magic cursor
Feel free to copy the override code from below and create the override yourself in your Framer project.
import { ComponentType, useEffect, useState } from "react"
import { motion, useSpring, useMotionValue } from "framer-motion"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faStar } from "@fortawesome/free-solid-svg-icons"
import ReactDOM from "react-dom"
export function withCursor(Component): ComponentType {
return (props) => {
const originPosition = { x: 0, y: 0 }
const [isHover, setIsHover] = useState(false)
const [count, setCount] = useState(0)
const [last, setLast] = useState({
starTimestamp: 0,
starPosition: originPosition,
mousePosition: originPosition,
})
const config = {
starAnimationDuration: 1500,
minimumTimeBetweenStars: 250,
minimumDistanceBetweenStars: 75,
glowDuration: 75,
maximumGlowPointSpacing: 10,
colors: ["144 211 252", "252 254 255"],
sizes: ["1.4rem", "1rem", "0.6rem"],
animations: ["fall-1", "fall-2", "fall-3"],
}
const rand = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min
const selectRandom = (items) => items[rand(0, items.length - 1)]
const withUnit = (value, unit) => `${value}${unit}`
const px = (value) => withUnit(value, "px")
const ms = (value) => withUnit(value, "ms")
const calcDistance = (a, b) => {
const diffX = b.x - a.x,
diffY = b.y - a.y
return Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2))
}
const calcElapsedTime = (start, end) => end - start
const createStar = (position) => {
const star = document.createElement("span")
const color = selectRandom(config.colors)
star.className = "star"
// Use FontAwesomeIcon to render the star icon
const iconComponent = <FontAwesomeIcon icon={faStar} />
ReactDOM.render(iconComponent, star)
star.style.left = px(position.x)
star.style.top = px(position.y)
star.style.fontSize = selectRandom(config.sizes)
star.style.color = `rgb(${color})`
star.style.textShadow = `0px 0px 1.5rem rgb(${color} / 0.5)`
star.style.animationName = config.animations[count % 3]
setCount((prevCount) => prevCount + 1)
star.style.animationDuration = ms(config.starAnimationDuration)
document.body.appendChild(star)
setTimeout(
() => document.body.removeChild(star),
config.starAnimationDuration
)
}
const createGlowPoint = (position) => {
const glow = document.createElement("div")
glow.className = "glow-point"
glow.style.left = px(position.x)
glow.style.top = px(position.y)
document.body.appendChild(glow)
setTimeout(
() => document.body.removeChild(glow),
config.glowDuration
)
}
const determinePointQuantity = (distance) =>
Math.max(Math.floor(distance / config.maximumGlowPointSpacing), 1)
const createGlow = (lastPoint, currentPoint) => {
const distance = calcDistance(lastPoint, currentPoint)
const quantity = determinePointQuantity(distance)
const dx = (currentPoint.x - lastPoint.x) / quantity
const dy = (currentPoint.y - lastPoint.y) / quantity
Array.from(Array(quantity)).forEach((_, index) => {
const x = lastPoint.x + dx * index,
y = lastPoint.y + dy * index
createGlowPoint({ x, y })
})
}
const updateLastStar = (position) => {
setLast((prevLast) => ({
...prevLast,
starTimestamp: new Date().getTime(),
starPosition: position,
}))
}
const updateLastMousePosition = (position) => {
setLast((prevLast) => ({
...prevLast,
mousePosition: position,
}))
}
const adjustLastMousePosition = (position) => {
if (last.mousePosition.x === 0 && last.mousePosition.y === 0) {
setLast((prevLast) => ({
...prevLast,
mousePosition: position,
}))
}
}
const handleOnMove = (e) => {
const mousePosition = {
x: e.clientX + window.scrollX,
y: e.clientY + window.scrollY,
}
adjustLastMousePosition(mousePosition)
const now = new Date().getTime()
const hasMovedFarEnough =
calcDistance(last.starPosition, mousePosition) >=
config.minimumDistanceBetweenStars
const hasBeenLongEnough =
calcElapsedTime(last.starTimestamp, now) >
config.minimumTimeBetweenStars
if (isHover) {
if (hasMovedFarEnough || hasBeenLongEnough) {
createStar(mousePosition)
updateLastStar(mousePosition)
}
createGlow(last.mousePosition, mousePosition)
}
updateLastMousePosition(mousePosition)
}
useEffect(() => {
window.addEventListener("mousemove", handleOnMove)
window.addEventListener("touchmove", (e) =>
handleOnMove(e.touches[0])
)
document.body.addEventListener("mouseleave", () =>
updateLastMousePosition(originPosition)
)
return () => {
window.removeEventListener("mousemove", handleOnMove)
window.removeEventListener("touchmove", (e) =>
handleOnMove(e.touches[0])
)
document.body.removeEventListener("mouseleave", () =>
updateLastMousePosition(originPosition)
)
}
}, [last])
return (
<>
<style>{`
.glow-point {
position: absolute;
box-shadow: 0rem 0rem 1.2rem 0.6rem rgb(102, 242, 255, 0.85);
pointer-events: none;
}
.star {
position: absolute;
z-index: 2;
color: white;
font-size: 1rem;
animation-duration: 1500ms;
animation-fill-mode: forwards;
pointer-events: none;
}
@keyframes fall-1 {
0% {
transform: translate(0px, 0px) rotateX(45deg) rotateY(30deg) rotateZ(0deg) scale(0.25);
opacity: 0;
}
5% {
transform: translate(10px, -10px) rotateX(45deg) rotateY(30deg) rotateZ(0deg) scale(1);
opacity: 1;
}
100% {
transform: translate(25px, 200px) rotateX(180deg) rotateY(270deg) rotateZ(90deg) scale(1);
opacity: 0;
}
}
@keyframes fall-2 {
0% {
transform: translate(0px, 0px) rotateX(-20deg) rotateY(10deg) scale(0.25);
opacity: 0;
}
10% {
transform: translate(-10px, -5px) rotateX(-20deg) rotateY(10deg) scale(1);
opacity: 1;
}
100% {
transform: translate(-10px, 160px) rotateX(-90deg) rotateY(45deg) scale(0.25);
opacity: 0;
}
}
@keyframes fall-3 {
0% {
transform: translate(0px, 0px) rotateX(0deg) rotateY(45deg) scale(0.5);
opacity: 0;
}
15% {
transform: translate(7px, 5px) rotateX(0deg) rotateY(45deg) scale(1);
opacity: 1;
}
100% {
transform: translate(20px, 120px) rotateX(-180deg) rotateY(-90deg) scale(0.5);
opacity: 0;
}
}
`}</style>
<Component
{...props}
onHoverStart={() => setIsHover(true)}
onHoverEnd={() => setIsHover(false)}
/>
</>
)
}
}
import { ComponentType, useEffect, useState } from "react"
import { motion, useSpring, useMotionValue } from "framer-motion"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faStar } from "@fortawesome/free-solid-svg-icons"
import ReactDOM from "react-dom"
export function withCursor(Component): ComponentType {
return (props) => {
const originPosition = { x: 0, y: 0 }
const [isHover, setIsHover] = useState(false)
const [count, setCount] = useState(0)
const [last, setLast] = useState({
starTimestamp: 0,
starPosition: originPosition,
mousePosition: originPosition,
})
const config = {
starAnimationDuration: 1500,
minimumTimeBetweenStars: 250,
minimumDistanceBetweenStars: 75,
glowDuration: 75,
maximumGlowPointSpacing: 10,
colors: ["144 211 252", "252 254 255"],
sizes: ["1.4rem", "1rem", "0.6rem"],
animations: ["fall-1", "fall-2", "fall-3"],
}
const rand = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min
const selectRandom = (items) => items[rand(0, items.length - 1)]
const withUnit = (value, unit) => `${value}${unit}`
const px = (value) => withUnit(value, "px")
const ms = (value) => withUnit(value, "ms")
const calcDistance = (a, b) => {
const diffX = b.x - a.x,
diffY = b.y - a.y
return Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2))
}
const calcElapsedTime = (start, end) => end - start
const createStar = (position) => {
const star = document.createElement("span")
const color = selectRandom(config.colors)
star.className = "star"
// Use FontAwesomeIcon to render the star icon
const iconComponent = <FontAwesomeIcon icon={faStar} />
ReactDOM.render(iconComponent, star)
star.style.left = px(position.x)
star.style.top = px(position.y)
star.style.fontSize = selectRandom(config.sizes)
star.style.color = `rgb(${color})`
star.style.textShadow = `0px 0px 1.5rem rgb(${color} / 0.5)`
star.style.animationName = config.animations[count % 3]
setCount((prevCount) => prevCount + 1)
star.style.animationDuration = ms(config.starAnimationDuration)
document.body.appendChild(star)
setTimeout(
() => document.body.removeChild(star),
config.starAnimationDuration
)
}
const createGlowPoint = (position) => {
const glow = document.createElement("div")
glow.className = "glow-point"
glow.style.left = px(position.x)
glow.style.top = px(position.y)
document.body.appendChild(glow)
setTimeout(
() => document.body.removeChild(glow),
config.glowDuration
)
}
const determinePointQuantity = (distance) =>
Math.max(Math.floor(distance / config.maximumGlowPointSpacing), 1)
const createGlow = (lastPoint, currentPoint) => {
const distance = calcDistance(lastPoint, currentPoint)
const quantity = determinePointQuantity(distance)
const dx = (currentPoint.x - lastPoint.x) / quantity
const dy = (currentPoint.y - lastPoint.y) / quantity
Array.from(Array(quantity)).forEach((_, index) => {
const x = lastPoint.x + dx * index,
y = lastPoint.y + dy * index
createGlowPoint({ x, y })
})
}
const updateLastStar = (position) => {
setLast((prevLast) => ({
...prevLast,
starTimestamp: new Date().getTime(),
starPosition: position,
}))
}
const updateLastMousePosition = (position) => {
setLast((prevLast) => ({
...prevLast,
mousePosition: position,
}))
}
const adjustLastMousePosition = (position) => {
if (last.mousePosition.x === 0 && last.mousePosition.y === 0) {
setLast((prevLast) => ({
...prevLast,
mousePosition: position,
}))
}
}
const handleOnMove = (e) => {
const mousePosition = {
x: e.clientX + window.scrollX,
y: e.clientY + window.scrollY,
}
adjustLastMousePosition(mousePosition)
const now = new Date().getTime()
const hasMovedFarEnough =
calcDistance(last.starPosition, mousePosition) >=
config.minimumDistanceBetweenStars
const hasBeenLongEnough =
calcElapsedTime(last.starTimestamp, now) >
config.minimumTimeBetweenStars
if (isHover) {
if (hasMovedFarEnough || hasBeenLongEnough) {
createStar(mousePosition)
updateLastStar(mousePosition)
}
createGlow(last.mousePosition, mousePosition)
}
updateLastMousePosition(mousePosition)
}
useEffect(() => {
window.addEventListener("mousemove", handleOnMove)
window.addEventListener("touchmove", (e) =>
handleOnMove(e.touches[0])
)
document.body.addEventListener("mouseleave", () =>
updateLastMousePosition(originPosition)
)
return () => {
window.removeEventListener("mousemove", handleOnMove)
window.removeEventListener("touchmove", (e) =>
handleOnMove(e.touches[0])
)
document.body.removeEventListener("mouseleave", () =>
updateLastMousePosition(originPosition)
)
}
}, [last])
return (
<>
<style>{`
.glow-point {
position: absolute;
box-shadow: 0rem 0rem 1.2rem 0.6rem rgb(102, 242, 255, 0.85);
pointer-events: none;
}
.star {
position: absolute;
z-index: 2;
color: white;
font-size: 1rem;
animation-duration: 1500ms;
animation-fill-mode: forwards;
pointer-events: none;
}
@keyframes fall-1 {
0% {
transform: translate(0px, 0px) rotateX(45deg) rotateY(30deg) rotateZ(0deg) scale(0.25);
opacity: 0;
}
5% {
transform: translate(10px, -10px) rotateX(45deg) rotateY(30deg) rotateZ(0deg) scale(1);
opacity: 1;
}
100% {
transform: translate(25px, 200px) rotateX(180deg) rotateY(270deg) rotateZ(90deg) scale(1);
opacity: 0;
}
}
@keyframes fall-2 {
0% {
transform: translate(0px, 0px) rotateX(-20deg) rotateY(10deg) scale(0.25);
opacity: 0;
}
10% {
transform: translate(-10px, -5px) rotateX(-20deg) rotateY(10deg) scale(1);
opacity: 1;
}
100% {
transform: translate(-10px, 160px) rotateX(-90deg) rotateY(45deg) scale(0.25);
opacity: 0;
}
}
@keyframes fall-3 {
0% {
transform: translate(0px, 0px) rotateX(0deg) rotateY(45deg) scale(0.5);
opacity: 0;
}
15% {
transform: translate(7px, 5px) rotateX(0deg) rotateY(45deg) scale(1);
opacity: 1;
}
100% {
transform: translate(20px, 120px) rotateX(-180deg) rotateY(-90deg) scale(0.5);
opacity: 0;
}
}
`}</style>
<Component
{...props}
onHoverStart={() => setIsHover(true)}
onHoverEnd={() => setIsHover(false)}
/>
</>
)
}
}
import { ComponentType, useEffect, useState } from "react"
import { motion, useSpring, useMotionValue } from "framer-motion"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faStar } from "@fortawesome/free-solid-svg-icons"
import ReactDOM from "react-dom"
export function withCursor(Component): ComponentType {
return (props) => {
const originPosition = { x: 0, y: 0 }
const [isHover, setIsHover] = useState(false)
const [count, setCount] = useState(0)
const [last, setLast] = useState({
starTimestamp: 0,
starPosition: originPosition,
mousePosition: originPosition,
})
const config = {
starAnimationDuration: 1500,
minimumTimeBetweenStars: 250,
minimumDistanceBetweenStars: 75,
glowDuration: 75,
maximumGlowPointSpacing: 10,
colors: ["144 211 252", "252 254 255"],
sizes: ["1.4rem", "1rem", "0.6rem"],
animations: ["fall-1", "fall-2", "fall-3"],
}
const rand = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min
const selectRandom = (items) => items[rand(0, items.length - 1)]
const withUnit = (value, unit) => `${value}${unit}`
const px = (value) => withUnit(value, "px")
const ms = (value) => withUnit(value, "ms")
const calcDistance = (a, b) => {
const diffX = b.x - a.x,
diffY = b.y - a.y
return Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2))
}
const calcElapsedTime = (start, end) => end - start
const createStar = (position) => {
const star = document.createElement("span")
const color = selectRandom(config.colors)
star.className = "star"
// Use FontAwesomeIcon to render the star icon
const iconComponent = <FontAwesomeIcon icon={faStar} />
ReactDOM.render(iconComponent, star)
star.style.left = px(position.x)
star.style.top = px(position.y)
star.style.fontSize = selectRandom(config.sizes)
star.style.color = `rgb(${color})`
star.style.textShadow = `0px 0px 1.5rem rgb(${color} / 0.5)`
star.style.animationName = config.animations[count % 3]
setCount((prevCount) => prevCount + 1)
star.style.animationDuration = ms(config.starAnimationDuration)
document.body.appendChild(star)
setTimeout(
() => document.body.removeChild(star),
config.starAnimationDuration
)
}
const createGlowPoint = (position) => {
const glow = document.createElement("div")
glow.className = "glow-point"
glow.style.left = px(position.x)
glow.style.top = px(position.y)
document.body.appendChild(glow)
setTimeout(
() => document.body.removeChild(glow),
config.glowDuration
)
}
const determinePointQuantity = (distance) =>
Math.max(Math.floor(distance / config.maximumGlowPointSpacing), 1)
const createGlow = (lastPoint, currentPoint) => {
const distance = calcDistance(lastPoint, currentPoint)
const quantity = determinePointQuantity(distance)
const dx = (currentPoint.x - lastPoint.x) / quantity
const dy = (currentPoint.y - lastPoint.y) / quantity
Array.from(Array(quantity)).forEach((_, index) => {
const x = lastPoint.x + dx * index,
y = lastPoint.y + dy * index
createGlowPoint({ x, y })
})
}
const updateLastStar = (position) => {
setLast((prevLast) => ({
...prevLast,
starTimestamp: new Date().getTime(),
starPosition: position,
}))
}
const updateLastMousePosition = (position) => {
setLast((prevLast) => ({
...prevLast,
mousePosition: position,
}))
}
const adjustLastMousePosition = (position) => {
if (last.mousePosition.x === 0 && last.mousePosition.y === 0) {
setLast((prevLast) => ({
...prevLast,
mousePosition: position,
}))
}
}
const handleOnMove = (e) => {
const mousePosition = {
x: e.clientX + window.scrollX,
y: e.clientY + window.scrollY,
}
adjustLastMousePosition(mousePosition)
const now = new Date().getTime()
const hasMovedFarEnough =
calcDistance(last.starPosition, mousePosition) >=
config.minimumDistanceBetweenStars
const hasBeenLongEnough =
calcElapsedTime(last.starTimestamp, now) >
config.minimumTimeBetweenStars
if (isHover) {
if (hasMovedFarEnough || hasBeenLongEnough) {
createStar(mousePosition)
updateLastStar(mousePosition)
}
createGlow(last.mousePosition, mousePosition)
}
updateLastMousePosition(mousePosition)
}
useEffect(() => {
window.addEventListener("mousemove", handleOnMove)
window.addEventListener("touchmove", (e) =>
handleOnMove(e.touches[0])
)
document.body.addEventListener("mouseleave", () =>
updateLastMousePosition(originPosition)
)
return () => {
window.removeEventListener("mousemove", handleOnMove)
window.removeEventListener("touchmove", (e) =>
handleOnMove(e.touches[0])
)
document.body.removeEventListener("mouseleave", () =>
updateLastMousePosition(originPosition)
)
}
}, [last])
return (
<>
<style>{`
.glow-point {
position: absolute;
box-shadow: 0rem 0rem 1.2rem 0.6rem rgb(102, 242, 255, 0.85);
pointer-events: none;
}
.star {
position: absolute;
z-index: 2;
color: white;
font-size: 1rem;
animation-duration: 1500ms;
animation-fill-mode: forwards;
pointer-events: none;
}
@keyframes fall-1 {
0% {
transform: translate(0px, 0px) rotateX(45deg) rotateY(30deg) rotateZ(0deg) scale(0.25);
opacity: 0;
}
5% {
transform: translate(10px, -10px) rotateX(45deg) rotateY(30deg) rotateZ(0deg) scale(1);
opacity: 1;
}
100% {
transform: translate(25px, 200px) rotateX(180deg) rotateY(270deg) rotateZ(90deg) scale(1);
opacity: 0;
}
}
@keyframes fall-2 {
0% {
transform: translate(0px, 0px) rotateX(-20deg) rotateY(10deg) scale(0.25);
opacity: 0;
}
10% {
transform: translate(-10px, -5px) rotateX(-20deg) rotateY(10deg) scale(1);
opacity: 1;
}
100% {
transform: translate(-10px, 160px) rotateX(-90deg) rotateY(45deg) scale(0.25);
opacity: 0;
}
}
@keyframes fall-3 {
0% {
transform: translate(0px, 0px) rotateX(0deg) rotateY(45deg) scale(0.5);
opacity: 0;
}
15% {
transform: translate(7px, 5px) rotateX(0deg) rotateY(45deg) scale(1);
opacity: 1;
}
100% {
transform: translate(20px, 120px) rotateX(-180deg) rotateY(-90deg) scale(0.5);
opacity: 0;
}
}
`}</style>
<Component
{...props}
onHoverStart={() => setIsHover(true)}
onHoverEnd={() => setIsHover(false)}
/>
</>
)
}
}