Skip to content

Commit 46fbf99

Browse files
authored
Merge pull request #47 from codeXsit/codex-25-pageTransition
feat: add page transition and mouse interaction
2 parents c60cbdf + fb80772 commit 46fbf99

File tree

15 files changed

+227
-45
lines changed

15 files changed

+227
-45
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
dist/

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ module.exports = {
3131
{ vars: "all", args: "after-used", ignoreRestSiblings: false },
3232
],
3333
"react/jsx-pascal-case": ["error", { allowAllCaps: true }],
34+
"jsx-a11y/mouse-events-have-key-events": "off",
3435
"no-console": "error",
3536
"import/no-unresolved": "off",
3637
"import/extensions": "off",

index.html

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
<!doctype html>
22
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>CodeX</title>
7+
</head>
38

4-
<head>
5-
<meta charset="UTF-8" />
6-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
7-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8-
<title>CodeX</title>
9-
</head>
10-
11-
<body>
12-
<div id="root"></div>
13-
<script type="module" src="/src/main.jsx"></script>
14-
</body>
15-
16-
</html>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.jsx"></script>
12+
</body>
13+
</html>

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
"@fontsource/league-gothic": "^5.0.18",
1919
"@fontsource/poppins": "^5.0.8",
2020
"@vitejs/plugin-react": "^4.2.1",
21+
"lodash": "^4.17.21",
2122
"prop-types": "^15.8.1",
2223
"react": "^18.2.0",
2324
"react-dom": "^18.2.0",
2425
"react-router-dom": "^6.22.1",
2526
"react-toastify": "^10.0.4",
26-
"vite": "^5.1.0"
27+
"vite": "^5.1.0",
28+
"framer-motion": "^11.0.20"
2729
},
2830
"devDependencies": {
2931
"@commitlint/cli": "^18.6.1",

src/App.jsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
1+
import { AnimatePresence } from "framer-motion";
12
import { HashRouter as Router, Routes, Route } from "react-router-dom";
23
import { ToastContainer } from "react-toastify";
34
import routes from "@/routes/index";
45
import Navbar from "@/components/Navbar/index";
6+
import Cursor from "./components/Cursor";
7+
import CursorVariantProvider from "@/context/CursorVariantProvider";
58

69
const navLinks = [
710
{ name: "About Us", path: "/about-us" },
811
{ name: "Our Team", path: "/teams" },
12+
{ name: "Events", path: "/events" },
913
{ name: "Gallery", path: "/gallery" },
1014
{ name: "Contact", path: "/contact" },
1115
{ name: "Community", path: "/community" },
1216
];
1317

1418
function App() {
1519
return (
16-
<Router>
17-
<Navbar links={navLinks} />
20+
<CursorVariantProvider>
21+
<AnimatePresence>
22+
<Router>
23+
<Navbar links={navLinks} />
24+
<Cursor />
1825

19-
<ToastContainer />
20-
<Routes>
21-
{routes.map((route) => (
22-
<Route path={route.path} element={route.render} key={route.lable} />
23-
))}
24-
</Routes>
25-
</Router>
26+
<ToastContainer />
27+
<Routes>
28+
{routes.map((route) => (
29+
<Route
30+
path={route.path}
31+
element={route.render}
32+
key={route.label}
33+
/>
34+
))}
35+
</Routes>
36+
</Router>
37+
</AnimatePresence>
38+
</CursorVariantProvider>
2639
);
2740
}
2841

src/components/Cursor/index.jsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { useState, useEffect, useRef, useContext } from "react";
2+
import { debounce } from "lodash";
3+
import { motion } from "framer-motion";
4+
import { CursorVariantContext } from "@/context/CursorVariantProvider";
5+
6+
function Cursor() {
7+
const { cursorVariant } = useContext(CursorVariantContext);
8+
9+
const [mousePosition, setMousePosition] = useState({
10+
x: 0,
11+
y: 0,
12+
});
13+
14+
const cursorRef = useRef(null);
15+
16+
const debouncedMouseMove = debounce((e) => {
17+
setMousePosition({
18+
x: e.clientX,
19+
y: e.clientY,
20+
});
21+
}, 4);
22+
23+
function mouseMove(e) {
24+
debouncedMouseMove(e);
25+
}
26+
27+
useEffect(() => {
28+
if (cursorRef.current) {
29+
// cursorRef.current.style.left = `${mousePosition.x}px`;
30+
// cursorRef.current.style.top = `${mousePosition.y}px`;
31+
cursorRef.current.animate(
32+
{
33+
left: `${mousePosition.x}px`,
34+
top: `${mousePosition.y}px`,
35+
},
36+
{ duration: 400, fill: "forwards" },
37+
);
38+
}
39+
window.addEventListener("mousemove", mouseMove);
40+
41+
return () => {
42+
window.removeEventListener("mousemove", mouseMove);
43+
};
44+
}, [mousePosition]);
45+
46+
const variants = {
47+
default: {
48+
mixBlendMode: "normal",
49+
backgroundColor: "#ffffff",
50+
},
51+
text: {
52+
height: 150,
53+
width: 150,
54+
backgroundColor: "#E76941",
55+
mixBlendMode: "difference",
56+
},
57+
none: {
58+
height: 0,
59+
width: 0,
60+
},
61+
};
62+
63+
return (
64+
<motion.div
65+
ref={cursorRef}
66+
className="h-6 w-6 rounded-full border-2 border-background-dark fixed top-0 left-0 -translate-x-1/2 -translate-y-1/2 pointer-events-none z-50"
67+
variants={variants}
68+
animate={cursorVariant}
69+
/>
70+
);
71+
}
72+
73+
export default Cursor;

src/components/Heading/index.jsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1+
import { useContext } from "react";
12
import PropTypes from "prop-types";
3+
import { CursorVariantContext } from "../../context/CursorVariantProvider";
24

35
function Heading({ text, className, bgTextStyle, frontTextStyle }) {
6+
const { setCursorVariantText, setCursorVariantDefault } =
7+
useContext(CursorVariantContext);
8+
49
return (
510
<div
611
className={`relative text-center font-black uppercase tracking-tighter ${className}`}
712
>
813
<div
14+
onMouseOver={setCursorVariantText}
15+
onMouseOut={setCursorVariantDefault}
916
className={`${bgTextStyle} xs:text-4xl sm:text-6xl lg:text-8xl text-outlined text-transparent`}
1017
>
1118
{text}
1219
</div>
1320
<div
21+
onMouseOver={setCursorVariantText}
22+
onMouseOut={setCursorVariantDefault}
1423
className={`${frontTextStyle} xs:text-2xl sm:text-4xl lg:text-6xl absolute w-full leading-none bottom-[-20%]`}
1524
>
1625
{text}

src/components/Navbar/index.jsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
1-
import { useState } from "react";
1+
import { useState, useContext } from "react";
22
import { Link, useLocation } from "react-router-dom";
33
import PropTypes from "prop-types";
44
import Logo from "@/assets/images/logo.svg";
55
import closeIcon from "@/assets/images/close.svg";
66
import menuIcon from "@/assets/images/menu.svg";
7+
import { CursorVariantContext } from "@/context/CursorVariantProvider";
78

89
function Navbar({ links }) {
910
const [isOpen, setIsOpen] = useState(false);
1011
const location = useLocation();
1112

13+
const { setCursorVariantNone, setCursorVariantDefault } =
14+
useContext(CursorVariantContext);
15+
1216
return (
1317
<nav
1418
className={`shadow-md w-full flex xs:flex-col md:flex-row ${isOpen ? "xs:h-screen" : ""} md:h-full justify-between items-center px-6 bg-secondary-dark`}
1519
>
1620
<div className="flex flex-row justify-between xs:w-full md:w-auto items-center">
17-
<Link to="/" className="cursor-pointer">
21+
<Link
22+
to="/"
23+
className="cursor-pointer"
24+
onMouseEnter={setCursorVariantNone}
25+
onMouseLeave={setCursorVariantDefault}
26+
>
1827
<img src={Logo} className="w-[10rem]" alt="logo" />
1928
</Link>
2029
<button
@@ -34,6 +43,8 @@ function Navbar({ links }) {
3443
md:flex pl-0 justify-start md:justify-center md:items-center md:pb-0 md:z-auto left-0 xs:justify-center xs:gap-y-12
3544
${isOpen ? "flex flex-col grow" : "hidden"}
3645
`}
46+
onMouseEnter={setCursorVariantNone}
47+
onMouseLeave={setCursorVariantDefault}
3748
>
3849
{links.map((link) => (
3950
<li
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { motion } from "framer-motion";
2+
import PropTypes from "prop-types";
3+
4+
function PageTransition({ children }) {
5+
return (
6+
<>
7+
<motion.div
8+
initial={{ y: "-100%" }}
9+
animate={{ y: ["-100%", "0%", "-100%"] }}
10+
transition={{ duration: 0.8, ease: "easeOut" }}
11+
className="z-50 absolute h-screen w-full bg-primary top-0 left-0"
12+
/>
13+
14+
<motion.div
15+
initial={{ opacity: 0 }}
16+
animate={{ opacity: 1 }}
17+
transition={{ delay: 0.7 }}
18+
>
19+
{children}
20+
</motion.div>
21+
</>
22+
);
23+
}
24+
25+
PageTransition.propTypes = {
26+
children: PropTypes.node.isRequired,
27+
};
28+
29+
export default PageTransition;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createContext, useState, useMemo } from "react";
2+
import PropTypes from "prop-types";
3+
4+
export const CursorVariantContext = createContext();
5+
6+
function CursorVariantProvider({ children }) {
7+
const [cursorVariant, setCursorVariant] = useState("default");
8+
9+
const contextValue = useMemo(
10+
() => ({
11+
cursorVariant,
12+
setCursorVariantText: () => setCursorVariant("text"),
13+
setCursorVariantDefault: () => setCursorVariant("default"),
14+
setCursorVariantNone: () => setCursorVariant("none"),
15+
}),
16+
[cursorVariant],
17+
);
18+
19+
return (
20+
<CursorVariantContext.Provider value={contextValue}>
21+
{children}
22+
</CursorVariantContext.Provider>
23+
);
24+
}
25+
26+
CursorVariantProvider.propTypes = {
27+
children: PropTypes.node.isRequired,
28+
};
29+
30+
export default CursorVariantProvider;

0 commit comments

Comments
 (0)