/** * Parallax for .section-has-bg — transform on .section-bg only. * JS on all viewports (background-attachment: fixed breaks inside overflow:hidden). */ (function () { 'use strict'; if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; var SPEED = 0.3; var OVERFLOW_RATIO = 0.35; var MIN_OVERFLOW_PX = 72; var items = []; var ticking = false; function scrollY() { return window.pageYOffset || document.documentElement.scrollTop || 0; } function fitBgToSection(section, bg) { var sectionH = section.offsetHeight; var sectionW = section.offsetWidth; if (!sectionH || !sectionW) return; var overflow = Math.max(Math.ceil(sectionH * OVERFLOW_RATIO), MIN_OVERFLOW_PX); var bgH = sectionH + overflow; var bgW = sectionW + Math.ceil(overflow * 0.5); bg.style.width = bgW + 'px'; bg.style.minWidth = bgW + 'px'; bg.style.height = bgH + 'px'; bg.style.minHeight = bgH + 'px'; bg.style.top = '50%'; bg.style.left = '50%'; bg.style.right = 'auto'; bg.style.bottom = 'auto'; } function maxShift(section, bg) { var sectionH = section.offsetHeight; var bgH = bg.offsetHeight; if (!sectionH || !bgH) { return Math.max(Math.ceil(sectionH * OVERFLOW_RATIO / 2), MIN_OVERFLOW_PX / 2); } return Math.max(0, (bgH - sectionH) / 2 - 2); } function rawParallaxY(section, y) { return (y - section.offsetTop) * SPEED; } function clampY(value, shift) { if (!shift) return 0; return Math.max(-shift, Math.min(shift, value)); } function applyTransform(item, y) { fitBgToSection(item.section, item.bg); var shift = maxShift(item.section, item.bg); var offset = clampY(rawParallaxY(item.section, y), shift); item.bg.style.setProperty('--parallax-y', offset + 'px'); item.bg.style.transform = 'translate3d(-50%, calc(-50% + ' + offset + 'px), 0)'; } function init() { document.querySelectorAll('.section-has-bg').forEach(function (section) { var bg = section.querySelector('.section-bg'); if (!bg) return; section.classList.add('parallax-js'); var item = { section: section, bg: bg, active: false }; items.push(item); fitBgToSection(section, bg); function syncPosition() { applyTransform(item, scrollY()); } syncPosition(); if (bg.tagName === 'IMG' && !bg.complete) { bg.addEventListener('load', syncPosition, { once: true }); } bg.addEventListener('error', syncPosition, { once: true }); }); if (!items.length) return; if ('IntersectionObserver' in window) { var io = new IntersectionObserver( function (entries) { for (var i = 0; i < entries.length; i++) { var entry = entries[i]; for (var j = 0; j < items.length; j++) { if (items[j].section === entry.target) { items[j].active = entry.isIntersecting; break; } } } requestTick(); }, { root: null, rootMargin: '80px 0px', threshold: 0 } ); items.forEach(function (item) { io.observe(item.section); }); } else { items.forEach(function (item) { item.active = true; }); } window.addEventListener('scroll', requestTick, { passive: true }); window.addEventListener('resize', requestTick, { passive: true }); requestTick(); } function requestTick() { if (ticking) return; ticking = true; requestAnimationFrame(update); } function update() { ticking = false; var y = scrollY(); for (var i = 0; i < items.length; i++) { var item = items[i]; if (!item.active) continue; applyTransform(item, y); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();