PageSourceSearch

https://www.sleek-mag.com/wp-content/themes/sleek-child/assets/js/parallax.js?ver=1779687511

js sleek-mag.com collected 2026-10-02 03:41:57 UTC 6,113 bytes, 167 lines download raw bytes

1/**
2 * SLEEK — homepage column parallax desync (DECK2 p.16-19).
3 *
4 * The right column's inner wrapper ([data-parallax-target], now a child
5 * of .sleek-col--right rather than being the col itself) gets
6 * translateY at 1.6× the page scroll velocity, so it appears to read
7 * "ahead" of the left column. Pure rAF loop, no scroll listener (the
8 * scroll-event flake documented in progress.js applies here too).
9 *
10 * Hard guards:
11 *   - prefers-reduced-motion → no transforms ever.
12 *   - viewport ≤ 768px → no transforms (mobile is single-column stack).
13 *   - target element missing → no-op.
14 *
15 * Clip placement (2026-04-24 refactor):
16 *   Previous implementation set `overflow: hidden` on the grid
17 *   container. That clipped EVERYTHING inside the grid — which blocked
18 *   left-column tiles from breaking out to 100vw (needed for the Print
19 *   Block ribbon).
20 *
21 *   New placement: the outer `.sleek-col--right` acts as the clipping
22 *   box (height = HL, overflow hidden), while its inner child carries
23 *   the transform. The grid no longer clips, so left-column tiles are
24 *   free to extend via negative margin / viewport units.
25 *
26 * Bottom-end alignment:
27 *   Right col outer box is height=HL; the inner translates inside it.
28 *   Page natural scroll = max(HL, HR_wrap_dom) - V ≈ HL - V (since the
29 *   outer is HL and the inner overflow is clipped). Nike banner /
30 *   footer sit below the grid cleanly as before.
31 *
32 *   ResizeObserver re-measures HL when images load late so the cap
33 *   stays in sync with the real left-column height.
34 */
35(function () {
36    'use strict';
37
38    // 1. Reduced motion — bail early.
39    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
40        return;
41    }
42
43    // 2. Mobile breakpoint — bail. Mobile is a single-column stack.
44    var MOBILE_BREAKPOINT = 768;
45    function isMobile() {
46        return window.innerWidth <= MOBILE_BREAKPOINT;
47    }
48
49    var grid;
50    var leftCol;
51    var rightCol;  // outer clipping box
52    var target;    // inner transformed wrapper
53    var resizeObs;
54    var EXTRA_VELOCITY = 0.6; // 1.0 native + 0.6 extra = 1.6× total (tuned 2026-04-23)
55    var LERP = 0.15;          // 0..1; higher = snappier, lower = silkier
56    var current = 0;
57
58    function setClipHeight() {
59        if (!rightCol || !leftCol) return;
60        var h = leftCol.offsetHeight;
61        if (h > 0) {
62            rightCol.style.height = h + 'px';
63            rightCol.style.overflow = 'hidden';
64        }
65    }
66
67    function clearClipHeight() {
68        if (!rightCol) return;
69        rightCol.style.height = '';
70        rightCol.style.overflow = '';
71    }
72
73    function tick() {
74        if (!target) return;
75
76        var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
77
78        // Desired translation: pull the inner wrapper upward as the
79        // page scrolls.
80        var desired = -scrollY * EXTRA_VELOCITY;
81
82        // Clamp so the inner wrapper doesn't slide off the bottom of
83        // its own content. If inner height is HR_inner, the lowest its
84        // top can go is -(HR_inner - clipBoxHeight). We use the clip
85        // box height (outer right col) because that is effectively our
86        // "viewport" for the inner.
87        var innerH = target.offsetHeight;
88        var clipH  = rightCol ? rightCol.offsetHeight : window.innerHeight;
89        var minDesired = Math.min(0, -(innerH - clipH));
90        if (desired < minDesired) desired = minDesired;
91        if (desired > 0) desired = 0;
92
93        // Lerp current toward desired for silky motion.
94        current += (desired - current) * LERP;
95
96        // Snap final pixel when very close to avoid sub-pixel jitter.
97        if (Math.abs(desired - current) < 0.05) current = desired;
98
99        target.style.transform = 'translate3d(0, ' + current.toFixed(2) + 'px, 0)';
100
101        window.requestAnimationFrame(tick);
102    }
103
104    function reset() {
105        if (target) target.style.transform = '';
106        clearClipHeight();
107    }
108
109    function init() {
110        grid     = document.querySelector('[data-parallax-grid]');
111        leftCol  = grid ? grid.querySelector('.sleek-col--left')   : null;
112        rightCol = grid ? grid.querySelector('.sleek-col--right')  : null;
113        target   = grid ? grid.querySelector('[data-parallax-target]') : null;
114
115        if (!grid || !leftCol || !rightCol || !target) return;
116        if (isMobile()) return;
117
118        // Initial measurement. Defer one frame so flex layout settles.
119        window.requestAnimationFrame(function () {
120            setClipHeight();
121        });
122
123        // Late image loads change left col height — re-measure when they do.
124        if (typeof ResizeObserver !== 'undefined') {
125            resizeObs = new ResizeObserver(function () {
126                setClipHeight();
127            });
128            resizeObs.observe(leftCol);
129        } else {
130            // Fallback: measure on every img load in left col.
131            var imgs = leftCol.querySelectorAll('img');
132            for (var i = 0; i < imgs.length; i++) {
133                if (!imgs[i].complete) {
134                    imgs[i].addEventListener('load', setClipHeight, { once: true });
135                }
136            }
137        }
138
139        current = 0;
140        window.requestAnimationFrame(tick);
141    }
142
143    // On viewport resize across the mobile breakpoint, restart or stop.
144    var lastIsMobile = isMobile();
145    window.addEventListener('resize', function () {
146        var nowMobile = isMobile();
147        if (nowMobile !== lastIsMobile) {
148            lastIsMobile = nowMobile;
149            if (nowMobile) {
150                if (resizeObs) { resizeObs.disconnect(); resizeObs = null; }
151                reset();
152                target = null;
153            } else {
154                init();
155            }
156        } else if (!nowMobile) {
157            // Width changed but still desktop — re-measure clip height.
158            setClipHeight();
159        }
160    }, { passive: true });
161
162    if (document.readyState === 'loading') {
163        document.addEventListener('DOMContentLoaded', init);
164    } else {
165        init();
166    }
167})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.