Skip to content

Commit 7c2bc79

Browse files
authored
Merge pull request #1 from Code-Parth/update/smooth-cursor
Update/smooth cursor
2 parents bcf9c0b + 6ef28a5 commit 7c2bc79

File tree

11 files changed

+50
-403
lines changed

11 files changed

+50
-403
lines changed

apps/www/content/docs/components/smooth-cursor.mdx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ npx shadcn@latest add @magicui/smooth-cursor
3434

3535
<TabsContent value="manual">
3636

37+
```bash
38+
npx add smooth-cursor framer-motion
39+
```
40+
3741
<Steps>
3842

3943
<Step>Copy and paste the following code into your project.</Step>
@@ -102,10 +106,10 @@ select {
102106

103107
## Props
104108

105-
| Prop | Type | Default | Description |
106-
| -------------- | ----------------- | ---------------------- | ------------------------------------------------------ |
107-
| `cursor` | `React.ReactNode` | `<DefaultCursorSVG />` | Custom cursor component to replace the default cursor |
108-
| `springConfig` | `SpringConfig` | See below | Configuration object for the spring animation behavior |
109+
| Prop | Type | Default | Description |
110+
| -------------- | -------------- | ---------------------- | ------------------------------------------------------ |
111+
| `cursor` | `JSX.Element`. | `<DefaultCursorSVG />` | Custom cursor component to replace the default cursor |
112+
| `springConfig` | `SpringConfig` | See below | Configuration object for the spring animation behavior |
109113

110114
### SpringConfig Type
111115

apps/www/next.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const nextConfig = {
1616
"images.unsplash.com",
1717
"img.youtube.com",
1818
"pbs.twimg.com",
19+
"cdn.outrank.so",
1920
],
2021
},
2122
async redirects() {

apps/www/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"react-tweet": "^3.2.2",
6666
"rough-notation": "^0.5.1",
6767
"schema-dts": "^1.1.5",
68+
"smooth-cursor": "^0.1.2",
6869
"sonner": "^1.7.4",
6970
"svg-dotted-map": "^2.0.1",
7071
"tailwind-merge": "^3.3.1",

apps/www/public/llms-full.txt

Lines changed: 5 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -13225,16 +13225,11 @@ Description: A customizable, physics-based smooth cursor animation component wit
1322513225
--- file: magicui/smooth-cursor.tsx ---
1322613226
"use client"
1322713227

13228-
import { FC, useEffect, useRef, useState } from "react"
13229-
import { motion, useSpring } from "motion/react"
13230-
13231-
interface Position {
13232-
x: number
13233-
y: number
13234-
}
13228+
import { JSX } from "react"
13229+
import { SmoothCursor as SmoothCursorComponent } from "smooth-cursor"
1323513230

1323613231
export interface SmoothCursorProps {
13237-
cursor?: React.ReactNode
13232+
cursor?: JSX.Element
1323813233
springConfig?: {
1323913234
damping: number
1324013235
stiffness: number
@@ -13243,193 +13238,8 @@ export interface SmoothCursorProps {
1324313238
}
1324413239
}
1324513240

13246-
const DefaultCursorSVG: FC = () => {
13247-
return (
13248-
<svg
13249-
xmlns="http://www.w3.org/2000/svg"
13250-
width={50}
13251-
height={54}
13252-
viewBox="0 0 50 54"
13253-
fill="none"
13254-
style={{ scale: 0.5 }}
13255-
>
13256-
<g filter="url(#filter0_d_91_7928)">
13257-
<path
13258-
d="M42.6817 41.1495L27.5103 6.79925C26.7269 5.02557 24.2082 5.02558 23.3927 6.79925L7.59814 41.1495C6.75833 42.9759 8.52712 44.8902 10.4125 44.1954L24.3757 39.0496C24.8829 38.8627 25.4385 38.8627 25.9422 39.0496L39.8121 44.1954C41.6849 44.8902 43.4884 42.9759 42.6817 41.1495Z"
13259-
fill="black"
13260-
/>
13261-
<path
13262-
d="M43.7146 40.6933L28.5431 6.34306C27.3556 3.65428 23.5772 3.69516 22.3668 6.32755L6.57226 40.6778C5.3134 43.4156 7.97238 46.298 10.803 45.2549L24.7662 40.109C25.0221 40.0147 25.2999 40.0156 25.5494 40.1082L39.4193 45.254C42.2261 46.2953 44.9254 43.4347 43.7146 40.6933Z"
13263-
stroke="white"
13264-
strokeWidth={2.25825}
13265-
/>
13266-
</g>
13267-
<defs>
13268-
<filter
13269-
id="filter0_d_91_7928"
13270-
x={0.602397}
13271-
y={0.952444}
13272-
width={49.0584}
13273-
height={52.428}
13274-
filterUnits="userSpaceOnUse"
13275-
colorInterpolationFilters="sRGB"
13276-
>
13277-
<feFlood floodOpacity={0} result="BackgroundImageFix" />
13278-
<feColorMatrix
13279-
in="SourceAlpha"
13280-
type="matrix"
13281-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
13282-
result="hardAlpha"
13283-
/>
13284-
<feOffset dy={2.25825} />
13285-
<feGaussianBlur stdDeviation={2.25825} />
13286-
<feComposite in2="hardAlpha" operator="out" />
13287-
<feColorMatrix
13288-
type="matrix"
13289-
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"
13290-
/>
13291-
<feBlend
13292-
mode="normal"
13293-
in2="BackgroundImageFix"
13294-
result="effect1_dropShadow_91_7928"
13295-
/>
13296-
<feBlend
13297-
mode="normal"
13298-
in="SourceGraphic"
13299-
in2="effect1_dropShadow_91_7928"
13300-
result="shape"
13301-
/>
13302-
</filter>
13303-
</defs>
13304-
</svg>
13305-
)
13306-
}
13307-
13308-
export function SmoothCursor({
13309-
cursor = <DefaultCursorSVG />,
13310-
springConfig = {
13311-
damping: 45,
13312-
stiffness: 400,
13313-
mass: 1,
13314-
restDelta: 0.001,
13315-
},
13316-
}: SmoothCursorProps) {
13317-
const [isMoving, setIsMoving] = useState(false)
13318-
const lastMousePos = useRef<Position>({ x: 0, y: 0 })
13319-
const velocity = useRef<Position>({ x: 0, y: 0 })
13320-
const lastUpdateTime = useRef(Date.now())
13321-
const previousAngle = useRef(0)
13322-
const accumulatedRotation = useRef(0)
13323-
13324-
const cursorX = useSpring(0, springConfig)
13325-
const cursorY = useSpring(0, springConfig)
13326-
const rotation = useSpring(0, {
13327-
...springConfig,
13328-
damping: 60,
13329-
stiffness: 300,
13330-
})
13331-
const scale = useSpring(1, {
13332-
...springConfig,
13333-
stiffness: 500,
13334-
damping: 35,
13335-
})
13336-
13337-
useEffect(() => {
13338-
const updateVelocity = (currentPos: Position) => {
13339-
const currentTime = Date.now()
13340-
const deltaTime = currentTime - lastUpdateTime.current
13341-
13342-
if (deltaTime > 0) {
13343-
velocity.current = {
13344-
x: (currentPos.x - lastMousePos.current.x) / deltaTime,
13345-
y: (currentPos.y - lastMousePos.current.y) / deltaTime,
13346-
}
13347-
}
13348-
13349-
lastUpdateTime.current = currentTime
13350-
lastMousePos.current = currentPos
13351-
}
13352-
13353-
const smoothMouseMove = (e: MouseEvent) => {
13354-
const currentPos = { x: e.clientX, y: e.clientY }
13355-
updateVelocity(currentPos)
13356-
13357-
const speed = Math.sqrt(
13358-
Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2)
13359-
)
13360-
13361-
cursorX.set(currentPos.x)
13362-
cursorY.set(currentPos.y)
13363-
13364-
if (speed > 0.1) {
13365-
const currentAngle =
13366-
Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) +
13367-
90
13368-
13369-
let angleDiff = currentAngle - previousAngle.current
13370-
if (angleDiff > 180) angleDiff -= 360
13371-
if (angleDiff < -180) angleDiff += 360
13372-
accumulatedRotation.current += angleDiff
13373-
rotation.set(accumulatedRotation.current)
13374-
previousAngle.current = currentAngle
13375-
13376-
scale.set(0.95)
13377-
setIsMoving(true)
13378-
13379-
const timeout = setTimeout(() => {
13380-
scale.set(1)
13381-
setIsMoving(false)
13382-
}, 150)
13383-
13384-
return () => clearTimeout(timeout)
13385-
}
13386-
}
13387-
13388-
let rafId: number
13389-
const throttledMouseMove = (e: MouseEvent) => {
13390-
if (rafId) return
13391-
13392-
rafId = requestAnimationFrame(() => {
13393-
smoothMouseMove(e)
13394-
rafId = 0
13395-
})
13396-
}
13397-
13398-
document.body.style.cursor = "none"
13399-
window.addEventListener("mousemove", throttledMouseMove)
13400-
13401-
return () => {
13402-
window.removeEventListener("mousemove", throttledMouseMove)
13403-
document.body.style.cursor = "auto"
13404-
if (rafId) cancelAnimationFrame(rafId)
13405-
}
13406-
}, [cursorX, cursorY, rotation, scale])
13407-
13408-
return (
13409-
<motion.div
13410-
style={{
13411-
position: "fixed",
13412-
left: cursorX,
13413-
top: cursorY,
13414-
translateX: "-50%",
13415-
translateY: "-50%",
13416-
rotate: rotation,
13417-
scale: scale,
13418-
zIndex: 100,
13419-
pointerEvents: "none",
13420-
willChange: "transform",
13421-
}}
13422-
initial={{ scale: 0 }}
13423-
animate={{ scale: 1 }}
13424-
transition={{
13425-
type: "spring",
13426-
stiffness: 400,
13427-
damping: 30,
13428-
}}
13429-
>
13430-
{cursor}
13431-
</motion.div>
13432-
)
13241+
export function SmoothCursor({ cursor, springConfig }: SmoothCursorProps) {
13242+
return <SmoothCursorComponent cursor={cursor} springConfig={springConfig} />
1343313243
}
1343413244

1343513245

apps/www/public/r/registry.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@
193193
"type": "registry:ui",
194194
"description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects",
195195
"dependencies": [
196-
"framer-motion"
196+
"framer-motion",
197+
"smooth-cursor"
197198
],
198199
"files": [
199200
{

apps/www/public/r/smooth-cursor.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
"type": "registry:ui",
55
"description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects",
66
"dependencies": [
7-
"framer-motion"
7+
"framer-motion",
8+
"smooth-cursor"
89
],
910
"files": [
1011
{
1112
"path": "registry/magicui/smooth-cursor.tsx",
12-
"content": "\"use client\"\n\nimport { FC, useEffect, useRef, useState } from \"react\"\nimport { motion, useSpring } from \"motion/react\"\n\ninterface Position {\n x: number\n y: number\n}\n\nexport interface SmoothCursorProps {\n cursor?: React.ReactNode\n springConfig?: {\n damping: number\n stiffness: number\n mass: number\n restDelta: number\n }\n}\n\nconst DefaultCursorSVG: FC = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={50}\n height={54}\n viewBox=\"0 0 50 54\"\n fill=\"none\"\n style={{ scale: 0.5 }}\n >\n <g filter=\"url(#filter0_d_91_7928)\">\n <path\n d=\"M42.6817 41.1495L27.5103 6.79925C26.7269 5.02557 24.2082 5.02558 23.3927 6.79925L7.59814 41.1495C6.75833 42.9759 8.52712 44.8902 10.4125 44.1954L24.3757 39.0496C24.8829 38.8627 25.4385 38.8627 25.9422 39.0496L39.8121 44.1954C41.6849 44.8902 43.4884 42.9759 42.6817 41.1495Z\"\n fill=\"black\"\n />\n <path\n d=\"M43.7146 40.6933L28.5431 6.34306C27.3556 3.65428 23.5772 3.69516 22.3668 6.32755L6.57226 40.6778C5.3134 43.4156 7.97238 46.298 10.803 45.2549L24.7662 40.109C25.0221 40.0147 25.2999 40.0156 25.5494 40.1082L39.4193 45.254C42.2261 46.2953 44.9254 43.4347 43.7146 40.6933Z\"\n stroke=\"white\"\n strokeWidth={2.25825}\n />\n </g>\n <defs>\n <filter\n id=\"filter0_d_91_7928\"\n x={0.602397}\n y={0.952444}\n width={49.0584}\n height={52.428}\n filterUnits=\"userSpaceOnUse\"\n colorInterpolationFilters=\"sRGB\"\n >\n <feFlood floodOpacity={0} result=\"BackgroundImageFix\" />\n <feColorMatrix\n in=\"SourceAlpha\"\n type=\"matrix\"\n values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\"\n result=\"hardAlpha\"\n />\n <feOffset dy={2.25825} />\n <feGaussianBlur stdDeviation={2.25825} />\n <feComposite in2=\"hardAlpha\" operator=\"out\" />\n <feColorMatrix\n type=\"matrix\"\n values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0\"\n />\n <feBlend\n mode=\"normal\"\n in2=\"BackgroundImageFix\"\n result=\"effect1_dropShadow_91_7928\"\n />\n <feBlend\n mode=\"normal\"\n in=\"SourceGraphic\"\n in2=\"effect1_dropShadow_91_7928\"\n result=\"shape\"\n />\n </filter>\n </defs>\n </svg>\n )\n}\n\nexport function SmoothCursor({\n cursor = <DefaultCursorSVG />,\n springConfig = {\n damping: 45,\n stiffness: 400,\n mass: 1,\n restDelta: 0.001,\n },\n}: SmoothCursorProps) {\n const [isMoving, setIsMoving] = useState(false)\n const lastMousePos = useRef<Position>({ x: 0, y: 0 })\n const velocity = useRef<Position>({ x: 0, y: 0 })\n const lastUpdateTime = useRef(Date.now())\n const previousAngle = useRef(0)\n const accumulatedRotation = useRef(0)\n\n const cursorX = useSpring(0, springConfig)\n const cursorY = useSpring(0, springConfig)\n const rotation = useSpring(0, {\n ...springConfig,\n damping: 60,\n stiffness: 300,\n })\n const scale = useSpring(1, {\n ...springConfig,\n stiffness: 500,\n damping: 35,\n })\n\n useEffect(() => {\n const updateVelocity = (currentPos: Position) => {\n const currentTime = Date.now()\n const deltaTime = currentTime - lastUpdateTime.current\n\n if (deltaTime > 0) {\n velocity.current = {\n x: (currentPos.x - lastMousePos.current.x) / deltaTime,\n y: (currentPos.y - lastMousePos.current.y) / deltaTime,\n }\n }\n\n lastUpdateTime.current = currentTime\n lastMousePos.current = currentPos\n }\n\n const smoothMouseMove = (e: MouseEvent) => {\n const currentPos = { x: e.clientX, y: e.clientY }\n updateVelocity(currentPos)\n\n const speed = Math.sqrt(\n Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2)\n )\n\n cursorX.set(currentPos.x)\n cursorY.set(currentPos.y)\n\n if (speed > 0.1) {\n const currentAngle =\n Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) +\n 90\n\n let angleDiff = currentAngle - previousAngle.current\n if (angleDiff > 180) angleDiff -= 360\n if (angleDiff < -180) angleDiff += 360\n accumulatedRotation.current += angleDiff\n rotation.set(accumulatedRotation.current)\n previousAngle.current = currentAngle\n\n scale.set(0.95)\n setIsMoving(true)\n\n const timeout = setTimeout(() => {\n scale.set(1)\n setIsMoving(false)\n }, 150)\n\n return () => clearTimeout(timeout)\n }\n }\n\n let rafId: number\n const throttledMouseMove = (e: MouseEvent) => {\n if (rafId) return\n\n rafId = requestAnimationFrame(() => {\n smoothMouseMove(e)\n rafId = 0\n })\n }\n\n document.body.style.cursor = \"none\"\n window.addEventListener(\"mousemove\", throttledMouseMove)\n\n return () => {\n window.removeEventListener(\"mousemove\", throttledMouseMove)\n document.body.style.cursor = \"auto\"\n if (rafId) cancelAnimationFrame(rafId)\n }\n }, [cursorX, cursorY, rotation, scale])\n\n return (\n <motion.div\n style={{\n position: \"fixed\",\n left: cursorX,\n top: cursorY,\n translateX: \"-50%\",\n translateY: \"-50%\",\n rotate: rotation,\n scale: scale,\n zIndex: 100,\n pointerEvents: \"none\",\n willChange: \"transform\",\n }}\n initial={{ scale: 0 }}\n animate={{ scale: 1 }}\n transition={{\n type: \"spring\",\n stiffness: 400,\n damping: 30,\n }}\n >\n {cursor}\n </motion.div>\n )\n}\n",
13+
"content": "\"use client\"\n\nimport { JSX } from \"react\"\nimport { SmoothCursor as SmoothCursorComponent } from \"smooth-cursor\"\n\nexport interface SmoothCursorProps {\n cursor?: JSX.Element\n springConfig?: {\n damping: number\n stiffness: number\n mass: number\n restDelta: number\n }\n}\n\nexport function SmoothCursor({ cursor, springConfig }: SmoothCursorProps) {\n return <SmoothCursorComponent cursor={cursor} springConfig={springConfig} />\n}\n",
1314
"type": "registry:ui"
1415
}
1516
]

apps/www/public/registry.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@
193193
"type": "registry:ui",
194194
"description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects",
195195
"dependencies": [
196-
"framer-motion"
196+
"framer-motion",
197+
"smooth-cursor"
197198
],
198199
"files": [
199200
{

apps/www/registry.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@
193193
"type": "registry:ui",
194194
"description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects",
195195
"dependencies": [
196-
"framer-motion"
196+
"framer-motion",
197+
"smooth-cursor"
197198
],
198199
"files": [
199200
{

0 commit comments

Comments
 (0)