PageSourceSearch

https://husl.gold/wp-content/themes/hwt-husl-theme/assets/js/hwt-viewport.js?ver=1.2.0

js husl.gold collected 2026-10-01 16:02:12 UTC 8,750 bytes, 246 lines download raw bytes

1/* Heiser WebTec – WAAPI Viewport Reveal (Elementor-aware)
2 * Beobachtet .hwt-watch / [data-hwt-watch] und animiert per Web Animations API
3 * von blur/translate/scale ins Endstadium (opacity:1, blur(0), transform:perspective(1000px))
4 * Zusätzlich: Allen Elementor-Elementen wird .hwt-is-not-visible gegeben
5 * und beim Eintritt in den Viewport entfernt (CSS-first-Workflow).
6 */
7(() => {
8  'use strict';
9
10  // --- Konfiguration ---
11  const SELECTOR           = '.hwt-watch, [data-hwt-watch]';
12  const ELEMENTOR_SELECTOR = '.elementor-widget, .elementor-column, .elementor-section, .elementor-element';
13  const NOT_VISIBLE_CLASS  = 'hwt-is-not-visible';
14  const VISIBLE_CLASS = 'hwt-is-visible';
15
16const VIS_RATIO    = window.matchMedia('(max-width: 767px)').matches ? 0.1 : 0.5;
17const ROOT_MARGIN  = window.matchMedia('(max-width: 767px)').matches ? '0px 0px 0px 0px' : '0px 0px -10% 0px';
18
19  const DEFAULTS = {
20    duration: 1000,                         // ms
21    easing: 'cubic-bezier(.2,.65,.25,1)',   // knackig, aber smooth
22    translateY: 24,                         // px Start-Offset
23    blur: 30,                                // px Start-Blur
24    scale: 0.92,                            // leicht kleiner am Start
25    delay: 0
26  };
27
28  const $$ = (root = document, sel = SELECTOR) => Array.from(root.querySelectorAll(sel));
29
30  // prefers-reduced-motion respektieren
31  const REDUCED = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
32
33  // Sichtbarkeitsquote (0..1)
34  const visibleRatio = (el) => {
35    const r  = el.getBoundingClientRect();
36    const vh = window.innerHeight || document.documentElement.clientHeight;
37    const vw = window.innerWidth  || document.documentElement.clientWidth;
38    if (r.width <= 0 || r.height <= 0) return 0;
39    const visH = Math.max(0, Math.min(r.bottom, vh) - Math.max(r.top, 0));
40    const visW = Math.max(0, Math.min(r.right,  vw) - Math.max(r.left, 0));
41    return (visH * visW) / (r.width * r.height);
42  };
43
44  const getRatio = (el) => {
45    // Optional per data-hwt-threshold überschreibbar
46    const a = parseFloat(el.getAttribute('data-hwt-threshold'));
47    return Number.isFinite(a) ? a : VIS_RATIO;
48  };
49
50  // Per-Element Overrides via Data-Attribute (Dauer, Delay etc.)
51  const readOpts = (el) => ({
52    duration: parseInt(el.getAttribute('data-hwt-duration')) || DEFAULTS.duration,
53    easing: el.getAttribute('data-hwt-easing') || DEFAULTS.easing,
54    translateY: parseFloat(el.getAttribute('data-hwt-translate')) || DEFAULTS.translateY,
55    blur: parseFloat(el.getAttribute('data-hwt-blur')) || DEFAULTS.blur,
56    scale: parseFloat(el.getAttribute('data-hwt-scale')) || DEFAULTS.scale,
57    delay: parseInt(el.getAttribute('data-hwt-delay')) || DEFAULTS.delay,
58  });
59
60  // Initialzustand NUR via JS setzen (kein CSS nötig) – für .hwt-watch
61  const setInitial = (el, opts) => {
62    if (el.__hwtInit) return;
63    el.style.opacity = '0';
64    el.style.filter = `blur(${opts.blur}px)`;
65    el.style.transform = `perspective(1000px) translateY(${opts.translateY}px) scale(${opts.scale})`;
66    el.style.willChange = 'opacity, filter, transform';
67    el.__hwtInit = true;
68  };
69
70  // Animation mit WAAPI – läuft 1x, bleibt im Endzustand stehen
71  const animateIn = (el) => {
72    if (el.__hwtDone || REDUCED) {
73      // Bei reduced-motion: sofortiger Endzustand
74      el.style.opacity = '1';
75      el.style.filter = 'blur(0)';
76      el.style.transform = 'perspective(1000px)';
77      el.style.willChange = '';
78      el.__hwtDone = true;
79      return;
80    }
81
82    const o = readOpts(el);
83    // Falls WAAPI fehlt, kurzer CSS-Notfall-Transition-Fallback:
84    if (!('animate' in el)) {
85      el.style.transition = `opacity ${o.duration}ms ${o.easing}, filter ${o.duration}ms ${o.easing}, transform ${o.duration}ms ${o.easing}`;
86      requestAnimationFrame(() => {
87        el.style.opacity = '1';
88        el.style.filter = 'blur(0)';
89        el.style.transform = 'perspective(1000px)';
90      });
91      el.__hwtDone = true;
92      return;
93    }
94
95    const anim = el.animate(
96      [
97        {
98          opacity: 0,
99          filter: `blur(${o.blur}px)`,
100          transform: `perspective(1000px) translateY(${o.translateY}px) scale(${o.scale})`
101        },
102        {
103          opacity: 1,
104          filter: 'blur(0px)',
105          transform: 'perspective(1000px)'
106        }
107      ],
108      {
109        duration: o.duration,
110        easing: o.easing,
111        delay: o.delay,
112        fill: 'forwards' // Endzustand beibehalten
113      }
114    );
115
116    anim.onfinish = () => {
117      el.style.opacity = '1';
118      el.style.filter = 'blur(0)';
119      el.style.transform = 'perspective(1000px)';
120      el.style.willChange = '';
121      el.__hwtDone = true;
122    };
123  };
124
125  // IO Setup
126  let io = null;
127  const buildThresholds = (target) => {
128    const steps = [0, 0.1, 0.25, 0.35, 0.45, target, 0.6, 0.75, 0.9, 1];
129    return Array.from(new Set(steps)).sort((a, b) => a - b);
130    // dichtere Steps um target sorgen fürs Auslösen nahe der Zielquote
131  };
132
133  const ensureObserver = () => {
134    if (io) return io;
135    if ('IntersectionObserver' in window) {
136      io = new IntersectionObserver((entries) => {
137        entries.forEach((entry) => {
138          const el   = entry.target;
139          const need = getRatio(el);
140          const ratio = entry.intersectionRatio;
141
142          // 1) Elementor: Sichtbarkeitsklasse toggeln
143          if (el.matches(ELEMENTOR_SELECTOR)) {
144  if (ratio >= need) {
145    el.classList.remove(NOT_VISIBLE_CLASS);
146    el.classList.add(VISIBLE_CLASS);
147  } else {
148    el.classList.remove(VISIBLE_CLASS);
149    el.classList.add(NOT_VISIBLE_CLASS);
150  }
151}
152
153          // 2) Spezielle .hwt-watch-Animation
154          if (el.matches(SELECTOR) && ratio >= need) {
155            animateIn(el);
156            io.unobserve(el); // nur einmal animieren
157          }
158        });
159      }, {
160        root: null,
161        threshold: buildThresholds(VIS_RATIO),
162        rootMargin: ROOT_MARGIN
163      });
164    }
165    return io;
166  };
167
168  // Elemente registrieren
169  const register = (root = document) => {
170    const obs = ensureObserver();
171
172    // a) ALLE Elementor-Elemente: Startzustand -> not-visible + beobachten
173    const allEl = $$(root, ELEMENTOR_SELECTOR);
174    allEl.forEach((el) => {
175      el.classList.add(NOT_VISIBLE_CLASS);
176      if (obs) {
177        obs.observe(el);
178        // Falls beim Laden schon genug sichtbar, sofort entfernen
179        if (visibleRatio(el) >= getRatio(el)) el.classList.remove(NOT_VISIBLE_CLASS);
180        if (visibleRatio(el) >= getRatio(el)) el.classList.add(VISIBLE_CLASS);
181      } else {
182        // Fallback ohne IO
183        if (visibleRatio(el) >= getRatio(el)) el.classList.remove(NOT_VISIBLE_CLASS);
184        if (visibleRatio(el) >= getRatio(el)) el.classList.add(VISIBLE_CLASS);
185      }
186    });
187
188    // b) Nur .hwt-watch: Initial JS-Styles + beobachten + ggf. sofort animieren
189    const animEls = $$(root, SELECTOR);
190    animEls.forEach((el) => {
191      setInitial(el, readOpts(el));
192      if (obs) {
193        obs.observe(el);
194        if (visibleRatio(el) >= getRatio(el)) {
195          animateIn(el);
196          obs.unobserve(el);
197        }
198      } else {
199        if (visibleRatio(el) >= getRatio(el)) animateIn(el);
200      }
201    });
202  };
203
204  // Elementor-Integration + DOM-Änderungen beachten
205  const installMutationObserver = () => {
206    const mo = new MutationObserver((muts) => {
207      for (const m of muts) {
208        if (!m.addedNodes || !m.addedNodes.length) continue;
209        m.addedNodes.forEach((n) => {
210          if (!(n instanceof Element)) return;
211          if (n.matches(ELEMENTOR_SELECTOR) || n.matches(SELECTOR)) register(n);
212          register(n); // auch Kinder scannen
213        });
214      }
215    });
216    mo.observe(document.body, { childList: true, subtree: true });
217  };
218
219  const start = () => {
220    register(document);
221    installMutationObserver();
222  };
223
224  // Elementor-sicher booten
225  const boot = () => {
226    const run = () => {
227      if (window.elementorFrontend && window.elementorFrontend.hooks) {
228        elementorFrontend.hooks.addAction('frontend/element_ready/global', (scope) => {
229          const node = scope[0] || scope;
230          register(node);
231        });
232      }
233      start();
234    };
235    if (window.elementorFrontend && window.elementorFrontend.on) {
236      window.jQuery(window).on('elementor/frontend/init', run);
237    } else if (document.readyState === 'loading') {
238      document.addEventListener('DOMContentLoaded', run);
239    } else {
240      run();
241    }
242  };
243
244  boot();
245})();
246

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.