# JavaScript Interactivity — jQuery + GSAP Guida per aggiungere interattività e animazioni fluide ai siti web. --- ## Setup Librerie ### CDN Links ```html ``` --- ## jQuery Patterns ### Document Ready ```javascript $(document).ready(function() { // Tutto il codice va qui dentro // o usa la shorthand: }); // Shorthand (equivalente) $(function() { // Codice qui }); ``` ### Mobile Menu Toggle ```javascript $(function() { const $menuToggle = $('.mobile-menu-toggle'); const $navMenu = $('.nav-menu'); const $body = $('body'); $menuToggle.on('click', function() { $navMenu.toggleClass('is-open'); $menuToggle.toggleClass('is-active'); $body.toggleClass('menu-open'); // Update ARIA const expanded = $menuToggle.attr('aria-expanded') === 'true'; $menuToggle.attr('aria-expanded', !expanded); }); // Close menu on link click (mobile) $navMenu.find('a').on('click', function() { if ($(window).width() < 768) { $navMenu.removeClass('is-open'); $menuToggle.removeClass('is-active'); $body.removeClass('menu-open'); $menuToggle.attr('aria-expanded', 'false'); } }); // Close menu on outside click $(document).on('click', function(e) { if (!$(e.target).closest('.nav-menu, .mobile-menu-toggle').length) { $navMenu.removeClass('is-open'); $menuToggle.removeClass('is-active'); $body.removeClass('menu-open'); $menuToggle.attr('aria-expanded', 'false'); } }); }); ``` ### Smooth Scroll ```javascript $(function() { $('a[href^="#"]').on('click', function(e) { const targetId = this.getAttribute('href'); // Ignora link vuoti o non-anchor if (targetId === '#' || !targetId.startsWith('#')) return; const $target = $(targetId); if ($target.length) { e.preventDefault(); const offsetTop = $target.offset().top; const headerHeight = $('header').outerHeight() || 0; const scrollPosition = offsetTop - headerHeight - 20; $('html, body').stop().animate({ scrollTop: scrollPosition }, 800, 'easeInOutQuad'); // Update URL without jumping history.pushState(null, null, targetId); } }); }); // Easing function (se non usi jQuery easing plugin) $.easing.easeInOutQuad = function(x, t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return -c / 2 * (t * (t - 2) - 1) + b; }; ``` ### FAQ Accordion ```javascript $(function() { const $faqQuestions = $('.faq-question'); $faqQuestions.on('click', function() { const $question = $(this); const $answer = $question.next('.faq-answer'); const $item = $question.closest('.faq-item'); const isActive = $question.hasClass('is-active'); // Close all other items (accordion style) $faqQuestions.not(this).removeClass('is-active'); $('.faq-answer').not($answer).slideUp(300); $('.faq-item').not($item).removeClass('is-active'); // Toggle current item $question.toggleClass('is-active'); $answer.slideToggle(300); $item.toggleClass('is-active'); // Update ARIA const expanded = $question.attr('aria-expanded') === 'true'; $question.attr('aria-expanded', !expanded); }); // Keyboard accessibility $faqQuestions.on('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); $(this).click(); } }); }); ``` ### Form Validation ```javascript $(function() { const $form = $('.contact-form'); const $submitBtn = $form.find('button[type="submit"]'); $form.on('submit', function(e) { let isValid = true; // Validate required fields $form.find('[required]').each(function() { const $field = $(this); const value = $field.val().trim(); const $error = $field.siblings('.form-error'); if (!value) { isValid = false; $field.addClass('is-invalid'); if ($error.length) { $error.text('Questo campo è obbligatorio').show(); } else { $field.after('Questo campo è obbligatorio'); } } else { $field.removeClass('is-invalid'); $error.hide(); // Email validation if ($field.attr('type') === 'email') { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { isValid = false; $field.addClass('is-invalid'); if ($error.length) { $error.text('Inserisci un\'email valida').show(); } else { $field.after('Inserisci un\'email valida'); } } } } }); if (!isValid) { e.preventDefault(); // Scroll to first error const $firstError = $form.find('.is-invalid').first(); if ($firstError.length) { $('html, body').animate({ scrollTop: $firstError.offset().top - 100 }, 500); } } }); // Clear error on input $form.find('input, textarea').on('input', function() { $(this).removeClass('is-invalid'); $(this).siblings('.form-error').hide(); }); // Form submission success (AJAX example with Formspree) $form.on('submit', function(e) { e.preventDefault(); $submitBtn.prop('disabled', true).text('Invio in corso...'); $.ajax({ url: $form.attr('action'), method: 'POST', data: $form.serialize(), headers: { 'Accept': 'application/json' } }) .done(function(response) { $form.trigger('reset'); $submitBtn.text('Messaggio Inviato!'); // Show success message $form.after('
Grazie! Ti contatteremo presto.
'); setTimeout(function() { $submitBtn.prop('disabled', false).text('Invia Messaggio'); $('.form-success').fadeOut(); }, 3000); }) .fail(function() { $submitBtn.prop('disabled', false).text('Riprova'); $form.after('
Errore nell\'invio. Riprova più tardi.
'); }); }); }); ``` ### Back to Top Button ```javascript $(function() { const $backToTop = $('.back-to-top'); // Show/hide on scroll $(window).on('scroll', function() { if ($(window).scrollTop() > 300) { $backToTop.addClass('is-visible'); } else { $backToTop.removeClass('is-visible'); } }); // Smooth scroll to top $backToTop.on('click', function(e) { e.preventDefault(); $('html, body').animate({ scrollTop: 0 }, 800); }); }); ``` ```html ``` ```css .back-to-top { position: fixed; bottom: 32px; right: 32px; width: 48px; height: 48px; background: var(--color-primary); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 100; } .back-to-top.is-visible { opacity: 1; visibility: visible; } .back-to-top:hover { background: var(--color-primary-dark); transform: translateY(-4px); } ``` ### Image Lazy Loading ```javascript $(function() { const $lazyImages = $('img[loading="lazy"]'); // Native lazy loading support check if ('loading' in HTMLImageElement.prototype) { // Browser supports native lazy loading $lazyImages.each(function() { const src = $(this).data('src'); if (src) { $(this).attr('src', src); } }); } else { // Fallback for browsers without support const imageObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { if (entry.isIntersecting) { const $img = $(entry.target); const src = $img.data('src'); if (src) { $img.attr('src', src); $img.on('load', function() { $img.addClass('is-loaded'); }); } observer.unobserve(entry.target); } }); }); $lazyImages.each(function() { imageObserver.observe(this); }); } }); ``` --- ## GSAP Animations ### Basic Animations ```javascript $(function() { // Fade in element gsap.from('.hero h1', { duration: 1, opacity: 0, y: 30, ease: 'power3.out' }); // Stagger animation gsap.from('.service-card', { duration: 0.8, opacity: 0, y: 40, stagger: 0.15, ease: 'power3.out', delay: 0.3 }); // Multiple properties gsap.from('.hero .btn', { duration: 1, opacity: 0, y: 20, scale: 0.95, delay: 0.5, ease: 'back.out(1.7)' }); }); ``` ### Timeline ```javascript $(function() { const tl = gsap.timeline({ defaults: { ease: 'power3.out' } }); tl.from('.hero h1', { duration: 1, opacity: 0, y: 50 }) .from('.hero-sub', { duration: 0.8, opacity: 0, y: 30 }, '-=0.5') .from('.hero .btn', { duration: 0.6, opacity: 0, y: 20 }, '-=0.4') .from('.logo-wall', { duration: 1, opacity: 0 }, '-=0.3'); }); ``` ### ScrollTrigger Animations ```javascript // Register ScrollTrigger gsap.registerPlugin(ScrollTrigger); $(function() { // Fade in on scroll gsap.utils.toArray('.fade-on-scroll').forEach(function(elem) { gsap.to(elem, { scrollTrigger: { trigger: elem, start: 'top 85%', toggleActions: 'play none none none' }, opacity: 1, y: 0, duration: 1, ease: 'power3.out' }); }); // Slide in from left gsap.utils.toArray('.slide-in-left').forEach(function(elem) { gsap.from(elem, { scrollTrigger: { trigger: elem, start: 'top 80%', toggleActions: 'play none none none' }, opacity: 0, x: -50, duration: 1, ease: 'power3.out' }); }); // Slide in from right gsap.utils.toArray('.slide-in-right').forEach(function(elem) { gsap.from(elem, { scrollTrigger: { trigger: elem, start: 'top 80%', toggleActions: 'play none none none' }, opacity: 0, x: 50, duration: 1, ease: 'power3.out' }); }); // Scale up gsap.utils.toArray('.scale-up').forEach(function(elem) { gsap.from(elem, { scrollTrigger: { trigger: elem, start: 'top 85%', toggleActions: 'play none none none' }, opacity: 0, scale: 0.8, duration: 1, ease: 'back.out(1.7)' }); }); // Parallax effect gsap.to('.parallax-bg', { scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true }, y: 100, ease: 'none' }); // Pin section (sticky) gsap.to('.sticky-section', { scrollTrigger: { trigger: '.sticky-section', start: 'top top', end: '+=100%', pin: true, scrub: true } }); }); ``` ### Hover Animations ```javascript $(function() { // Card hover with GSAP const $cards = $('.service-card'); $cards.each(function() { const $card = $(this); $card.on('mouseenter', function() { gsap.to($card, { duration: 0.3, y: -8, boxShadow: '0 12px 32px rgba(0, 0, 0, 0.15)', ease: 'power2.out' }); }); $card.on('mouseleave', function() { gsap.to($card, { duration: 0.3, y: 0, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)', ease: 'power2.out' }); }); }); // Button hover const $buttons = $('.btn'); $buttons.each(function() { const $btn = $(this); $btn.on('mouseenter', function() { gsap.to($btn, { duration: 0.2, scale: 1.05, ease: 'power2.out' }); }); $btn.on('mouseleave', function() { gsap.to($btn, { duration: 0.2, scale: 1, ease: 'power2.out' }); }); }); }); ``` ### Complex Animation Sequence ```javascript $(function() { const tl = gsap.timeline({ scrollTrigger: { trigger: '.feature-section', start: 'top 70%', toggleActions: 'play none none none' } }); tl.from('.feature-section h2', { duration: 0.8, opacity: 0, y: 30, ease: 'power3.out' }) .from('.feature-section p', { duration: 0.6, opacity: 0, y: 20 }, '-=0.4') .from('.feature-grid .feature-card', { duration: 0.6, opacity: 0, y: 40, stagger: 0.1, ease: 'back.out(1.7)' }, '-=0.3') .from('.feature-section .btn', { duration: 0.5, opacity: 0, scale: 0.9 }, '-=0.3'); }); ``` --- ## Performance Best Practices ### Reduce Motion ```javascript // Check for reduced motion preference const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) { // Disable GSAP animations gsap.globalTimeline.timeScale(0); // Or set duration to 0 gsap.defaults({ duration: 0 }); } ``` ### Debounce Scroll Events ```javascript // Debounce function function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Usage $(window).on('scroll', debounce(function() { // Scroll logic here }, 100)); ``` ### Use CSS for Simple Animations ```css /* Prefer CSS transitions for simple hover effects */ .btn { transition: transform 0.2s ease, box-shadow 0.2s ease; } .btn:hover { transform: translateY(-2px); } /* Use GSAP only for complex animations */ ``` --- ## Accessibility ### Respect User Preferences ```javascript // Check reduced motion const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (!prefersReducedMotion) { // Initialize animations initAnimations(); } function initAnimations() { // GSAP animations here } ``` ### Focus Management ```javascript // Trap focus in mobile menu function trapFocus($element) { const $focusable = $element.find('a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'); const $first = $focusable.first(); const $last = $focusable.last(); $element.on('keydown', function(e) { if (e.key === 'Tab') { if (e.shiftKey) { if (document.activeElement === $first[0]) { e.preventDefault(); $last.focus(); } } else { if (document.activeElement === $last[0]) { e.preventDefault(); $first.focus(); } } } }); } ``` --- ## Checklist Interattività - [ ] Mobile menu toggle funzionante - [ ] Smooth scroll per anchor links - [ ] FAQ accordion accessibile - [ ] Form validation con feedback - [ ] Back to top button (se pagine lunghe) - [ ] Lazy loading immagini - [ ] GSAP animations (hero, scroll) - [ ] Hover effects su cards/buttons - [ ] Respect reduced motion preference - [ ] Keyboard navigation testata - [ ] No console errors - [ ] Performance ottimizzata (debounce, CSS where possible) --- _References per agency-web-developer skill_