fix performance and new mobile menu
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
989d7f0b98
commit
6904171303
4 files changed
+436
-142
No files matched your search
+145
-40
@@ -33,8 +33,11 @@
|
||||
cookieAccept: document.getElementById('cookie-accept'),
|
||||
cookieDecline: document.getElementById('cookie-decline'),
|
||||
themeToggle: document.getElementById('theme-toggle'),
|
||||
themeToggleMobile: document.getElementById('theme-toggle-mobile'),
|
||||
mobileMenuToggle: document.getElementById('mobile-menu-toggle'),
|
||||
mainNav: document.getElementById('main-nav'),
|
||||
mobileMenuClose: document.getElementById('mobile-menu-close'),
|
||||
mobileNav: document.getElementById('mobile-nav'),
|
||||
mobileMenuOverlay: document.getElementById('mobile-menu-overlay'),
|
||||
header: document.querySelector('.site-header'),
|
||||
loadMoreBtn: document.getElementById('load-more-posts'),
|
||||
postsContainer: document.getElementById('posts-container'),
|
||||
@@ -72,25 +75,31 @@
|
||||
// ============= MODO ESCURO =============
|
||||
|
||||
function initDarkMode() {
|
||||
if (!elements.themeToggle) return;
|
||||
const toggles = [elements.themeToggle, elements.themeToggleMobile].filter(Boolean);
|
||||
if (toggles.length === 0) return;
|
||||
|
||||
const icon = elements.themeToggle.querySelector('i');
|
||||
const currentTheme = localStorage.getItem('theme');
|
||||
|
||||
// Aplicar tema inicial
|
||||
if (currentTheme === 'light') {
|
||||
body.classList.remove('dark-mode');
|
||||
icon?.classList.replace('fa-sun', 'fa-moon');
|
||||
toggles.forEach(toggle => {
|
||||
const icon = toggle.querySelector('i');
|
||||
icon?.classList.replace('fa-sun', 'fa-moon');
|
||||
});
|
||||
} else {
|
||||
body.classList.add('dark-mode');
|
||||
icon?.classList.replace('fa-moon', 'fa-sun');
|
||||
toggles.forEach(toggle => {
|
||||
const icon = toggle.querySelector('i');
|
||||
icon?.classList.replace('fa-moon', 'fa-sun');
|
||||
});
|
||||
if (!currentTheme) {
|
||||
localStorage.setItem('theme', 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle modo escuro
|
||||
elements.themeToggle.addEventListener('click', (e) => {
|
||||
function toggleDarkMode(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -99,50 +108,94 @@
|
||||
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
|
||||
if (icon) {
|
||||
icon.classList.replace(isDark ? 'fa-moon' : 'fa-sun', isDark ? 'fa-sun' : 'fa-moon');
|
||||
}
|
||||
toggles.forEach(toggle => {
|
||||
const icon = toggle.querySelector('i');
|
||||
if (icon) {
|
||||
icon.classList.replace(isDark ? 'fa-moon' : 'fa-sun', isDark ? 'fa-sun' : 'fa-moon');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', toggleDarkMode);
|
||||
});
|
||||
}
|
||||
|
||||
// ============= MENU MOBILE =============
|
||||
|
||||
function initMobileMenu() {
|
||||
if (!elements.mobileMenuToggle || !elements.mainNav) return;
|
||||
|
||||
function toggleMenu(shouldOpen) {
|
||||
const isOpen = shouldOpen ?? !elements.mobileMenuToggle.classList.contains('active');
|
||||
|
||||
elements.mobileMenuToggle.classList.toggle('active', isOpen);
|
||||
elements.mobileMenuToggle.setAttribute('aria-expanded', isOpen);
|
||||
elements.mainNav.classList.toggle('active', isOpen);
|
||||
body.classList.toggle('menu-open', isOpen);
|
||||
body.style.overflow = isOpen ? 'hidden' : '';
|
||||
if (!elements.mobileMenuToggle || !elements.mobileNav || !elements.mobileMenuOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle menu
|
||||
function openMenu() {
|
||||
elements.mobileMenuToggle.classList.add('active');
|
||||
elements.mobileMenuToggle.setAttribute('aria-expanded', 'true');
|
||||
elements.mobileNav.classList.add('active');
|
||||
elements.mobileMenuOverlay.classList.add('active');
|
||||
body.classList.add('menu-open');
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
elements.mobileMenuToggle.classList.remove('active');
|
||||
elements.mobileMenuToggle.setAttribute('aria-expanded', 'false');
|
||||
elements.mobileNav.classList.remove('active');
|
||||
elements.mobileMenuOverlay.classList.remove('active');
|
||||
body.classList.remove('menu-open');
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
if (elements.mobileNav.classList.contains('active')) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
}
|
||||
|
||||
// Abrir/fechar menu ao clicar no botão hamburguer
|
||||
elements.mobileMenuToggle.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleMenu();
|
||||
});
|
||||
|
||||
// Fechar menu ao clicar em links
|
||||
elements.mainNav.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'A' && !e.target.closest('.theme-toggle-item')) {
|
||||
toggleMenu(false);
|
||||
}
|
||||
// Fechar menu ao clicar no botão X
|
||||
if (elements.mobileMenuClose) {
|
||||
elements.mobileMenuClose.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
});
|
||||
}
|
||||
|
||||
// Fechar menu ao clicar no overlay
|
||||
elements.mobileMenuOverlay.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
// Fechar menu ao clicar fora
|
||||
document.addEventListener('click', (e) => {
|
||||
if (body.classList.contains('menu-open')) {
|
||||
const isClickInsideNav = elements.mainNav.contains(e.target);
|
||||
const isClickOnToggle = elements.mobileMenuToggle.contains(e.target);
|
||||
|
||||
if (!isClickInsideNav && !isClickOnToggle) {
|
||||
toggleMenu(false);
|
||||
}
|
||||
// Fechar menu ao clicar em links
|
||||
const mobileLinks = elements.mobileNav.querySelectorAll('a');
|
||||
mobileLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
// Não previne o comportamento padrão para permitir navegação
|
||||
// Apenas fecha o menu
|
||||
closeMenu();
|
||||
});
|
||||
});
|
||||
|
||||
// Fechar menu ao clicar no botão de tema mobile
|
||||
if (elements.themeToggleMobile) {
|
||||
elements.themeToggleMobile.addEventListener('click', (e) => {
|
||||
// Não fecha imediatamente, só após trocar tema
|
||||
// O evento de tema já está tratado em initDarkMode
|
||||
});
|
||||
}
|
||||
|
||||
// Fechar menu com tecla ESC
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && elements.mobileNav.classList.contains('active')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -177,12 +230,25 @@
|
||||
function initHeaderScroll() {
|
||||
if (!elements.header) return;
|
||||
|
||||
const handleScroll = debounce(() => {
|
||||
const shouldElevate = window.scrollY > 100;
|
||||
let lastScrollY = window.scrollY;
|
||||
let ticking = false;
|
||||
|
||||
const updateHeader = () => {
|
||||
const shouldElevate = lastScrollY > 100;
|
||||
elements.header.style.boxShadow = shouldElevate
|
||||
? '0 4px 20px rgba(0,0,0,0.15)'
|
||||
: '0 2px 10px rgba(0,0,0,0.1)';
|
||||
}, 50);
|
||||
ticking = false;
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
lastScrollY = window.scrollY;
|
||||
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(updateHeader);
|
||||
ticking = true;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
}
|
||||
@@ -198,8 +264,13 @@
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
// Usar requestAnimationFrame para animação suave
|
||||
requestAnimationFrame(() => {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
});
|
||||
// Parar de observar após animar
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
@@ -208,7 +279,7 @@
|
||||
cards.forEach(card => {
|
||||
card.style.opacity = '0';
|
||||
card.style.transform = 'translateY(20px)';
|
||||
card.style.transition = 'all 0.6s ease';
|
||||
card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
|
||||
observer.observe(card);
|
||||
});
|
||||
}
|
||||
@@ -301,6 +372,39 @@
|
||||
|
||||
// ============= INICIALIZAÇÃO =============
|
||||
|
||||
/**
|
||||
* Lazy loading de imagens nativas
|
||||
*/
|
||||
function initLazyLoading() {
|
||||
// Verificar se o navegador suporta lazy loading nativo
|
||||
if ('loading' in HTMLImageElement.prototype) {
|
||||
const images = document.querySelectorAll('img[loading="lazy"]');
|
||||
images.forEach(img => {
|
||||
if (img.dataset.src) {
|
||||
img.src = img.dataset.src;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: usar Intersection Observer
|
||||
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
if (img.dataset.src) {
|
||||
img.src = img.dataset.src;
|
||||
img.removeAttribute('data-src');
|
||||
}
|
||||
observer.unobserve(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
|
||||
imageObserver.observe(img);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initCookieBanner();
|
||||
initDarkMode();
|
||||
@@ -310,6 +414,7 @@
|
||||
initCardAnimations();
|
||||
initRippleEffect();
|
||||
initLoadMorePosts();
|
||||
initLazyLoading();
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in new issue
Block a user