| 1 |
/* Parallax Section — frontend */ |
| 2 |
(function () { |
| 3 |
'use strict'; |
| 4 |
|
| 5 |
var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; |
| 6 |
|
| 7 |
/* ── collect all parallax sections ──────────────────────────────────────── */ |
| 8 |
var sections = []; |
| 9 |
|
| 10 |
function collectSections() { |
| 11 |
sections = Array.prototype.slice.call( |
| 12 |
document.querySelectorAll('.bkps-outer[data-parallax="1"]') |
| 13 |
).map(function (outer) { |
| 14 |
var bg = outer.querySelector('.bkps-bg'); |
| 15 |
var speed = parseFloat(outer.getAttribute('data-speed')) || 0.4; |
| 16 |
return { outer: outer, bg: bg, speed: speed }; |
| 17 |
}).filter(function (s) { return s.bg; }); |
| 18 |
} |
| 19 |
|
| 20 |
/* ── rAF update loop ─────────────────────────────────────────────────────── */ |
| 21 |
var ticking = false; |
| 22 |
|
| 23 |
function updateAll() { |
| 24 |
var scrollY = window.pageYOffset; |
| 25 |
|
| 26 |
sections.forEach(function (s) { |
| 27 |
var rect = s.outer.getBoundingClientRect(); |
| 28 |
var vh = window.innerHeight; |
| 29 |
|
| 30 |
/* only update when on screen */ |
| 31 |
if (rect.bottom < 0 || rect.top > vh) { return; } |
| 32 |
|
| 33 |
/* relative scroll position: 0 when section top at screen bottom, 1 when section bottom at screen top */ |
| 34 |
var progress = (scrollY + vh - (s.outer.offsetTop)) / (vh + s.outer.offsetHeight); |
| 35 |
progress = Math.max(0, Math.min(1, progress)); |
| 36 |
|
| 37 |
/* movement range: ±(speed * 30)% of outer height */ |
| 38 |
var shift = (progress - 0.5) * s.speed * s.outer.offsetHeight; |
| 39 |
|
| 40 |
s.bg.style.transform = 'translate3d(0, ' + shift + 'px, 0)'; |
| 41 |
}); |
| 42 |
ticking = false; |
| 43 |
} |
| 44 |
|
| 45 |
function onScroll() { |
| 46 |
if (!ticking) { |
| 47 |
ticking = true; |
| 48 |
requestAnimationFrame(updateAll); |
| 49 |
} |
| 50 |
} |
| 51 |
|
| 52 |
/* ── init ────────────────────────────────────────────────────────────────── */ |
| 53 |
function init() { |
| 54 |
if (reducedMotion) { return; } |
| 55 |
collectSections(); |
| 56 |
if (!sections.length) { return; } |
| 57 |
|
| 58 |
window.addEventListener('scroll', onScroll, { passive: true }); |
| 59 |
window.addEventListener('resize', function () { |
| 60 |
collectSections(); /* recollect offsets on resize */ |
| 61 |
updateAll(); |
| 62 |
}); |
| 63 |
/* initial position */ |
| 64 |
requestAnimationFrame(updateAll); |
| 65 |
} |
| 66 |
|
| 67 |
if (document.readyState === 'loading') { |
| 68 |
document.addEventListener('DOMContentLoaded', init); |
| 69 |
} else { |
| 70 |
init(); |
| 71 |
} |
| 72 |
}()); |
| 73 |
|