/** * Оптимизированный JavaScript в стиле Apple.com * Плавные анимации, параллакс, микро-взаимодействия */ class AppleWebsite { constructor() { this.init(); } init() { // Инициализация компонентов this.initSmoothScroll(); this.initParallax(); this.initScrollAnimations(); this.initCountdown(); this.initContactForm(); this.initGallery(); this.initHeaderEffects(); this.initScrollProgress(); this.initScrollTextEffect(); // Новый эффект подъема текста this.initSideBlocksEffect(); // Новый эффект блоков с боков // Оптимизация производительности this.initPerformance(); // Инициализация после загрузки this.onLoad(); } // ===== Плавная прокрутка ===== initSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const href = anchor.getAttribute('href'); if (href === '#') return; const target = document.querySelector(href); if (target) { e.preventDefault(); window.scrollTo({ top: target.offsetTop - 80, behavior: 'smooth' }); } }); }); } // ===== Параллакс эффекты ===== initParallax() { // Простой параллакс для фона window.addEventListener('scroll', () => { const scrolled = window.pageYOffset; const hero = document.querySelector('.hero'); if (hero) { hero.style.transform = `translateY(${scrolled * 0.3}px)`; } }); // Параллакс при движении мыши window.addEventListener('mousemove', (e) => { const x = (e.clientX / window.innerWidth - 0.5) * 10; const y = (e.clientY / window.innerHeight - 0.5) * 10; document.querySelectorAll('.feature-card').forEach((element, index) => { const speed = 0.3 + (index * 0.05); element.style.transform = `translate(${x * speed}px, ${y * speed}px)`; }); }); } // ===== Анимации при скролле ===== initScrollAnimations() { const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // Добавляем класс анимации if (entry.target.classList.contains('fade-in')) { entry.target.style.animationPlayState = 'running'; } // Анимация для галереи с задержкой if (entry.target.classList.contains('gallery-grid')) { const items = entry.target.querySelectorAll('.gallery-item'); items.forEach((item, index) => { setTimeout(() => { item.style.animationPlayState = 'running'; }, index * 100); }); } } }); }, observerOptions); // Наблюдаем за элементами с анимацией document.querySelectorAll('.fade-in, .gallery-grid').forEach(el => observer.observe(el)); } // ===== Эффект подъема текста при скролле ===== initScrollTextEffect() { const heroTitle = document.querySelector('.hero-title'); const heroSubtitle = document.querySelector('.hero-subtitle'); const header = document.getElementById('header'); if (!heroTitle || !header) return; window.addEventListener('scroll', () => { const scrolled = window.pageYOffset; const windowHeight = window.innerHeight; const headerHeight = header.offsetHeight; // Эффект для заголовка if (heroTitle) { // Ускоренный подъем текста const titleProgress = Math.min(scrolled / (windowHeight * 0.5), 1); const titleTranslateY = 30 - (titleProgress * 60); // Быстрее поднимается const titleScale = 1 - (titleProgress * 0.2); // Немного уменьшается const titleOpacity = 1 - (titleProgress * 0.5); // Плавно исчезает heroTitle.style.transform = `translateY(${titleTranslateY}px) scale(${titleScale})`; heroTitle.style.opacity = Math.max(titleOpacity, 0); // Когда текст полностью на темном фоне хедера if (scrolled > headerHeight * 2) { heroTitle.style.position = 'fixed'; heroTitle.style.top = `${headerHeight / 2}px`; heroTitle.style.left = '50%'; heroTitle.style.transform = 'translateX(-50%) scale(0.8)'; heroTitle.style.zIndex = '1000'; heroTitle.style.color = '#FFFFFF'; heroTitle.style.textShadow = '0 2px 10px rgba(0,0,0,0.5)'; } else { heroTitle.style.position = ''; heroTitle.style.top = ''; heroTitle.style.left = ''; heroTitle.style.zIndex = ''; heroTitle.style.color = ''; heroTitle.style.textShadow = ''; } } // Эффект для подзаголовка if (heroSubtitle) { const subtitleProgress = Math.min(scrolled / (windowHeight * 0.6), 1); const subtitleTranslateY = 30 - (subtitleProgress * 50); const subtitleOpacity = 1 - (subtitleProgress * 0.7); heroSubtitle.style.transform = `translateY(${subtitleTranslateY}px)`; heroSubtitle.style.opacity = Math.max(subtitleOpacity, 0); } }); } // ===== Эффект блоков, приезжающих с боков к центру ===== initSideBlocksEffect() { const sideBlocks = document.querySelectorAll('.feature-card, .price-card, .gallery-item, .contact-item'); if (!sideBlocks.length) return; const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -100px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const block = entry.target; const rect = block.getBoundingClientRect(); const windowWidth = window.innerWidth; // Определяем с какой стороны блок должен приехать const blockCenter = rect.left + rect.width / 2; const isLeftSide = blockCenter < windowWidth / 2; // Устанавливаем начальную позицию block.style.transform = `translateX(${isLeftSide ? '-100px' : '100px'})`; block.style.opacity = '0'; block.style.transition = 'all 0.8s cubic-bezier(0.4, 0, 0.2, 1)'; // Запускаем анимацию с небольшой задержкой для эффекта волны setTimeout(() => { block.style.transform = 'translateX(0)'; block.style.opacity = '1'; // Добавляем дополнительный эффект "подпрыгивания" при появлении setTimeout(() => { block.style.transform = 'translateY(-10px)'; setTimeout(() => { block.style.transform = 'translateY(0)'; }, 150); }, 300); }, entry.target.dataset.delay || 0); observer.unobserve(block); } }); }, observerOptions); // Назначаем задержки для эффекта волны sideBlocks.forEach((block, index) => { block.dataset.delay = index * 100; observer.observe(block); }); } // ===== Индикатор прокрутки ===== initScrollProgress() { const progressBar = document.querySelector('.scroll-progress'); if (!progressBar) return; window.addEventListener('scroll', () => { const windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; const scrolled = (window.pageYOffset / windowHeight) * 100; progressBar.style.width = scrolled + '%'; }); } // ===== Таймер обратного отсчета ===== initCountdown() { const countdownElement = document.getElementById('countdown'); if (!countdownElement) return; const endDate = new Date('April 30, 2024 23:59:59').getTime(); const updateCountdown = () => { const now = new Date().getTime(); const timeLeft = endDate - now; if (timeLeft < 0) { document.getElementById('days').textContent = '00'; document.getElementById('hours').textContent = '00'; document.getElementById('minutes').textContent = '00'; return; } const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); document.getElementById('days').textContent = days.toString().padStart(2, '0'); document.getElementById('hours').textContent = hours.toString().padStart(2, '0'); document.getElementById('minutes').textContent = minutes.toString().padStart(2, '0'); }; updateCountdown(); setInterval(updateCountdown, 60000); } // ===== Контактная форма ===== initContactForm() { const form = document.getElementById('viewing-form'); if (!form) return; form.addEventListener('submit', (e) => { e.preventDefault(); const formData = { name: document.getElementById('name').value.trim(), phone: document.getElementById('phone').value.trim(), email: document.getElementById('email').value.trim(), message: document.getElementById('message').value.trim() }; // Валидация if (!formData.name || !formData.phone) { this.showNotification('Пожалуйста, заполните имя и телефон', 'error'); return; } // Показываем успешную отправку this.showFormSuccess(form); // В реальном приложении: отправка данных на сервер console.log('Form submitted:', formData); // Сброс формы setTimeout(() => { form.reset(); this.showNotification('Заявка отправлена! Мы перезвоним вам в течение 2 часов.', 'success'); }, 1500); }); // Маска для телефона const phoneInput = document.getElementById('phone'); if (phoneInput) { phoneInput.addEventListener('input', (e) => { let value = e.target.value.replace(/\D/g, ''); if (value.length > 0) { if (value.length <= 3) { value = '+7 (' + value; } else if (value.length <= 6) { value = '+7 (' + value.substring(0, 3) + ') ' + value.substring(3); } else if (value.length <= 8) { value = '+7 (' + value.substring(0, 3) + ') ' + value.substring(3, 6) + '-' + value.substring(6); } else { value = '+7 (' + value.substring(0, 3) + ') ' + value.substring(3, 6) + '-' + value.substring(6, 8) + '-' + value.substring(8, 10); } } e.target.value = value; }); } } showFormSuccess(form) { const submitBtn = form.querySelector('button[type="submit"]'); if (!submitBtn) return; const originalText = submitBtn.innerHTML; // Показываем состояние загрузки submitBtn.innerHTML = 'Отправка...'; submitBtn.disabled = true; // Восстанавливаем через 1.5 секунды setTimeout(() => { submitBtn.innerHTML = originalText; submitBtn.disabled = false; }, 1500); } // ===== Галерея ===== initGallery() { const galleryItems = document.querySelectorAll('.gallery-item'); galleryItems.forEach(item => { item.addEventListener('mouseenter', () => { item.style.transform = 'scale(1.05)'; }); item.addEventListener('mouseleave', () => { item.style.transform = 'scale(1)'; }); // Клик для увеличения item.addEventListener('click', () => { this.openLightbox(item.querySelector('img').src); }); }); } openLightbox(src) { // Создаем лайтбокс const lightbox = document.createElement('div'); lightbox.className = 'lightbox'; lightbox.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.9); display: flex; align-items: center; justify-content: center; z-index: 10000; opacity: 0; animation: fadeIn 0.3s forwards; `; const img = document.createElement('img'); img.src = src; img.style.cssText = ` max-width: 90%; max-height: 90%; object-fit: contain; `; const closeBtn = document.createElement('button'); closeBtn.innerHTML = '×'; closeBtn.style.cssText = ` position: absolute; top: 20px; right: 20px; background: none; border: none; color: white; font-size: 40px; cursor: pointer; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; `; closeBtn.addEventListener('click', () => { lightbox.style.animation = 'fadeOut 0.3s forwards'; setTimeout(() => { if (lightbox.parentNode) { lightbox.parentNode.removeChild(lightbox); } }, 300); }); lightbox.appendChild(img); lightbox.appendChild(closeBtn); document.body.appendChild(lightbox); // Добавляем анимации const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } `; document.head.appendChild(style); // Удаляем стиль после закрытия лайтбокса lightbox.addEventListener('animationend', () => { if (lightbox.style.animationName === 'fadeOut') { document.head.removeChild(style); } }); } // ===== Эффекты для хедера ===== initHeaderEffects() { const header = document.getElementById('header'); window.addEventListener('scroll', () => { if (window.scrollY > 100) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } }); } // ===== Уведомления ===== showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `apple-notification notification-${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 24px; right: 24px; padding: 16px 24px; background: ${type === 'success' ? 'var(--color-green)' : type === 'error' ? 'var(--color-red)' : 'var(--color-blue)'}; color: white; border-radius: var(--radius-md); font-size: 15px; font-weight: 500; z-index: 10001; transform: translateX(120%); animation: slideInRight 0.3s forwards, slideOutRight 0.3s 4.7s forwards; box-shadow: var(--shadow-lg); max-width: 400px; `; document.body.appendChild(notification); // Добавляем анимации const animationStyle = document.createElement('style'); animationStyle.textContent = ` @keyframes slideInRight { from { transform: translateX(120%); } to { transform: translateX(0); } } @keyframes slideOutRight { from { transform: translateX(0); } to { transform: translateX(120%); } } `; document.head.appendChild(animationStyle); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } document.head.removeChild(animationStyle); }, 5000); } // ===== Оптимизация производительности ===== initPerformance() { // Ленивая загрузка изображений if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; const src = img.getAttribute('data-src'); if (src) { img.src = src; img.classList.add('loaded'); } imageObserver.unobserve(img); } }); }); document.querySelectorAll('img[data-src]').forEach(img => { imageObserver.observe(img); }); } // Дебаунс скролл событий let scrollTimeout; window.addEventListener('scroll', () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { // Обработка событий после окончания скролла }, 100); }); // Оптимизация для мобильных устройств if ('ontouchstart' in window) { document.body.classList.add('touch-device'); // Увеличиваем области касания document.querySelectorAll('a, button').forEach(el => { el.style.minHeight = '44px'; el.style.minWidth = '44px'; }); } } // ===== После загрузки ===== onLoad() { // Скрываем загрузчик const pageLoader = document.querySelector('.page-loader'); if (pageLoader) { setTimeout(() => { pageLoader.style.display = 'none'; }, 1000); } // Запускаем начальные анимации setTimeout(() => { document.querySelectorAll('.fade-in').forEach(el => { el.style.animationPlayState = 'running'; }); }, 500); // Инициализируем эффект блоков после загрузки setTimeout(() => { this.initSideBlocksEffect(); }, 1000); } } // Инициализация при загрузке DOM document.addEventListener('DOMContentLoaded', () => { new AppleWebsite(); }); // Глобальная обработка ошибок window.addEventListener('error', (e) => { console.error('Website error:', e.error); }); // Плавный выход window.addEventListener('beforeunload', () => { document.body.style.opacity = '0'; document.body.style.transition = 'opacity 0.3s'; });