91 lines
4.0 KiB
JavaScript
91 lines
4.0 KiB
JavaScript
/*
|
|
* parallax-init — synchronous pre-Blazor primer for ParallaxImage.
|
|
*
|
|
* Why this exists (the Server→WASM "position pop"):
|
|
* App.razor uses InteractiveAuto. On a cold WASM cache the page renders
|
|
* server-side, the Server circuit hydrates and parallax.js register()s
|
|
* (setting data-parallax-active, which cancels the CSS view() animation),
|
|
* then WASM takes over: Blazor disposes the Server component → DisposeAsync
|
|
* → unregister() → data-parallax-active is REMOVED. For the multi-frame gap
|
|
* until WASM's new instance re-register()s (its IntersectionObserver fires
|
|
* asynchronously), the CSS animation resumes and drives background-position-y
|
|
* to its own view()-timeline value, which differs from the JS math — that
|
|
* brief reversion is the visible pop.
|
|
*
|
|
* This script runs ONCE, synchronously, after the DOM is parsed but before
|
|
* Blazor boots, and sets data-parallax-active on every parallax window. That
|
|
* attribute is never tied to a component instance, so it survives the
|
|
* Server→WASM handoff — the CSS animation stays cancelled the whole time and
|
|
* parallax.js remains the sole writer of background-position-y across both
|
|
* render modes. register()/unregister() lifecycle is unchanged; on genuine
|
|
* navigation the element leaves the DOM entirely, so a stale attribute is moot.
|
|
*
|
|
* Why plain JS, not TypeScript:
|
|
* A synchronous pre-Blazor primer must be a classic <script> (no module
|
|
* graph, no async import). The RCL's TS pipeline emits ESNext modules, which
|
|
* are deferred — too late to beat the handoff. This file is therefore a
|
|
* hand-authored static asset, deliberately outside Interop/.
|
|
*
|
|
* Math parity with parallax.ts applyParallax():
|
|
* speed and direction are recovered from the inline custom properties the
|
|
* component already sets (--parallax-from / --parallax-to encode
|
|
* ParallaxSpeed and InvertDirection), then the SAME progress→position formula
|
|
* is applied. Any divergence here would itself be a pop, so the two must stay
|
|
* in lockstep — change one, change the other.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
function clamp(value, min, max) {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|
|
|
|
function prime(win) {
|
|
var styles = getComputedStyle(win);
|
|
var from = parseFloat(styles.getPropertyValue('--parallax-from')) || 0;
|
|
var to = parseFloat(styles.getPropertyValue('--parallax-to')) || 0;
|
|
|
|
// Component encodes: non-inverted → from:0, to:end; inverted → from:end, to:0.
|
|
// Recover the original inputs from whichever endpoint carries the magnitude.
|
|
var invert = from > to;
|
|
var speed = clamp(Math.max(from, to) / 100, 0, 1);
|
|
|
|
var rect = win.getBoundingClientRect();
|
|
var viewportH = window.innerHeight;
|
|
|
|
var progress = invert ? rect.top / viewportH : 1 - rect.top / viewportH;
|
|
progress = clamp(progress, 0, 1);
|
|
|
|
var pos = progress * speed * 100;
|
|
|
|
var layers = win.querySelectorAll(':scope > .layer');
|
|
for (var i = 0; i < layers.length; i++) {
|
|
layers[i].style.backgroundPositionY = pos + '%';
|
|
}
|
|
|
|
// Cancel the CSS animation and adopt the JS value atomically.
|
|
win.setAttribute('data-parallax-active', '');
|
|
}
|
|
|
|
function primeAll() {
|
|
// Match parallax.ts: under reduced motion the module skips its scroll
|
|
// listener and never sets data-parallax-active, leaving the layer at
|
|
// --parallax-from (CSS animation is already `none` via media query).
|
|
// Do the same here so the two paths stay consistent.
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
return;
|
|
}
|
|
|
|
var windows = document.querySelectorAll('.parallax-window');
|
|
for (var i = 0; i < windows.length; i++) {
|
|
prime(windows[i]);
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', primeAll);
|
|
} else {
|
|
primeAll();
|
|
}
|
|
})();
|