Skip to content

Commit c816a3b

Browse files
authored
feat: add gsap and replace nav animation with gsap (#603)
* feat: add gsap and replace nav animation with gsap * fix: fix navigation width * fix: remove unused variable * fix: add tailwindcss package * fix: fix croocked X
1 parent 0b370e2 commit c816a3b

File tree

5 files changed

+283
-933
lines changed

5 files changed

+283
-933
lines changed

apps/website/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@
1010
"@tailwindcss/forms": "^0.5.10",
1111
"@tailwindcss/postcss": "^4.1.11",
1212
"@tailwindcss/typography": "^0.5.16",
13-
"@tailwindcss/vite": "^4.1.11",
13+
"@tailwindcss/vite": "^4.1.13",
1414
"astro": "5.13.7",
1515
"astro-font": "^1.1.0",
1616
"autoprefixer": "^10.4.21",
1717
"classnames": "^2.5.1",
1818
"date-fns": "^4.1.0",
19+
"gsap": "^3.13.0",
1920
"marked": "^16.1.2",
2021
"posthog-js": "^1.222.0",
2122
"react": "^19.1.0",
2223
"react-dom": "^19.1.0",
2324
"react-icons": "^5.5.0",
24-
"tailwind": "^4.0.0",
25-
"tailwind-bootstrap-grid": "^6.0.0"
25+
"tailwind-bootstrap-grid": "^6.0.0",
26+
"tailwindcss": "^4.1.13"
2627
},
2728
"scripts": {
2829
"dev": "astro dev --port 4321",
Lines changed: 132 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
import { getImage } from "astro:assets";
33
import crocoderLogo from "../assets/crocoder-logo.png";
4-
import "../styles/navigation.css";
54
65
const optimizedLogo = await getImage({
76
src: crocoderLogo,
@@ -34,8 +33,6 @@ const optimizedLogo = await getImage({
3433
md:rounded-r-lg
3534
md:overflow-hidden
3635
relative
37-
gap:12
38-
max-md:has-[input:checked]:bg-secondary
3936
md:w-[680px]"
4037
data-navhidden="false"
4138
>
@@ -72,17 +69,17 @@ const optimizedLogo = await getImage({
7269
</li>
7370
</ul>
7471

75-
<label for="nav-menu-toggle" class="md:hidden flex flex-col gap-1.5">
76-
<input
77-
id="nav-menu-toggle"
78-
type="checkbox"
79-
readonly
80-
class="hidden peer/menu-toggle"
81-
/>
72+
<button
73+
id="nav-menu-toggle"
74+
aria-expanded="false"
75+
aria-controls="mobile-list"
76+
aria-label="Toggle menu"
77+
class="md:hidden flex flex-col gap-1.5"
78+
>
8279
<span id="top-line" class="h-0.5 w-6 bg-neutral-50 line line1"></span>
8380
<span id="middle-line" class="h-0.5 w-6 bg-neutral-50 line line2"></span>
8481
<span id="bottom-line" class="h-0.5 w-6 bg-neutral-50 line line3"></span>
85-
</label>
82+
</button>
8683

8784
<ul class="hidden items-center md:flex">
8885
<li id="for-ctos" data-navhidden="true" class="m-2">
@@ -101,7 +98,7 @@ const optimizedLogo = await getImage({
10198
Blog
10299
</a>
103100
</li>
104-
<li id="book-a-call-container" class="ml-auto w-0">
101+
<li id="book-a-call-container" class="ml-auto pr-2">
105102
<a
106103
data-navhidden="true"
107104
id="book-a-call-action"
@@ -165,140 +162,152 @@ const optimizedLogo = await getImage({
165162
</div>
166163
</header>
167164

168-
<script is:inline>
169-
let lastScrollTop = 0;
170-
let isScroll = false;
165+
<script>
166+
import { gsap } from "gsap";
167+
import { ScrollTrigger } from "gsap/ScrollTrigger";
168+
169+
gsap.registerPlugin(ScrollTrigger);
170+
let mm = gsap.matchMedia();
171+
172+
const NAV_SHRINK_WIDTH = "377px";
173+
const NAV_EXPAND_WIDTH = "680px";
174+
const FRAME_DURATION = 0.425;
171175

172-
const allDataHidden = document.querySelectorAll("[data-navhidden]");
176+
const navBar = document.getElementById("nav-bar");
177+
const logoElem = document.getElementById("logo");
178+
const forCtoElem = document.getElementById("for-ctos");
179+
const blogElem = document.getElementById("blog");
173180
const bookACallAction = document.getElementById("book-a-call-action");
181+
const containerElement = document.getElementById("book-a-call-container");
174182
const mobileBookACallAction = document.getElementById(
175183
"mobile-book-a-call-action",
176184
);
177-
const navMenuToggle = document.getElementById("nav-menu-toggle");
185+
178186
const mobileContact = document.getElementById("mobile-contact");
179-
const navElem = document.getElementById("nav-bar");
180-
const logoElem = document.getElementById("logo");
181-
const forCtoElem = document.getElementById("for-ctos");
182-
const blogElem = document.getElementById("blog");
183-
const containerElement = document.getElementById("book-a-call-container");
184187
const mobileList = document.getElementById("mobile-list");
188+
const navMenuToggle = document.getElementById("nav-menu-toggle");
185189

186-
function onScroll() {
187-
const currentScrollTop =
188-
window.scrollY || document.documentElement.scrollTop;
190+
mm.add("(width >= 768px)", () => {
191+
if (!navBar || !bookACallAction) return;
189192

190-
if (!isScroll) {
191-
isScroll = true;
192-
window.requestAnimationFrame(() => {
193-
handleScroll(currentScrollTop);
194-
isScroll = false;
195-
});
196-
}
197-
}
193+
const navHideItems = [forCtoElem, blogElem, logoElem];
198194

199-
function handleScroll(currentScrollTop) {
200-
/* Since the animation shouldn't trigger on first render, all the animation
201-
classes are added first time the handle scroll is triggered*/
202-
if (!navElem.classList.contains("animated-navigation")) {
203-
navElem.classList.add("animated-navigation");
204-
navElem.classList.remove("md:w-[680px]");
205-
forCtoElem.classList.add("animated-text");
206-
blogElem.classList.add("animated-text");
207-
bookACallAction.classList.add("animated-button", "w-fit");
208-
containerElement.classList.remove("w-0");
209-
containerElement.classList.add("pr-2");
210-
handleClassChanges(allDataHidden);
211-
}
212-
if (
213-
currentScrollTop > lastScrollTop &&
214-
allDataHidden[0].getAttribute("data-navhidden") === "true"
215-
) {
216-
invertDataAttributte(allDataHidden);
217-
handleClassChanges(allDataHidden);
218-
} else if (
219-
currentScrollTop <= lastScrollTop &&
220-
allDataHidden[0].getAttribute("data-navhidden") === "false"
221-
) {
222-
invertDataAttributte(allDataHidden);
223-
}
195+
gsap.set(navBar, { width: NAV_EXPAND_WIDTH, overwrite: false });
196+
gsap.set(navHideItems, {
197+
autoAlpha: 1,
198+
overflow: "hidden",
199+
});
200+
gsap.set(containerElement, { autoAlpha: 0 });
201+
gsap.set(bookACallAction, {
202+
autoAlpha: 0,
203+
overflow: "hidden",
204+
display: "none",
205+
});
224206

225-
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
226-
}
207+
const tl = gsap.timeline({
208+
paused: true,
209+
defaults: { duration: FRAME_DURATION, ease: "none" },
210+
});
227211

228-
function invertDataAttributte(dataHiddenNodes) {
229-
dataHiddenNodes.forEach((node) => {
230-
if (node.closest("li")?.textContent?.includes("Contact us")) return;
212+
tl.to(navHideItems, {
213+
autoAlpha: 0,
214+
stagger: 0.05,
215+
overwrite: "auto",
216+
display: "none",
217+
}).to(navBar, { width: NAV_SHRINK_WIDTH }, "-=0.1");
231218

232-
const currentVal = node.getAttribute("data-navhidden");
233-
node.setAttribute(
234-
"data-navhidden",
235-
currentVal === "true" ? "false" : "true",
236-
);
219+
tl.addLabel("showBookACall");
237220

238-
node.style.animationName = "none";
239-
void node.offsetWidth;
240-
node.style.animationName = "";
241-
});
242-
}
221+
tl.to(
222+
bookACallAction,
223+
{ autoAlpha: 1, display: "list-item" },
224+
"showBookACall",
225+
);
226+
tl.to(containerElement, { autoAlpha: 1 }, "showBookACall");
227+
228+
let lastDirection = 0;
243229

244-
function handleClassChanges(dataHiddenNodes) {
245-
dataHiddenNodes.forEach((node) => {
246-
let condition;
247-
switch (node.getAttribute("id")) {
248-
case "logo":
249-
condition = node.getAttribute("data-navhidden") === "false";
250-
logoElem.classList.toggle("md:hidden", condition);
251-
logoElem.classList.toggle("w-0", condition);
252-
break;
253-
case "for-ctos":
254-
condition = node.getAttribute("data-navhidden") === "true";
255-
forCtoElem.classList.toggle("hidden", condition);
256-
forCtoElem.classList.toggle("!w-0", condition);
257-
break;
258-
case "blog":
259-
condition = node.getAttribute("data-navhidden") === "true";
260-
blogElem.classList.toggle("hidden", condition);
261-
blogElem.classList.toggle("!w-0", condition);
262-
break;
263-
case "book-a-call-action":
264-
condition = node.getAttribute("data-navhidden") === "false";
265-
bookACallAction.classList.toggle("hidden", condition);
266-
bookACallAction.classList.toggle("!w-0", condition);
267-
break;
268-
default:
269-
break;
270-
}
230+
const st = ScrollTrigger.create({
231+
onUpdate(self) {
232+
if (self.direction === 1 && lastDirection !== 1) {
233+
tl.play();
234+
lastDirection = 1;
235+
} else if (self.direction === -1 && lastDirection !== -1) {
236+
tl.reverse();
237+
lastDirection = -1;
238+
}
239+
},
271240
});
272-
}
273241

274-
navMenuToggle.addEventListener("change", () => {
275-
mobileContact.classList.toggle("hidden", navMenuToggle.checked);
276-
mobileList.classList.toggle("hidden", !navMenuToggle.checked);
277-
mobileList.classList.toggle("flex", navMenuToggle.checked);
242+
return () => {
243+
tl.kill();
244+
st.kill();
245+
gsap.killTweensOf([
246+
navBar,
247+
...navHideItems,
248+
bookACallAction,
249+
containerElement,
250+
]);
251+
};
278252
});
279253

280-
navElem.addEventListener("animationend", (event) => {
254+
mm.add("(width < 768px)", () => {
281255
if (
282-
event.animationName === "navigation-animation" &&
283-
allDataHidden[0].getAttribute("data-navhidden") === "true"
284-
) {
285-
handleClassChanges(allDataHidden);
286-
}
287-
});
256+
!navMenuToggle ||
257+
!mobileList ||
258+
!mobileContact ||
259+
!mobileBookACallAction
260+
)
261+
return;
262+
263+
gsap.set(mobileList, {
264+
height: 0,
265+
autoAlpha: 0,
266+
display: "flex",
267+
overflow: "hidden",
268+
});
269+
gsap.set(mobileContact, { autoAlpha: 1 });
270+
271+
const tlBurger = gsap.timeline({
272+
paused: true,
273+
defaults: { duration: 0.3, ease: "power2.inOut" },
274+
});
275+
276+
tlBurger
277+
.to("#top-line", { rotation: 45, x: 0, y: 10 }, 0)
278+
.to("#middle-line", { autoAlpha: 0 }, 0)
279+
.to("#bottom-line", { rotation: -45, x: 0, y: -6 }, 0)
280+
.to(navBar, { backgroundColor: "#3c3843" }, 0)
281+
.to(mobileList, { height: "auto", autoAlpha: 1, duration: 0.4 }, 0)
282+
.to(mobileContact, { autoAlpha: 0 }, 0);
283+
284+
const handleBurgerClick = () => {
285+
const expanded = navMenuToggle.getAttribute("aria-expanded") === "true";
286+
navMenuToggle.setAttribute("aria-expanded", String(!expanded));
287+
288+
if (expanded) tlBurger.reverse();
289+
else tlBurger.play();
290+
};
288291

289-
function handleBookACall() {
290-
navMenuToggle.checked = false;
291-
window.navScroll = true;
292+
const handleBookACallClick = () => {
293+
tlBurger.reverse();
294+
navMenuToggle.setAttribute("aria-expanded", "false");
295+
};
292296

293-
setTimeout(() => {
294-
window.navScroll = false;
295-
}, 1500);
296-
}
297+
mobileBookACallAction.addEventListener("click", handleBookACallClick);
298+
navMenuToggle.addEventListener("click", handleBurgerClick);
297299

298-
window.addEventListener("scroll", onScroll, { passive: true });
299-
bookACallAction.addEventListener("click", handleBookACall);
300+
return () => {
301+
navMenuToggle.removeEventListener("click", handleBurgerClick);
302+
mobileBookACallAction.removeEventListener("click", handleBookACallClick);
300303

301-
mobileBookACallAction.addEventListener("click", () => {
302-
navMenuToggle.click();
304+
gsap.killTweensOf([
305+
mobileList,
306+
mobileContact,
307+
"#top-line",
308+
"#middle-line",
309+
"#bottom-line",
310+
]);
311+
};
303312
});
304313
</script>

0 commit comments

Comments
 (0)