diff --git a/src/assets/js/components/projects.js b/src/assets/js/components/projects.js
index 95be364..f906f8f 100644
--- a/src/assets/js/components/projects.js
+++ b/src/assets/js/components/projects.js
@@ -2,6 +2,9 @@
* Projects section with modern design
*/
+// Module-level observer to prevent memory leaks
+let projectObserver = null;
+
export const populateProjects = (projects) => {
const projectsContainer = document.getElementById('projects-container');
if (!projectsContainer) return;
@@ -29,9 +32,9 @@ export const populateProjects = (projects) => {
${
project.image
? `
-
@@ -65,10 +68,10 @@ export const populateProjects = (projects) => {
${
project.link
? `
-
@@ -81,8 +84,8 @@ export const populateProjects = (projects) => {
${
project.github
? `
- {
// Add intersection observer for scroll animations
const animateOnScroll = () => {
+ // Cleanup previous observer to prevent memory leaks
+ if (projectObserver) {
+ projectObserver.disconnect();
+ projectObserver = null;
+ }
+
const elements = document.querySelectorAll('.fade-in-up');
- const observer = new IntersectionObserver(
+ projectObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
+ // Unobserve after animation to free memory
+ projectObserver.unobserve(entry.target);
}
});
},
@@ -127,7 +138,7 @@ export const populateProjects = (projects) => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
el.style.transition = 'opacity 0.6s ease-out, transform 0.6s ease-out';
- observer.observe(el);
+ projectObserver.observe(el);
});
};
@@ -138,3 +149,14 @@ export const populateProjects = (projects) => {
animateOnScroll();
}
};
+
+/**
+ * Cleanup function for SPA scenarios or component unmounting
+ * Call this to properly clean up the IntersectionObserver
+ */
+export const cleanupProjects = () => {
+ if (projectObserver) {
+ projectObserver.disconnect();
+ projectObserver = null;
+ }
+};
diff --git a/src/assets/js/utils/mobileMenu.js b/src/assets/js/utils/mobileMenu.js
index f4066f0..04e2265 100644
--- a/src/assets/js/utils/mobileMenu.js
+++ b/src/assets/js/utils/mobileMenu.js
@@ -2,35 +2,66 @@
* Mobile menu functionality
*/
+// Store cleanup function to prevent memory leaks
+let cleanupMobileMenu = null;
+
export const initMobileMenu = () => {
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
- if (mobileMenuButton && mobileMenu) {
- mobileMenuButton.addEventListener('click', () => {
- mobileMenu.classList.toggle('hidden');
- });
+ if (!mobileMenuButton || !mobileMenu) return;
- // Close mobile menu when clicking outside
- document.addEventListener('click', (event) => {
- const isClickInsideMenu = mobileMenu.contains(event.target);
- const isClickOnButton = mobileMenuButton.contains(event.target);
-
- if (
- !isClickInsideMenu &&
- !isClickOnButton &&
- !mobileMenu.classList.contains('hidden')
- ) {
- mobileMenu.classList.add('hidden');
- }
- });
+ // Cleanup previous listeners if reinitializing
+ if (cleanupMobileMenu) {
+ cleanupMobileMenu();
+ }
+
+ // Define event handlers so they can be removed later
+ const handleMenuToggle = () => {
+ mobileMenu.classList.toggle('hidden');
+ };
+
+ const handleOutsideClick = (event) => {
+ const isClickInsideMenu = mobileMenu.contains(event.target);
+ const isClickOnButton = mobileMenuButton.contains(event.target);
+
+ if (!isClickInsideMenu && !isClickOnButton && !mobileMenu.classList.contains('hidden')) {
+ mobileMenu.classList.add('hidden');
+ }
+ };
- // Close mobile menu when a navigation link is clicked
- const mobileNavLinks = mobileMenu.querySelectorAll('a');
+ // Close mobile menu when a navigation link is clicked
+ const mobileNavLinks = mobileMenu.querySelectorAll('a');
+ const handleLinkClick = () => {
+ mobileMenu.classList.add('hidden');
+ };
+
+ // Attach event listeners
+ mobileMenuButton.addEventListener('click', handleMenuToggle);
+ document.addEventListener('click', handleOutsideClick);
+ mobileNavLinks.forEach((link) => {
+ link.addEventListener('click', handleLinkClick);
+ });
+
+ // Store cleanup function to remove all event listeners
+ cleanupMobileMenu = () => {
+ mobileMenuButton.removeEventListener('click', handleMenuToggle);
+ document.removeEventListener('click', handleOutsideClick);
mobileNavLinks.forEach((link) => {
- link.addEventListener('click', () => {
- mobileMenu.classList.add('hidden');
- });
+ link.removeEventListener('click', handleLinkClick);
});
+ cleanupMobileMenu = null;
+ };
+
+ return cleanupMobileMenu;
+};
+
+/**
+ * Cleanup function to remove mobile menu event listeners
+ * Call this when the mobile menu is no longer needed (e.g., SPA navigation)
+ */
+export const cleanup = () => {
+ if (cleanupMobileMenu) {
+ cleanupMobileMenu();
}
};