PageSourceSearch

https://antenne-trier.com/wp-content/plugins/breakdance/plugin/ani…/js/entrance.js?bd_ver=3.0.0-beta.9

js antenne-trier.com collected 2026-10-05 09:45:24 UTC 10,695 bytes, 396 lines download raw bytes

1/* global gsap, ScrollTrigger, imagesLoaded */
2(function () {
3  const {
4    mergeObjects,
5    onResize,
6    matchMedia,
7    prefersReducedMotion,
8    isBuilder
9  } = BreakdanceFrontend.utils;
10
11  // ScrollTrigger.refresh(true) uses a "safe" code path that skips the
12  // plugin's lazy self-registration, so it reads `document` off an
13  // uninitialized internal and throws
14  // "Cannot read properties of undefined (reading 'fullscreenElement')"
15  // when it runs before gsap.registerPlugin(ScrollTrigger). This can happen
16  // because gsap and ScrollTrigger are loaded async, so ScrollTrigger may
17  // evaluate before gsap and never auto-register. Only refresh once an
18  // animation instance has registered the plugin; if it hasn't, there are no
19  // ScrollTrigger instances to refresh anyway. gsap.core.globals() with no
20  // args reads the registered globals without mutating them.
21  function isScrollTriggerRegistered() {
22    return (
23      typeof gsap !== 'undefined' &&
24      typeof ScrollTrigger !== 'undefined' &&
25      !!gsap.core?.globals?.().ScrollTrigger
26    );
27  }
28
29  function refreshScrollTrigger() {
30    if (!isScrollTriggerRegistered()) return;
31    ScrollTrigger.refresh(true);
32  }
33
34  class BreakdanceEntrance {
35    enabledClass = 'breakdance-animation-enabled';
36    beforeClass = 'is-before';
37    animatingClass = 'is-animating';
38    completedClass = 'is-animated';
39
40    defaultOptions = {
41      animation_type: null,
42      duration: { number: 500, unit: 'ms', style: '500ms' },
43      delay: { number: 0, unit: 'ms', style: '0ms' },
44      advanced: {
45        distance: { number: 100, unit: "px", style: "100px" },
46        offset: { number: 0, unit: 'px', style: '0px' },
47        ease: 'power1.out',
48        anchorPlacement: 'top bottom',
49        once: false,
50        disable_at: null
51      }
52    };
53
54    delay = 0;
55    initialized = false;
56
57    constructor(selector, options) {
58      gsap.registerPlugin(ScrollTrigger);
59
60      this.cleanup = this.cleanup.bind(this);
61      this.reset = this.reset.bind(this);
62
63      this.selector = selector;
64      this.options = mergeObjects(this.defaultOptions, options);
65      this.rootEl = document.documentElement;
66
67      this.init();
68    }
69
70    getAnimations() {
71      const distance = this.options.advanced.distance.style;
72      const unit = this.options.advanced.distance.unit;
73
74      return {
75        fade: [
76          { autoAlpha: 0 },
77          { autoAlpha: 1 }
78        ],
79
80        slideUp: [
81          { y: distance },
82          { y: `0${unit}` }
83        ],
84        slideDown: [
85          { y: `-=${distance}` },
86          { y: `0${unit}` }
87        ],
88        slideLeft: [
89          { x: `-=${distance}` },
90          { x: `0${unit}` }
91        ],
92        slideRight: [
93          { x: distance },
94          { x: `0${unit}` }
95        ],
96
97        flipUp: [
98          { perspective: 2500, rotateX: `-=${distance}` },
99          { rotateX: `0${unit}` }
100        ],
101
102        flipDown: [
103          { perspective: 2500, rotateX: distance },
104          { rotateX: `0${unit}` }
105        ],
106        flipLeft: [
107          { perspective: 2500, rotateY: `-=${distance}` },
108          { rotateY: `0${unit}` }
109        ],
110        flipRight: [
111          { perspective: 2500, rotateY: distance },
112          { rotateY: `0${unit}` }
113        ],
114
115        zoomIn: [
116          { scale: 0.6 },
117          { scale: 1 }
118        ],
119        zoomOut: [
120          { scale: 1.2 },
121          { scale: 1 }
122        ],
123      };
124    }
125
126    canAnimate() {
127      const breakpoint = this.options.advanced.disable_at;
128      if (!breakpoint) return true;
129      return !matchMedia(breakpoint);
130    }
131
132    getDuration(value) {
133      if (!value) return value;
134      if (value.unit === 's') return value.number;
135      return value.number / 1000; // Convert MS to S
136    }
137
138    cleanup() {
139      // Clear all inline styles, otherwise they will throw off animations for images.
140      gsap.set(this.element, { clearProps: 'all' });
141    }
142
143    reset(element) {
144      const el = element || this.element;
145      el.classList.add(this.beforeClass);
146      el.classList.remove(this.completedClass);
147      el.classList.remove(this.animatingClass);
148    }
149
150    getOffset(offset, anchor) {
151      const defaultOffset = 120;
152
153      // Top-bottom placement looks terrible if the element is animated with offset zero.
154      if (anchor === 'top bottom' && offset.number === 0) {
155        return defaultOffset;
156      }
157
158      return offset.number;
159    }
160
161    createTween() {
162      const type = this.options.animation_type;
163      const animations = this.getAnimations();
164
165      if (!animations[type]) {
166        console.error(`[ENTRANCE] The selected ${type}
166 animation is invalid.`);
167        return;
168      }
169
170      const [from, to] = animations[type];
171
172      const { ease, once, anchorPlacement } = this.options.advanced;
173      const duration = this.getDuration(this.options.duration);
174      const delay = this.getDuration(this.options.delay) + this.delay;
175      const offset = this.getOffset(this.options.advanced.offset, anchorPlacement);
176      const [elementStart, viewportStart] = anchorPlacement.split(' ');
177
178      const anim = gsap.timeline({
179        delay,
180        paused: true
181      });
182
183      this.element.classList.add(this.beforeClass);
184
185      if (isBuilder()) {
186        this.hideEl();
187      }
188
189      this.startTrigger = ScrollTrigger.create({
190        trigger: this.element,
191        start: `${elementStart}+=${offset} ${viewportStart}`,
192        toggleActions: "play none none none",
193        once,
194        onEnter: () => anim.play()
195      });
196
197      anim.fromTo(this.element, {
198        ...from,
199        autoAlpha: 0,
200      },
201      {
202        ...to,
203        duration,
204        delay,
205        ease,
206        autoAlpha: 1,
207        clearProps: 'all',
208        immediateRender: false,
209        onStart: () => {
210          this.element.classList.add(this.animatingClass);
211          this.element.classList.remove(this.beforeClass);
212        },
213        onComplete: () => {
214          this.element.classList.remove(this.animatingClass);
215          this.element.classList.add(this.completedClass);
216        },
217        onReverseComplete: () => {
218          this.element.classList.add(this.beforeClass);
219          this.element.classList.remove(this.animatingClass);
220        },
221      });
222
223      if (!once) {
224        this.goToBeginningOnReverse(anim);
225      }
226
227      return anim;
228    }
229
230    goToBeginningOnReverse(animation) {
231      // Reset animation to the beginning once its goes offscreen at the bottom.
232      this.endTrigger = ScrollTrigger.create({
233        trigger: this.element,
234        start: `top-=10 bottom`,
235        onLeaveBack: () => {
236          // Reset the animation to the beginning.
237          animation.pause(0);
238
239          // Clear all inline styles.
240          this.cleanup();
241
242          // Trick the builder into making the element selectable.
243          // This is needed because activating the element sets its visibility to hidden.
244          if (isBuilder()) {
245            this.hideEl();
246          }
247
248          this.reset();
249        }
250      });
251    }
252
253    hideEl() {
254      gsap.set(this.element, { autoAlpha: 0 });
255    }
256
257    update(options) {
258      this.options = mergeObjects(this.defaultOptions, options);
259      this.destroy();
260      this.init();
261    }
262
263    replay(delay = 0) {
264      this.destroy();
265      this.reset();
266      this.delay = delay;
267      this.init();
268    }
269
270    destroy() {
271      this.initialized = false;
272
273      if (!this.element) return;
274
275      this.element.classList.add(this.completedClass);
276
277      if (!this.tween) return;
278
279      this.tween.kill();
280      this.tween = null;
281      this.startTrigger?.kill();
282      this.endTrigger?.kill();
283
284      // Remove all inline styles
285      gsap.set(this.element, { clearProps: 'all' });
286    }
287
288    refresh() {
289      ScrollTrigger.refresh();
290    }
291
292    initTween() {
293      if (this.initialized) return;
294      this.initialized = true;
295
296      this.element.classList.remove(this.completedClass);
297      this.tween = this.createTween();
298    }
299
300    initOrDestroy() {
301      if (this.canAnimate()) {
302        this.initTween();
303      } else {
304        this.destroy();
305      }
306    }
307
308    init() {
309      if (!this.options.animation_type) return;
310
311      this.element = document.querySelector(this.selector);
312      this.element.bdAnim = this;
313
314      if (prefersReducedMotion()) {
315        this.element.classList.add(this.completedClass);
316        return;
317      }
318
319      onResize(() => this.initOrDestroy());
320      this.rootEl.classList.add(this.enabledClass);
321    }
322
323    static autoload() {
324      const loaded = imagesLoaded(document.body);
325
326      // Listen for images to be loaded.
327      loaded.on("always", () => {
328        // Refresh all instances.
329        const event = new Event("breakdance_refresh_animations", { bubbles: true });
330        document.dispatchEvent(event);
331
332        // Refresh ScrollTrigger only once.
333        refreshScrollTrigger();
334      });
335
336      // Play event
337      addEventListener("breakdance_play_animations", (event) => {
338        const target = event.target === window ? document.body : event.target;
339
340        const nodes = [
341          target,
342          ...target.querySelectorAll("[data-entrance]")
343        ];
344
345        nodes.forEach((el) => el.bdAnim?.replay(event?.detail?.delay));
346      });
347
348      // Cleanup event
349      addEventListener("breakdance_refresh_animations", (event) => {
350        const target = event.target === window ? document.body : event.target;
351
352        const nodes = [
353          target,
354          ...target.querySelectorAll("[data-entrance]")
355        ];
356
357        nodes.forEach((el) => el.bdAnim?.cleanup());
358      });
359
360      addEventListener("breakdance_reset_animations", (event) => {
361        const target = event.target === window ? document.body : event.target;
362
363        const nodes = [
364          target,
365          ...target.querySelectorAll("[data-entrance]")
366        ];
367
368        nodes.forEach((el) => el.bdAnim?.reset(el));
369      });
370    }
371
372    static dontLetScrollTriggerMutateScrollPosition() {
373      window.addEventListener("load", () => {
374        if (!location.hash) return;
375        if (window.bdeAnimationScrolled) return;
376        const scrollElem = document.querySelector(location.hash);
377        if (!scrollElem) return;
378
379        // Safari fix: Scroll to the element after the next repaint.
380        requestAnimationFrame(() => {
381          scrollElem.scrollIntoView({
382            behavior: "smooth",
383          });
384        });
385
386        // Prevent scrolling to the same element twice.
387        window.bdeAnimationScrolled = true;
388      });
389    }
390  }
391
392  window.BreakdanceEntrance = BreakdanceEntrance;
393
394  BreakdanceEntrance.autoload();
395  BreakdanceEntrance.dontLetScrollTriggerMutateScrollPosition();
396}());

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.