PageSourceSearch

https://www.iaslc.org/modules/contrib/blazy/js/blazy.load.js?tlu4o2

js iaslc.org collected 2026-09-24 18:06:49 UTC 17,243 bytes, 519 lines download raw bytes

1/**
2 * @file
3 * Provides native, Intersection Observer API, or bLazy lazy loader.
4 */
5
6(function (Drupal, drupalSettings, _db, window, document) {
7
8  'use strict';
9
10  var _dataAnimation = 'data-animation';
11  var _dataDimensions = 'data-dimensions';
12  var _dataBg = 'data-backgrounds';
13  var _dataRatio = 'data-ratio';
14  var _isNativeExecuted = false;
15  var _resizeTick = 0;
16
17  /**
18   * Blazy public methods.
19   *
20   * @namespace
21   */
22  Drupal.blazy = Drupal.blazy || {
23    context: null,
24    init: null,
25    instances: [],
26    items: [],
27    windowWidth: 0,
28    blazySettings: drupalSettings.blazy || {},
29    ioSettings: drupalSettings.blazyIo || {},
30    revalidate: false,
31    options: {},
32    globals: function () {
33      var me = this;
34      var commons = {
35        success: me.clearing.bind(me),
36        error: me.clearing.bind(me),
37        selector: '.b-lazy',
38        errorClass: 'b-error',
39        successClass: 'b-loaded'
40      };
41
42      return _db.extend(me.blazySettings, me.ioSettings, commons);
43    },
44
45    clearing: function (el) {
46      var me = this;
47      var cn = _db.closest(el, '.media');
48      var an = _db.closest(el, '[' + _dataAnimation + ']');
49
50      // Clear loading classes.
51      me.clearLoading(el);
52
53      // Reevaluate the element.
54      me.reevaluate(el);
55
56      // Container might be the el itself for BG, do not NULL check here.
57      me.updateContainer(el, cn);
58
59      // Supports blur, animate.css for CSS background, picture, image, media.
60      if (an !== null || me.has(el, _dataAnimation)) {
61        _db.animate(an !== null ? an : el);
62      }
63
64      // Provides event listeners for easy overrides without full overrides.
65      // Runs before native to allow native use this on its own onload event.
66      _db.trigger(el, 'blazy.done', {options: me.options});
67
68      // Initializes the native lazy loading once the first found is loaded.
69      if (!_isNativeExecuted) {
70        _db.trigger(me.context, 'blazy.native', {options: me.options});
71
72        _isNativeExecuted = true;
73      }
74    },
75
76    clearLoading: function (el) {
77      // The .b-lazy element can be attached to IMG, or DIV as CSS background.
78      // The .(*)loading can be .media, .grid, .slide__content, .box, etc.
79      var loaders = [
80        el,
81        _db.closest(el, '.is-loading'),
82        _db.closest(el, '[class*="loading"]')
83      ];
84
85      _db.forEach(loaders, function (loader) {
86        if (loader !== null) {
87          loader.className = loader.className.replace(/(\S+)loading/g, '');
88        }
89      });
90    },
91
92    isLoaded: function (el) {
93      return el !== null && el.classList.contains(this.options.successClass);
94    },
95
96    reevaluate: function (el) {
97      var me = this;
98      var ie = el.classList.contains('b-responsive') && el.hasAttribute('data-pfsrc');
99
100      // In case an error, try forcing it, once.
101      if (me.init !== null && _db.hasClass(el, me.options.errorClass) && !_db.hasClass(el, 'b-checked')) {
102        el.classList.add('b-checked');
103
104        // This is a rare case, hardly called, just nice to have for errors.
105        window.setTimeout(function () {
106          if (me.has(el, _dataBg)) {
107            _db.updateBg(el, me.options.mobileFirst);
108          }
109          else {
110            me.init.load(el);
111          }
112        }, 100);
113      }
114
115      // @see http://scottjehl.github.io/picturefill/
116      if (window.picturefill && ie) {
117        window.picturefill({
118          reevaluate: true,
119          elements: [el]
120        });
121      }
122    },
123
124    has: function (el, attribute) {
125      return el !== null && el.hasAttribute(attribute);
126    },
127
128    contains: function (el, name) {
129      return el !== null && el.classList.contains(name);
130    },
131
132    updateContainer: function (el, cn) {
133      var me = this;
134      var isPicture = _db.equal(el.parentNode, 'picture') && me.has(cn, _dataDimensions);
135
136      // Fixed for effect Blur messes up Aspect ratio Fluid calculation.
137      window.setTimeout(function () {
138        if (me.isLoaded(el)) {
139          // Adds context for effetcs: blur, etc. considering BG, or just media.
140          (me.contains(cn, 'media') ? cn : el).classList.add('is-b-loaded');
141
142          // Only applies to ratio fluid.
143          if (isPicture) {
144            me.updatePicture(el, cn);
145          }
146
147          // Basically makes multi-breakpoint BG work for IO or old bLazy once.
148          if (me.has(el, _dataBg)) {
149            _db.updateBg(el, me.options.mobileFirst);
150          }
151        }
152      });
153    },
154
155    updatePicture: function (el, cn) {
156      var me = this;
157      var pad = Math.round(((el.naturalHeight / el.naturalWidth) * 100), 2);
158
159      cn.style.paddingBottom = pad + '%';
160
161      // Swap all aspect ratio once to reduce abrupt ratio changes for the rest.
162      if (me.instances.length > 0) {
163        var picture = function (elm) {
164          if (!('blazyInstance' in elm) && !('blazyUniform' in elm)) {
165            return;
166          }
167
168          if ((elm.blazyInstance === cn.blazyInstance) && (_resizeTick > 1 || !('isBlazyPicture' in elm))) {
169            _db.trigger(elm, 'blazy.uniform.' + elm.blazyInstance, {pad: pad});
170            elm.isBlazyPicture = true;
171          }
172        };
173
174        // Uniform sizes must apply to each instance, not globally.
175        _db.forEach(me.instances, function (elm) {
176          Drupal.debounce(picture(elm), 201, true);
177        }, me.context);
178      }
179    },
180
181    /**
182     * Attempts to fix for Views rewrite stripping out data URI causing 404.
183     *
184     * E.g.: src="image/jpg;base64 should be src="data:image/jpg;base64.
185     * The "Placeholder" 1px.gif via Blazy UI costs extra HTTP requests. This is
186     * a less costly solution, but not bulletproof due to being client-side
187     * which means too late to the party. Yet not bad for 404s below the fold.
188     * This must be run before any lazy (native, bLazy or IO) kicks in.
189     *
190     * @todo remove if a permanent non-client available other than Placeholder.
191     */
192    fixMissingDataUri: function () {
193      var me = this;
194      var doc = me.context;
195      var sel = me.options.selector + '[src^="image"]:not(.' + me.options.successClass + ')';
196      var els = doc.querySelector(sel) === null ? [] : doc.querySelectorAll(sel);
197
198      var fixDataUri = function (img) {
199        var src = img.getAttribute('src');
200        if (src.indexOf('base64') !== -1 || src.indexOf('svg+xml') !== -1) {
201          img.setAttribute('src', src.replace('image', 'data:image'));
202        }
203      };
204
205      if (els.length > 0) {
206        _db.forEach(els, fixDataUri);
207      }
208    },
209
210    /**
211     * Updates the dynamic multi-breakpoint aspect ratio: bg, picture or image.
212     *
213     * This only applies to Responsive images with aspect ratio fluid.
214     * Static ratio (media--ratio--169, etc.) is ignored and uses CSS instead.
215     *
216     * @param {HTMLElement} cn
217     *   The .media--ratio--fluid container HTML element.
218     */
219    updateRatio: function (cn) {
220      var me = this;
221      var el = _db.closest(cn, '.blazy');
222      var dimensions = _db.parse(cn.getAttribute(_dataDimensions));
223
224      if (!dimensions) {
225        me.updateFallbackRatio(cn);
226        return;
227      }
228
229      // For picture, this is more a dummy space till the image is downloaded.
230      var isPicture = cn.querySelector('picture') !== null && _resizeTick > 0;
231      var pad = _db.activeWidth(dimensions, isPicture);
232
233      // Provides marker for grouping between multiple instances.
234      cn.blazyInstance = 'blazyInstance' in el ? el.blazyInstance : null;
235      if (pad !== 'undefined') {
236        cn.style.paddingBottom = pad + '%';
237      }
238
239      // Fix for picture or bg element with resizing.
240      if (_resizeTick > 0 && (isPicture || me.has(cn, _dataBg))) {
241        me.updateContainer((isPicture ? cn.querySelector('img') : cn), cn);
242      }
243    },
244
245    updateFallbackRatio: function (cn) {
246      // Only rewrites if the style is indeed stripped out by Twig, and not set.
247      if (!cn.hasAttribute('style') && cn.hasAttribute(_dataRatio)) {
248        cn.style.paddingBottom = cn.getAttribute(_dataRatio) + '%';
249      }
250    },
251
252    /**
253     * Swap lazy attributes to let supportive browsers lazy load them.
254     *
255     * This means Blazy and even IO should not lazy-load them any more.
256     * Ensures to not touch lazy-loaded AJAX, or likely non-supported elements:
257     * Video, DIV, etc. Only IMG and IFRAME are supported for now.
258     * Due to native init is deferred, the first row is still using IO/ bLazy.
259     */
260    doNativeLazy: function () {
261      var me = this;
262
263      if (!me.isNativeLazy()) {
264        return;
265      }
266
267      var doc = me.context;
268      var sel = me.options.selector + '[loading]:not(.' + me.options.successClass + ')';
269
270      me.items = doc.querySelector(sel) === null ? [] : doc.querySelectorAll(sel);
271      if (me.items.length === 0) {
272        return;
273      }
274
275      var onNativeEvent = function (e) {
276        var el = e.target;
277        var er = e.type === 'error';
278
279        // Refines based on actual result, runs clearing, animation, etc.
280        el.classList.add(me.options[er ? 'errorClass' : 'successClass']);
281        me.clearing(el);
282
283        _db.unbindEvent(el, e.type, onNativeEvent);
284      };
285
286      var doNative = function (el) {
287        // Reset attributes, and let supportive browsers lazy load natively.
288        _db.setAttrs(el, ['srcset', 'src'], true);
289
290        // Also supports PICTURE or (future) VIDEO which contains SOURCEs.
291        _db.setAttrsWithSources(el, false, true);
292
293        // Blur thumbnail is just making use of the swap due to being small.
294        if (me.contains(el, 'b-blur')) {
295          el.removeAttribute('loading');
296        }
297        else {
298          // Mark it loaded to prevent bLazy/ IO to do any further work.
299          el.classList.add(me.options.successClass);
300
301          // Attempts to make nice with the harsh native, defer clearing, etc.
302          _db.bindEvent(el, 'load', onNativeEvent);
303          _db.bindEvent(el, 'error', onNativeEvent);
304        }
305      };
306
307      var onNative = function () {
308        _db.forEach(me.items, doNative);
309      };
310
311      _db.bindEvent(me.context, 'blazy.native', onNative, {once: true});
312    },
313
314    isNativeLazy: function () {
315      return 'loading' in HTMLImageElement.prototype;
316    },
317
318    isIo: function () {
319      return this.ioSettings && this.ioSettings.enabled && 'IntersectionObserver' in window;
320    },
321
322    isRo: function () {
323      return 'ResizeObserver' in window;
324    },
325
326    isBlazy: function () {
327      return !this.isIo() && 'Blazy' in window;
328    },
329
330    forEach: function (context) {
331      var blazies = context.querySelectorAll('.blazy:not(.blazy--on)');
332
333      // Various use cases: w/o formaters, custom, or basic, and mixed.
334      // The [data-blazy] is set by the module for formatters, or Views gallery.
335      if (blazies.length > 0) {
336        _db.forEach(blazies, doBlazy, context);
337      }
338
339      // Initializes blazy, we'll decouple features from lazy load scripts.
340      // We'll revert to 2.1 if any issue with this.
341      initBlazy(context);
342    },
343
344    run: function (opts) {
345      return this.isIo() ? new BioMedia(opts) : new Blazy(opts);
346    },
347
348    afterInit: function () {
349      var me = this;
350      var doc = me.context;
351      var rObserver = false;
352      var ratioItems = doc.querySelector('.media--ratio') === null ? [] : doc.querySelectorAll('.media--ratio');
353      var shouldLoop = ratioItems.length > 0;
354
355      var loopRatio = function (entries) {
356        me.windowWidth = _db.windowWidth();
357
358        // BC with bLazy, native/IO doesn't need to revalidate, bLazy does.
359        // Scenarios: long horizontal containers, Slick carousel slidesToShow >
360        // 3. If any issue, add a class `blazy--revalidate` manually to .blazy.
361        if (!me.isNativeLazy() && (me.isBlazy() || me.revalidate)) {
362          me.init.revalidate(true);
363        }
364
365        if (shouldLoop) {
366          _db.forEach(entries, function (entry) {
367            me.updateRatio('target' in entry ? entry.target : entry);
368          }, doc);
369        }
370
371        _resizeTick++;
372        return false;
373      };
374
375      var checkRatio = function () {
376        return me.isRo() ? new ResizeObserver(loopRatio) : loopRatio(ratioItems);
377      };
378
379      // Checks for aspect ratio, onload event is a bit later.
380      // Uses ResizeObserver for modern browsers, else degrades.
381      rObserver = checkRatio();
382      if (rObserver) {
383        if (shouldLoop) {
384          _db.forEach(ratioItems, function (entry) {
385            rObserver.observe(entry);
386          }, doc);
387        }
388      }
389      else {
390        _db.bindEvent(window, 'resize', Drupal.debounce(checkRatio, 200, true));
391      }
392    }
393  };
394
395  /**
396   * Initialize the blazy instance, either basic, advanced, or native.
397   *
398   * @param {HTMLElement} context
399   *   This can be document, or anything weird.
400   */
401  var initBlazy = function (context) {
402    var me = Drupal.blazy;
403    var documentElement = context instanceof HTMLDocument ? context : _db.closest(context, 'html');
404    var opts = {};
405
406    opts.mobileFirst = opts.mobileFirst || false;
407
408    // Weirdo: documentElement is null after colorbox cbox_close event.
409    documentElement = documentElement || document;
410
411    // Set docroot in case we are in an iframe.
412    if (!document.documentElement.isSameNode(documentElement)) {
413      opts.root = documentElement;
414    }
415
416    me.options = _db.extend({}, me.globals(), opts);
417    me.context = documentElement;
418
419    // Old bLazy, not IO, might need scrolling CSS selector like Modal library.
420    // A scrolling modal with an iframe like Entity Browser has no issue since
421    // the scrolling container is the entire DOM. Another use case is parallax.
422    var scrollElms = '#drupal-modal, .is-b-scroll';
423    if (me.options.container) {
424      scrollElms += ', ' + me.options.container.trim();
425    }
426    me.options.container = scrollElms;
427
428    // Attempts to fix for Views rewrite stripping out data URI causing 404.
429    me.fixMissingDataUri();
430
431    // Swap lazy attributes to let supportive browsers lazy load them.
432    me.doNativeLazy();
433
434    // Put the blazy/IO instance into a public object for references/ overrides.
435    // If native lazy load is supported, the following will skip internally.
436    me.init = me.run(me.options);
437
438    // Runs after init.
439    me.afterInit();
440  };
441
442  /**
443   * Blazy utility functions.
444   *
445   * @param {HTMLElement} elm
446   *   The .blazy/[data-blazy] container, not the lazyloaded .b-lazy element.
447   *
448   * @todo reenable initBlazy here if any issue with the following:
449   *   Each [data-blazy] may or may not:
450   *     - be ajaxified, be lightboxed, have uniform or different sizes, and
451   *       have few more unique features per instance, etc.
452   */
453  function doBlazy(elm) {
454    var me = Drupal.blazy;
455    var dataAttr = elm.getAttribute('data-blazy');
456    var opts = (!dataAttr || dataAttr === '1') ? {} : (_db.parse(dataAttr) || {});
457    var isUniform = me.contains(elm, 'blazy--field') || me.contains(elm, 'block-grid') || me.contains(elm, 'blazy--uniform');
458    var instance = (Math.random() * 10000).toFixed(0);
459    var eventId = 'blazy.uniform.' + instance;
460    var localItems = elm.querySelector('.media--ratio') === null ? [] : elm.querySelectorAll('.media--ratio');
461
462    me.options = _db.extend(me.options, opts);
463    me.revalidate = me.revalidate || elm.classList.contains('blazy--revalidate');
464    elm.classList.add('blazy--on');
465    elm.blazyInstance = instance;
466
467    if (isUniform) {
468      elm.blazyUniform = true;
469    }
470
471    me.instances.push(elm);
472
473    var swapRatio = function (e) {
474      var pad = e.detail.pad || 0;
475
476      if (pad > 10) {
477        _db.forEach(localItems, function (cn) {
478          cn.style.paddingBottom = pad + '%';
479        }, elm);
480      }
481    };
482
483    // Reduces abrupt ratio changes for the rest after the first loaded.
484    // To support resizing, use debounce. To disable use {once: true}.
485    if (isUniform && localItems.length > 0) {
486      _db.bindEvent(elm, eventId, swapRatio);
487    }
488  }
489
490  /**
491   * Attaches blazy behavior to HTML element identified by .blazy/[data-blazy].
492   *
493   * The .blazy/[data-blazy] is the .b-lazy container, might be .field, etc.
494   * The .b-lazy is the individual IMG, IFRAME, PICTURE, VIDEO, DIV, BODY, etc.
495   * The lazy-loaded element is .b-lazy, not its container. Note the hypen (b-)!
496   *
497   * @type {Drupal~behavior}
498   */
499  Drupal.behaviors.blazy = {
500    attach: function (context) {
501      // Drupal.attachBehaviors already does this so if this is necessary,
502      // someone does an invalid call. But let's be robust here.
503      // Note: context can be unexpected <script> element with Media library.
504      context = context || document;
505
506      // Originally identified at D7, yet might happen at D8 with AJAX.
507      // Prevents jQuery AJAX messes up where context might be an array.
508      if ('length' in context) {
509        context = context[0];
510      }
511
512      // Runs Blazy with multi-serving images, and aspect ratio supports.
513      // W/o [data-blazy] to address various scenarios like custom simple works,
514      // or within Views UI which is not easy to set [data-blazy] via UI.
515      _db.once(Drupal.blazy.forEach(context));
516    }
517  };
518
519}(Drupal, drupalSettings, dBlazy, this, this.document));

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.