PageSourceSearch

https://yaguara-huila.gov.co/SiteAssets/v1/assets/components/dinamic-scroll/scroll-animations.js

js yaguara-huila.gov.co collected 2026-10-02 22:05:52 UTC 7,333 bytes, 238 lines download raw bytes

1// =========================================================
2// Scroll Reveal Animation Handler (versión extendida: fade + wipe + zoom)
3// =========================================================
4class ScrollReveal {
5  /**
6   * @param {Object}  options
7   * @param {number}  [options.threshold=0.15]   Porción visible para activar
8   * @param {boolean} [options.reanimate=true]   Re-animar al volver a entrar
9   * @param {string}  [options.rootMargin="0px 0px -50px 0px"]
10   * @param {boolean} [options.forceMotion=false]Ignora prefers-reduced-motion (debug)
11   */
12  constructor(options = {}) {
13    this.elements = [];
14    this.windowHeight = window.innerHeight;
15    this.threshold = options.threshold ?? 0.15;
16    this.reanimate = options.reanimate ?? true;
17    this.rootMargin = options.rootMargin ?? "0px 0px -50px 0px";
18    this.forceMotion = options.forceMotion ?? false;
19
20    const reducePref = window.matchMedia(
21      "(prefers-reduced-motion: reduce)"
22    ).matches;
23    this.reduceMotion = this.forceMotion ? false : reducePref;
24
25    this.init();
26  }
27
28  init() {
29    // Todas las clases soportadas (reveal + fade + wipe + zoom)
30    this.elements = document.querySelectorAll(
31      [
32        ".scroll-reveal",
33        ".scroll-reveal-left",
34        ".scroll-reveal-right",
35        ".scroll-reveal-scale",
36        ".scroll-reveal-title",
37        ".scroll-reveal-card",
38        ".scroll-reveal-image",
39        ".scroll-reveal-fade",
40        ".scroll-fade",
41        ".scroll-fade-children",
42        ".scroll-wipe",
43        ".scroll-wipe-left",
44        ".scroll-wipe-right",
45        ".scroll-wipe-top",
46        ".scroll-zoom",
47        ".scroll-zoom-children",
48      ].join(", ")
49    );
50
51    // Fallback: si no hay IO, deja visibles sin animación
52    if (!("IntersectionObserver" in window)) {
53      this.elements.forEach((el) => el.classList.add("in-view"));
54      return;
55    }
56
57    this.setupIntersectionObserver();
58    this.setupScrollListener();
59    this.checkElements();
60  }
61
62  setupIntersectionObserver() {
63    const options = { threshold: this.threshold, rootMargin: this.rootMargin };
64
65    this.observer = new IntersectionObserver((entries) => {
66      entries.forEach((entry) => {
67        const el = entry.target;
68
69        if (this.reduceMotion) {
70          this.revealElement(el);
71          return;
72        }
73
74        if (entry.isIntersecting) {
75          this.revealElement(el);
76        } else {
77          this.handleExit(el);
78        }
79      });
80    }, options);
81
82    this.elements.forEach((el) => this.observer.observe(el));
83  }
84
85  setupScrollListener() {
86    let ticking = false;
87    const onScroll = () => {
88      if (!ticking) {
89        requestAnimationFrame(() => {
90          this.checkElements();
91          ticking = false;
92        });
93        ticking = true;
94      }
95    };
96
97    window.addEventListener("scroll", onScroll, { passive: true });
98    window.addEventListener("resize", () => {
99      this.windowHeight = window.innerHeight;
100      this.checkElements();
101    });
102  }
103
104  checkElements() {
105    this.elements.forEach((el) => {
106      if (!this.reanimate && el.classList.contains("revealed")) return;
107
108      if (this.isElementInViewport(el)) {
109        this.revealElement(el);
110      } else if (this.reanimate) {
111        this.handleExit(el);
112      }
113    });
114  }
115
116  isElementInViewport(el) {
117    const rect = el.getBoundingClientRect();
118    const h = rect.height || 0;
119    const tpx = h * this.threshold;
120    return rect.top <= this.windowHeight - tpx && rect.bottom >= tpx;
121  }
122
123  revealElement(el) {
124    el.classList.add("in-view");
125    el.classList.remove("out-view");
126    el.classList.add("revealed");
127
128    el.dispatchEvent(
129      new CustomEvent("elementRevealed", { detail: { element: el } })
130    );
131  }
132
133  handleExit(el) {
134    el.classList.remove("in-view");
135
136    // Wipe usa .out-view para animar salida
137    if (this.isWipe(el)) el.classList.add("out-view");
138
139    if (this.reanimate) el.classList.remove("revealed");
140  }
141
142  resetElement(el) {
143    el.classList.remove("revealed", "in-view", "out-view");
144  }
145
146  addElement(el) {
147    if (!el) return;
148
149    if (this.reduceMotion) {
150      this.revealElement(el);
151      return;
152    }
153    if (this.observer) this.observer.observe(el);
154
155    if (!this.elements.includes(el)) {
156      this.elements = [...this.elements, el];
157    }
158  }
159
160  reset() {
161    this.elements.forEach((el) => this.resetElement(el));
162    this.checkElements();
163  }
164
165  isWipe(el) {
166    return (
167      el.classList.contains("scroll-wipe") ||
168      el.classList.contains("scroll-wipe-left") ||
169      el.classList.contains("scroll-wipe-right") ||
170      el.classList.contains("scroll-wipe-top")
171    );
172  }
173}
174
175/* -------------------------------------------------------
176   Utilidades opcionales
177------------------------------------------------------- */
178
179// Heurística ligera para auto-etiquetar (desactivada por defecto)
180function autoAddScrollReveal() {
181  const sets = [
182    { selector: "h1, h2, h3, h4, h5, h6", cls: "scroll-reveal-title" },
183    { selector: ".card, .product, .service", cls: "scroll-reveal-card" },
184    { selector: "img", cls: "scroll-reveal-image" },
185    { selector: "p:not(.no-reveal), .content-section", cls: "scroll-fade" },
186  ];
187
188  sets.forEach(({ selector, cls }) => {
189    document.querySelectorAll(selector).forEach((el) => {
190      if (
191        el.classList.contains("scroll-reveal") ||
192        el.classList.contains("scroll-reveal-left") ||
193        el.classList.contains("scroll-reveal-right") ||
194        el.classList.contains("scroll-reveal-scale") ||
195        el.classList.contains("scroll-reveal-title") ||
196        el.classList.contains("scroll-reveal-card") ||
197        el.classList.contains("scroll-reveal-image") ||
198        el.classList.contains("scroll-reveal-fade") ||
199        el.classList.contains("scroll-fade") ||
200        el.classList.contains("scroll-fade-children") ||
201        el.classList.contains("scroll-wipe") ||
202        el.classList.contains("scroll-wipe-left") ||
203        el.classList.contains("scroll-wipe-right") ||
204        el.classList.contains("scroll-wipe-top") ||
205        el.classList.contains("scroll-zoom") ||
206        el.classList.contains("scroll-zoom-children")
207      )
208        return;
209
210      el.classList.add(cls);
211    });
212  });
213}
214
215// Inicialización
216document.addEventListener("DOMContentLoaded", () => {
217  // autoAddScrollReveal(); // <- activa si quieres auto-etiquetar
218  const scrollReveal = new ScrollReveal({
219    threshold: 0.15,
220    reanimate: true, // false si solo quieres animar una vez
221    rootMargin: "0px 0px -50px 0px",
222    forceMotion: false, // true para ignorar prefers-reduced-motion (debug)
223  });
224  window.scrollReveal = scrollReveal;
225});
226
227// API manual: agrega clase y registra en el observer
228function addScrollReveal(selector, animationType = "scroll-fade") {
229  document.querySelectorAll(selector).forEach((el) => {
230    el.classList.add(animationType);
231    if (window.scrollReveal) window.scrollReveal.addElement(el);
232  });
233}
234
235// CommonJS export
236if (typeof module !== "undefined" && module.exports) {
237  module.exports = { ScrollReveal, addScrollReveal, autoAddScrollReveal };
238}

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.