PageSourceSearch

https://vebcognc.org/wp-content/plugins/divi-essential/assets/js/dnxte-horizontal-scroll.js?ver=1783442540

js vebcognc.org collected 2026-10-05 19:17:34 UTC 8,807 bytes, 245 lines download raw bytes

1/**
2 * Horizontal Scroll — Frontend Runtime
3 * GSAP ScrollTrigger-powered horizontal scroll for Divi Sections.
4 * Modes: pinned, snap, parallax, gallery
5 *
6 * @package DNXTE
7 */
8(function () {
9  'use strict';
10
11  function DnxteHScroll(el) {
12    this.el = el;
13    this._tweens = [];
14    this._sts = [];
15    this._injected = [];
16    this._destroyed = false;
17
18    var gsap = window.gsap;
19    var ST = window.ScrollTrigger;
20    if (!gsap || !ST) return;
21
22    this.gsap = gsap;
23    this.ST = ST;
24
25    // Read config from data attributes
26    this.type        = el.getAttribute('data-hscroll-type') || 'pinned';
27    this.speed       = parseFloat(el.getAttribute('data-hscroll-speed')) || 1;
28    this.scrubRaw    = el.getAttribute('data-hscroll-scrub') || '1';
29    this.itemGap     = Math.max(0, parseFloat(el.getAttribute('data-hscroll-item-gap')) || 0);
30    this.anticipate  = el.getAttribute('data-hscroll-anticipate') === 'true';
31    this.contentPx   = el.getAttribute('data-hscroll-content-parallax') === 'true';
32    this.contentSpd  = parseFloat(el.getAttribute('data-hscroll-content-speed')) || 0.5;
33    this.enterFx     = el.getAttribute('data-hscroll-enter') || 'none';
34    this.stagger     = el.getAttribute('data-hscroll-stagger') !== 'false';
35    this.staggerDel  = parseFloat(el.getAttribute('data-hscroll-stagger-delay')) || 0.15;
36
37    this._init();
38  }
39
40  var proto = DnxteHScroll.prototype;
41
42  proto._init = function () {
43    var self = this;
44    var gsap = this.gsap;
45    var ST = this.ST;
46    var section = this.el;
47
48    // Find child "slides" — try rows first, then columns inside a single row
49    var slides = Array.prototype.slice.call(section.querySelectorAll(':scope > .et_pb_row, :scope > .et_pb_row_inner'));
50
51    // If only 1 row, use its columns as slides (most common DE use case)
52    if (slides.length === 1) {
53      var cols = slides[0].querySelectorAll(':scope > .et_pb_column, :scope > .et_pb_column_inner');
54      if (cols.length >= 2) {
55        slides = Array.prototype.slice.call(cols);
56      }
57    }
58
59    // Fallback: direct children that aren't decorations
60    if (!slides.length) {
61      slides = Array.prototype.slice.call(section.children).filter(function (c) {
62        return !c.classList.contains('et_pb_section_video_bg') &&
63               !c.classList.contains('et_pb_background_overlay') &&
64               c.tagName !== 'STYLE' && c.tagName !== 'SCRIPT';
65      });
66    }
67
68    if (!slides || slides.length < 2) return;
69
70    // Section: only clip horizontal overflow so the off-screen slides don't
71    // leak. Leaves Divi's own min-height / height / padding / background alone.
72    section.style.setProperty('overflow', 'hidden', 'important');
73    section.style.position = 'relative';
74
75    // Track parent row for potential cleanup, but don't override its styles —
76    // whatever Divi row/column settings the user configured stay intact.
77    var parentRow = slides[0].parentElement;
78    if (parentRow && parentRow !== section &&
79        (parentRow.classList.contains('et_pb_row') || parentRow.classList.contains('et_pb_row_inner'))) {
80      this._parentRow = parentRow;
81    }
82
83    // Inject horizontal flex wrapper — IN FLOW (not absolute). Section picks
84    // up its natural height from the slides' content. No height/width/padding
85    // forcing — Divi's column/row widths and paddings drive everything.
86    var wrapper = document.createElement('div');
87    wrapper.className = 'dnxte-hscroll-wrapper';
88    wrapper.style.cssText = [
89      'display:flex', 'flex-wrap:nowrap',
90      'gap:' + this.itemGap + 'px',
91      'will-change:transform'
92    ].join(';') + ';';
93
94    // Move slides into wrapper. Only prevent flex shrink/grow so the slides'
95    // Divi-configured widths survive when their total exceeds the viewport.
96    var items = [];
97    slides.forEach(function (slide) {
98      slide.style.setProperty('flex-shrink', '0', 'important');
99      slide.style.setProperty('flex-grow', '0', 'important');
100      wrapper.appendChild(slide);
101      items.push(slide);
102    });
103
104    section.appendChild(wrapper);
105    this._injected.push(wrapper);
106
107    // wrapper is block-level inside the section's content-box, so its
108    // clientWidth already equals (section.offsetWidth - paddingLeft - paddingRight).
109    // scrollWidth - clientWidth = how much the wrapper must translate left so
110    // the last item's right edge lands at the content-area's right edge —
111    // section padding (left and right) is naturally respected.
112    var scrollDist = wrapper.scrollWidth - wrapper.clientWidth;
113    if (scrollDist <= 0) return;
114
115    // Parse scrub value
116    var scrubValue = this.scrubRaw === 'true' ? true : parseFloat(this.scrubRaw);
117    if (isNaN(scrubValue)) scrubValue = 1;
118
119    // ── Main horizontal scroll timeline ──
120    var stConfig = {
121      trigger: section,
122      start: 'top top',
123      end: function () { return '+=' + (scrollDist / self.speed); },
124      scrub: scrubValue,
125      pin: true,
126      anticipatePin: this.anticipate ? 1 : 0,
127    };
128
129    // Snap mode
130    if (this.type === 'snap' && items.length > 1) {
131      stConfig.snap = {
132        snapTo: 1 / (items.length - 1),
133        duration: { min: 0.2, max: 0.5 },
134        ease: 'power1.inOut'
135      };
136    }
137
138    var mainTL = gsap.to(wrapper, {
139      x: -scrollDist,
140      ease: 'none',
141      scrollTrigger: stConfig
142    });
143    this._tweens.push(mainTL);
144    if (mainTL.scrollTrigger) this._sts.push(mainTL.scrollTrigger);
145    this._mainTL = mainTL;
146
147    // ── Content Parallax ──
148    if (this.contentPx || this.type === 'parallax') {
149      items.forEach(function (item) {
150        var content = item.querySelector('.et_pb_column, .et_pb_text, .et_pb_blurb, .et_pb_heading') || item.firstElementChild;
151        if (!content) return;
152        var tween = gsap.to(content, {
153          x: function () { return -window.innerWidth * self.contentSpd * 0.5; },
154          scrollTrigger: {
155            trigger: item,
156            containerAnimation: mainTL,
157            start: 'left right',
158            end: 'right left',
159            scrub: true
160          }
161        });
162        self._tweens.push(tween);
163      });
164    }
165
166    // ── Enter Effects ──
167    if (this.enterFx !== 'none') {
168      items.forEach(function (item) {
169        var els = item.querySelectorAll('.et_pb_text, .et_pb_blurb, .et_pb_heading, .et_pb_button, img, h1, h2, h3, p');
170        if (!els.length) els = [item];
171
172        Array.prototype.forEach.call(els, function (target, j) {
173          var fromVars = { opacity: 0 };
174          var toVars = { opacity: 1, duration: 0.6, ease: 'power2.out' };
175
176          switch (self.enterFx) {
177            case 'slideUp':   fromVars.y = 50; toVars.y = 0; break;
178            case 'slideDown': fromVars.y = -50; toVars.y = 0; break;
179            case 'scale':     fromVars.scale = 0.85; toVars.scale = 1; break;
180            case 'blur':      fromVars.filter = 'blur(10px)'; toVars.filter = 'blur(0px)'; break;
181          }
182
183          gsap.set(target, fromVars);
184          var tween = gsap.to(target, Object.assign({}, toVars, {
185            delay: self.stagger ? j * self.staggerDel : 0,
186            scrollTrigger: {
187              trigger: item,
188              containerAnimation: mainTL,
189              start: 'left 80%',
190              toggleActions: 'play none none reverse'
191            }
192          }));
193          self._tweens.push(tween);
194        });
195      });
196    }
197
198    ST.refresh();
199  };
200
201  proto.destroy = function () {
202    this._destroyed = true;
203    this._tweens.forEach(function (t) { try { t.kill(); } catch (e) {} });
204    this._sts.forEach(function (st) { try { st.kill(); } catch (e) {} });
205    this._injected.forEach(function (el) {
206      if (el && el.parentNode) el.parentNode.removeChild(el);
207    });
208    if (this.el) this.el.style.overflow = '';
209    this._tweens = []; this._sts = []; this._injected = [];
210  };
211
212  // ── Auto-init ──
213  function initAll() {
214    var els = document.querySelectorAll('[data-hscroll-init="true"]');
215    els.forEach(function (el) {
216      if (el._dnxteHScroll) return;
217      el._dnxteHScroll = new DnxteHScroll(el);
218    });
219  }
220
221  function boot() {
222    initAll();
223    if (document.body) {
224      var observer = new MutationObserver(function (mutations) {
225        var shouldInit = false;
226        mutations.forEach(function (m) { if (m.addedNodes && m.addedNodes.length) shouldInit = true; });
227        if (shouldInit) initAll();
228      });
229      observer.observe(document.body, { childList: true, subtree: true });
230    }
231  }
232
233  if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
234    window.DnxteHScroll = DnxteHScroll;
235    return;
236  }
237
238  if (document.readyState === 'loading') {
239    document.addEventListener('DOMContentLoaded', boot);
240  } else {
241    boot();
242  }
243
244  window.DnxteHScroll = DnxteHScroll;
245})();

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.